aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--flake.lock861
-rw-r--r--flake.lock.license9
-rw-r--r--flake.nix260
-rw-r--r--flake/default.nix5
-rw-r--r--hosts/by-name/apzu/configuration.nix6
-rw-r--r--hosts/by-name/apzu/hardware.nix11
-rw-r--r--hosts/by-name/tiamat/configuration.nix2
-rw-r--r--hosts/by-name/tiamat/hardware.nix4
-rw-r--r--hosts/by-name/tiamat/legacy-disk-module/fstrim.nix51
-rw-r--r--hosts/by-name/tiamat/legacy-disk-module/hibernate.nix55
-rw-r--r--hosts/by-name/tiamat/legacy-disk-module/module.nix159
-rw-r--r--hosts/default.nix7
-rw-r--r--inputs.nix136
-rw-r--r--modules/by-name/at/atuin/module.nix67
-rw-r--r--modules/by-name/at/atuin/secrets/encryption_key.age27
-rw-r--r--modules/by-name/at/atuin/secrets/user_id.age14
-rw-r--r--modules/by-name/au/ausweisapp/module.nix21
-rw-r--r--modules/by-name/ba/backup/env.vars1
-rw-r--r--modules/by-name/ba/backup/module.nix33
-rw-r--r--modules/by-name/bo/boot/iso_entry/archlive_iso.nix86
-rw-r--r--modules/by-name/bo/boot/iso_entry/signing_key.nix27
-rw-r--r--modules/by-name/bo/boot/module.nix292
-rw-r--r--modules/by-name/ca/calibre/module.nix38
-rw-r--r--modules/by-name/ca/cargo/module.nix14
-rw-r--r--modules/by-name/cl/cleanup/module.nix5
-rwxr-xr-xmodules/by-name/di/direnv/landrun-fish.sh48
-rw-r--r--modules/by-name/di/direnv/module.nix90
-rwxr-xr-xmodules/by-name/di/direnv/pre-cache.sh89
-rw-r--r--modules/by-name/di/disks/hibernate.nix7
-rw-r--r--modules/by-name/di/disks/module.nix85
-rw-r--r--[-rwxr-xr-x]modules/by-name/fi/fish/config/01_show-task.fish (renamed from modules/by-name/lf/lf/wrappers/lm/lm.sh)22
-rw-r--r--modules/by-name/fi/fish/config/keybindings/00__start.fish1
-rw-r--r--modules/by-name/fi/fish/config/keybindings/01_insert.fish86
-rw-r--r--modules/by-name/fi/fish/config/keybindings/02_command.fish185
-rw-r--r--modules/by-name/fi/fish/config/keybindings/03_visual.fish94
-rw-r--r--modules/by-name/fi/fish/config/keybindings/04_replace.fish9
-rw-r--r--modules/by-name/fi/fish/config/keybindings/05_operator.fish33
-rw-r--r--modules/by-name/fi/fish/config/keybindings/06_replace_one.fish7
-rw-r--r--modules/by-name/fi/fish/config/keybindings/07_others.fish9
-rw-r--r--modules/by-name/fi/fish/config/keybindings/08__end.fish1
-rw-r--r--modules/by-name/fi/fish/module.nix85
-rw-r--r--modules/by-name/fo/fonts/module.nix5
-rw-r--r--modules/by-name/fo/foot/module.nix75
-rw-r--r--modules/by-name/fo/foot/theme.ini40
-rw-r--r--modules/by-name/gi/git/aliases.nix15
-rw-r--r--modules/by-name/gi/git/git_ignore.git3
-rw-r--r--modules/by-name/gi/git/module.nix13
-rw-r--r--modules/by-name/gp/gpg/module.nix60
-rw-r--r--modules/by-name/gt/gtk/module.nix82
-rw-r--r--modules/by-name/i3/i3bar-river/module.nix23
-rw-r--r--modules/by-name/i3/i3status-rust/module.nix4
-rwxr-xr-xmodules/by-name/i3/i3status-rust/scripts/mpd_song_name.sh8
-rw-r--r--modules/by-name/im/impermanence/module.nix26
-rw-r--r--modules/by-name/lf/lf/colors2
-rwxr-xr-xmodules/by-name/lf/lf/commands/base.sh10
-rw-r--r--modules/by-name/lf/lf/commands/default.nix32
-rwxr-xr-xmodules/by-name/lf/lf/commands/scripts/archive_decompress.sh5
-rwxr-xr-xmodules/by-name/lf/lf/commands/scripts/cd_lf_make_map.sh27
-rwxr-xr-xmodules/by-name/lf/lf/commands/scripts/trash_restore.sh6
-rw-r--r--modules/by-name/lf/lf/ctpv/helpers.sh1
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/application/archive/default.nix9
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/application/epub/default.nix1
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/application/pdf/default.nix1
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/application/x-pem-file/default.nix4
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/audio/default.nix3
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/font/default.nix2
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/image/default.nix1
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/image/svg+xml/default.nix1
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/inode/directory/default.nix (renamed from modules/by-name/lf/lf/ctpv/prev/inode/default.nix)6
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/inode/directory/ls.sh (renamed from modules/by-name/lf/lf/ctpv/prev/inode/ls.sh)0
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/inode/fifo/default.nix19
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/inode/fifo/fifo.sh (renamed from modules/by-name/lf/lf/wrappers/ll/default.nix)14
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/inode/symlink/default.nix19
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/inode/symlink/symlink.sh (renamed from modules/by-name/lf/lf/ctpv/prev/inode/symlink.sh)0
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/inode/x-empty/default.nix19
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/inode/x-empty/empty.sh (renamed from modules/by-name/lf/lf/wrappers/lm/default.nix)14
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/message/rfc822/default.nix26
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/message/rfc822/rfc822.sh22
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/text/default.nix3
-rw-r--r--modules/by-name/lf/lf/ctpv/prev/video/default.nix1
-rw-r--r--modules/by-name/lf/lf/keybindings/default.nix184
-rw-r--r--modules/by-name/lf/lf/module.nix67
-rw-r--r--modules/by-name/lf/lf/wrappers/default.nix4
-rw-r--r--modules/by-name/lf/lf/wrappers/ll.fish41
-rwxr-xr-xmodules/by-name/lf/lf/wrappers/ll/ll.sh29
-rw-r--r--modules/by-name/lf/lf/wrappers/lm.fish20
-rw-r--r--modules/by-name/lo/locale/module.nix3
-rw-r--r--modules/by-name/lu/lutris/module.nix55
-rw-r--r--modules/by-name/ly/ly/blackhole-smooth-240x67.durbin0 -> 484298 bytes
-rw-r--r--modules/by-name/ly/ly/module.nix130
-rw-r--r--modules/by-name/ly/ly/setup.sh47
-rw-r--r--modules/by-name/me/mergiraf/module.nix1
-rw-r--r--modules/by-name/mp/mpd/module.nix8
-rw-r--r--modules/by-name/mp/mpd/mpc.nix32
-rw-r--r--modules/by-name/mp/mpdpopm/module.nix68
-rw-r--r--modules/by-name/mp/mpv/module.nix60
-rw-r--r--modules/by-name/ni/nix-index/command_not_found.fish111
-rw-r--r--modules/by-name/ni/nix-index/command_not_found.zsh (renamed from modules/by-name/ni/nix-index/command_not_found.sh)0
-rw-r--r--modules/by-name/ni/nix-index/module.nix6
-rw-r--r--modules/by-name/ni/nix/module.nix89
-rw-r--r--modules/by-name/ni/nixos-shell/module.nix5
-rw-r--r--modules/by-name/ni/nixpkgs/module.nix13
-rw-r--r--modules/by-name/nv/nvim/module.nix4
-rw-r--r--modules/by-name/nv/nvim/plgs/femaco/lua/femaco.lua43
-rw-r--r--modules/by-name/nv/nvim/plgs/lsp/servers/default.nix3
-rw-r--r--modules/by-name/nv/nvim/plgs/lsp/servers/servers/openscad.nix19
-rw-r--r--modules/by-name/nv/nvim/plgs/lsp/servers/servers/quick-lint-js.nix4
-rw-r--r--modules/by-name/nv/nvim/plgs/lsp/servers/servers/zls.nix30
-rw-r--r--modules/by-name/nv/nvim/plgs/luasnip/snippets/all.lua178
-rw-r--r--modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/delimiter.lua20
-rw-r--r--modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/greek.lua68
-rw-r--r--modules/by-name/nv/nvim/plgs/neorg/default.nix2
-rw-r--r--modules/by-name/nv/nvim/plgs/nvim-cmp/default.nix6
-rw-r--r--modules/by-name/pl/plymouth/module.nix33
-rw-r--r--modules/by-name/po/power/module.nix29
-rw-r--r--modules/by-name/qu/qutebrowser/include/redirects.py2
-rw-r--r--modules/by-name/qu/qutebrowser/module.hm.nix4
-rw-r--r--modules/by-name/qu/qutebrowser/module.nix6
-rw-r--r--modules/by-name/qu/qutebrowser/settings/default.nix4
-rw-r--r--modules/by-name/ri/river/keymap.nix15
-rw-r--r--modules/by-name/ri/river/module.nix67
-rw-r--r--modules/by-name/ss/ssh/module.nix50
-rw-r--r--modules/by-name/st/starship/module.nix157
-rw-r--r--modules/by-name/st/steam/module.nix20
-rw-r--r--modules/by-name/sw/swaybg/images/abstract-nord.png (renamed from modules/common/abstract-nord.png)bin140219 -> 140219 bytes
-rw-r--r--modules/by-name/sw/swaybg/images/abstract-nord.png.license (renamed from modules/common/abstract-nord.png.license)0
-rw-r--r--modules/by-name/sw/swaybg/module.nix35
-rw-r--r--modules/by-name/sw/swayidle/module.nix45
-rw-r--r--modules/by-name/ta/taskwarrior/module.nix7
-rw-r--r--modules/by-name/un/unison/module.nix11
-rw-r--r--modules/by-name/us/users/module.nix5
-rw-r--r--modules/by-name/ve/version/module.nix12
-rw-r--r--modules/by-name/xd/xdg/module.nix81
-rw-r--r--modules/by-name/yt/yt/module.nix4
-rw-r--r--modules/by-name/zs/zsh/module.nix3
-rw-r--r--modules/common/default.nix80
-rw-r--r--modules/common/nixos_shell_configuration.nix66
-rw-r--r--modules/common/projects.json4
-rw-r--r--modules/home.legacy/conf/beets/default.nix28
-rw-r--r--modules/home.legacy/conf/beets/plugins.nix9
-rw-r--r--modules/home.legacy/conf/beets/plugins/default.nix1
-rw-r--r--modules/home.legacy/conf/beets/plugins/ihate/default.nix1
-rw-r--r--modules/home.legacy/conf/beets/plugins/inline/default.nix4
-rw-r--r--modules/home.legacy/conf/beets/plugins/mbsubmit/default.nix0
-rw-r--r--modules/home.legacy/conf/beets/plugins/smartplaylist/default.nix42
-rw-r--r--modules/home.legacy/conf/default.nix3
-rw-r--r--modules/home.legacy/conf/gtk/default.nix65
-rw-r--r--modules/home.legacy/conf/starship/default.nix143
-rw-r--r--modules/home.legacy/conf/swayidle/config5
-rw-r--r--modules/home.legacy/conf/swayidle/config.license9
-rw-r--r--modules/home.legacy/conf/swayidle/default.nix35
-rw-r--r--modules/home.legacy/default.nix35
-rw-r--r--modules/home.legacy/pkgs/default.nix33
-rw-r--r--notes/beets_import.md20
-rw-r--r--pkgs/by-name/at/atuin-dvorak/package.nix32
-rw-r--r--pkgs/by-name/at/atuin-dvorak/set-dvorak-keybindings.patch37
-rw-r--r--pkgs/by-name/at/atuin-dvorak/set-dvorak-keybindings.patch.license9
-rwxr-xr-xpkgs/by-name/au/aumo/aumo.sh16
-rw-r--r--pkgs/by-name/au/aumo/package.nix6
-rwxr-xr-xpkgs/by-name/co/con2pdf/con2pdf.sh6
-rw-r--r--pkgs/by-name/di/direnv-patched/0001-rc-Also-consider-.nenvrc-files.patch28
-rw-r--r--pkgs/by-name/di/direnv-patched/package.nix28
-rw-r--r--pkgs/by-name/fi/fish-patched/Cargo.lock3551
-rw-r--r--pkgs/by-name/fi/fish-patched/package.nix38
-rw-r--r--pkgs/by-name/fi/fish-patched/patches/0003-feat-history-Add-turtle-as-history-backend.patch843
-rw-r--r--pkgs/by-name/fi/fish-patched/patches/0004-history-turtle-Fix-id-misuse-and-history-return-orde.patch50
-rw-r--r--pkgs/by-name/fi/fish-patched/patches/0005-history-history-Always-save-history-even-in-private-.patch32
-rw-r--r--pkgs/by-name/fi/fish-patched/patches/0006-history-turtle-Don-t-block-at-startup-and-wait-until.patch101
-rw-r--r--pkgs/by-name/fi/fish-patched/patches/0007-history-turtle-Don-t-reverse-newly-loaded-history.patch27
-rw-r--r--pkgs/by-name/fi/fish-patched/patches/0008-history-turtle-Reverse-history-upon-load.patch28
-rw-r--r--pkgs/by-name/fi/fish-patched/patches/0009-history-turtle-Do-more-work-in-the-command-handler-w.patch188
-rw-r--r--pkgs/by-name/fu/fupdate-sys/package.nix41
-rw-r--r--pkgs/by-name/fu/fupdate/Cargo.lock144
-rw-r--r--pkgs/by-name/fu/fupdate/Cargo.toml6
-rw-r--r--pkgs/by-name/fu/fupdate/flake.lock27
-rw-r--r--pkgs/by-name/fu/fupdate/flake.lock.license9
-rw-r--r--pkgs/by-name/fu/fupdate/flake.nix12
-rw-r--r--pkgs/by-name/fu/fupdate/src/cli.rs2
-rw-r--r--pkgs/by-name/fu/fupdate/src/main.rs2
-rwxr-xr-xpkgs/by-name/fu/fupdate/update.sh4
-rwxr-xr-xpkgs/by-name/gi/git-cgit/git-cgit.sh131
-rw-r--r--pkgs/by-name/gi/git-cgit/package.nix (renamed from pkgs/by-name/i3/i3status-rust-patched/package.nix)26
-rwxr-xr-xpkgs/by-name/hi/hibernate/hibernate.sh7
-rw-r--r--pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch190
-rw-r--r--pkgs/by-name/lf/lf-make-map/Cargo.lock360
-rw-r--r--pkgs/by-name/lf/lf-make-map/Cargo.toml7
-rw-r--r--pkgs/by-name/lf/lf-make-map/flake.lock27
-rw-r--r--pkgs/by-name/lf/lf-make-map/flake.lock.license9
-rw-r--r--pkgs/by-name/lf/lf-make-map/flake.nix10
-rw-r--r--pkgs/by-name/lf/lf-make-map/src/cli.rs6
-rw-r--r--pkgs/by-name/lf/lf-make-map/src/main.rs89
-rw-r--r--pkgs/by-name/lf/lf-make-map/src/mapping/interactive.rs172
-rw-r--r--pkgs/by-name/lf/lf-make-map/src/mapping/map_key.rs43
-rw-r--r--pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs1
-rwxr-xr-xpkgs/by-name/lf/lf-make-map/tests/base.sh2
-rw-r--r--pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs/output.old13
-rwxr-xr-xpkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs/test.sh49
-rw-r--r--pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs_duplicates/output.old17
-rwxr-xr-xpkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs_duplicates/test.sh53
-rwxr-xr-xpkgs/by-name/lf/lf-make-map/tests/cases/simple/test.sh8
-rwxr-xr-xpkgs/by-name/lf/lf-make-map/update.sh4
-rw-r--r--pkgs/by-name/mp/mpdpopm/.envrc22
-rw-r--r--pkgs/by-name/mp/mpdpopm/.gitignore16
-rw-r--r--pkgs/by-name/mp/mpdpopm/Cargo.lock2501
-rw-r--r--pkgs/by-name/mp/mpdpopm/Cargo.toml56
-rw-r--r--pkgs/by-name/mp/mpdpopm/README.md259
-rw-r--r--pkgs/by-name/mp/mpdpopm/README.org214
-rw-r--r--pkgs/by-name/mp/mpdpopm/build.rs15
-rw-r--r--pkgs/by-name/mp/mpdpopm/config.lsp11
-rw-r--r--pkgs/by-name/mp/mpdpopm/flake.nix56
-rw-r--r--pkgs/by-name/mp/mpdpopm/package.nix29
-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.rs591
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs150
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/clients.rs1200
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/config.rs189
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs233
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/dj/mod.rs28
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/filters.lalrpop160
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/filters_ast.rs1022
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/lib.rs185
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/messanges/mod.rs140
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/playcounts.rs342
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/storage/mod.rs145
-rw-r--r--pkgs/by-name/mp/mpdpopm/src/vars.rs4
-rwxr-xr-xpkgs/by-name/mp/mpdpopm/update.sh14
-rwxr-xr-xpkgs/by-name/mp/mpp-lyrics/mpp-lyrics.sh2
-rwxr-xr-xpkgs/by-name/mp/mpp-searchadd/mpp-searchadd.sh5
-rwxr-xr-xpkgs/by-name/mp/mpp/mpp.sh4
-rw-r--r--pkgs/by-name/mp/mpp/package.nix2
-rw-r--r--pkgs/by-name/no/notify-run/Cargo.lock4
-rw-r--r--pkgs/by-name/no/notify-run/Cargo.toml2
-rw-r--r--pkgs/by-name/no/notify-run/flake.lock27
-rw-r--r--pkgs/by-name/no/notify-run/flake.lock.license9
-rw-r--r--pkgs/by-name/no/notify-run/flake.nix7
-rw-r--r--pkgs/by-name/no/notify-run/src/main.rs82
-rwxr-xr-xpkgs/by-name/no/notify-run/update.sh4
-rw-r--r--pkgs/by-name/qu/qutebrowser-patched/package.nix30
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/Cargo.lock203
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/Cargo.toml28
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/flake.lock27
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/flake.lock.license9
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/flake.nix7
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs2
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs8
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs5
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs2
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs2
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs8
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs2
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs4
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs2
-rw-r--r--pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs11
-rwxr-xr-xpkgs/by-name/ri/river-mk-keymap/update.sh4
-rw-r--r--pkgs/by-name/sw/sw/package.nix25
-rwxr-xr-xpkgs/by-name/sw/sw/sw.sh49
-rw-r--r--pkgs/by-name/sy/systemd-toggle/package.nix23
-rwxr-xr-xpkgs/by-name/sy/systemd-toggle/systemd-toggle.sh46
-rw-r--r--pkgs/by-name/ta/tails-iso/files.json7
-rw-r--r--pkgs/by-name/ta/tails-iso/package.nix87
-rwxr-xr-xpkgs/by-name/ta/tails-iso/update.sh74
-rw-r--r--pkgs/by-name/ts/tskm/Cargo.lock650
-rw-r--r--pkgs/by-name/ts/tskm/Cargo.toml32
-rw-r--r--pkgs/by-name/ts/tskm/flake.lock27
-rw-r--r--pkgs/by-name/ts/tskm/flake.lock.license9
-rw-r--r--pkgs/by-name/ts/tskm/flake.nix7
-rw-r--r--pkgs/by-name/ts/tskm/src/browser/mod.rs45
-rw-r--r--pkgs/by-name/ts/tskm/src/cli.rs71
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/input/handle.rs11
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/input/mod.rs2
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs9
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/open/handle.rs28
-rw-r--r--pkgs/by-name/ts/tskm/src/interface/open/mod.rs2
-rw-r--r--pkgs/by-name/ts/tskm/src/main.rs11
-rw-r--r--pkgs/by-name/ts/tskm/src/state.rs26
-rw-r--r--pkgs/by-name/ts/tskm/src/task/mod.rs56
-rwxr-xr-xpkgs/by-name/ts/tskm/update.sh4
-rw-r--r--pkgs/by-name/yt/yt/package.nix4
-rwxr-xr-xpkgs/update_pkgs.sh2
-rw-r--r--scripts/npins_to_unflake.jq34
-rwxr-xr-xscripts/unflake.sh7
-rwxr-xr-xscripts/why-depends61
-rw-r--r--secrets.nix1
-rw-r--r--unflake.nix393
-rwxr-xr-xupdate.sh7
285 files changed, 18801 insertions, 3983 deletions
diff --git a/flake.lock b/flake.lock
deleted file mode 100644
index 65390eb9..00000000
--- a/flake.lock
+++ /dev/null
@@ -1,861 +0,0 @@
-{
- "nodes": {
- "agenix": {
- "inputs": {
- "darwin": [
- "nix-darwin"
- ],
- "home-manager": [
- "home-manager"
- ],
- "nixpkgs": [
- "nixpkgs"
- ],
- "systems": [
- "systems"
- ]
- },
- "locked": {
- "lastModified": 1762618334,
- "narHash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ=",
- "owner": "ryantm",
- "repo": "agenix",
- "rev": "fcdea223397448d35d9b31f798479227e80183f6",
- "type": "github"
- },
- "original": {
- "owner": "ryantm",
- "repo": "agenix",
- "type": "github"
- }
- },
- "beautysh": {
- "inputs": {
- "flake-parts": [
- "flake-parts"
- ],
- "git-hooks-nix": [
- "git-hooks"
- ],
- "nix-github-actions": [
- "nix-github-actions"
- ],
- "nixpkgs": [
- "nixpkgs"
- ],
- "pyproject-build-systems": "pyproject-build-systems",
- "pyproject-nix": "pyproject-nix",
- "treefmt-nix": [
- "treefmt-nix"
- ],
- "uv2nix": "uv2nix"
- },
- "locked": {
- "lastModified": 1764601770,
- "narHash": "sha256-jM4ChpHM/YTr2YIL5uZ1j4K8SoSrQUSJed2g9Z9GbVY=",
- "owner": "lovesegfault",
- "repo": "beautysh",
- "rev": "18907582aeb81b3aa0457183f42e95e16ab68c92",
- "type": "github"
- },
- "original": {
- "owner": "lovesegfault",
- "repo": "beautysh",
- "type": "github"
- }
- },
- "crane": {
- "locked": {
- "lastModified": 1768700043,
- "narHash": "sha256-rfs2aP+wdueJZ6uABaj0e0PavQyzkRJuJX30HNcBPTg=",
- "owner": "ipetkov",
- "repo": "crane",
- "rev": "935de8bd6838d940988bb065be2a2034259327b9",
- "type": "github"
- },
- "original": {
- "owner": "ipetkov",
- "repo": "crane",
- "type": "github"
- }
- },
- "devshell": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1764011051,
- "narHash": "sha256-M7SZyPZiqZUR/EiiBJnmyUbOi5oE/03tCeFrTiUZchI=",
- "owner": "numtide",
- "repo": "devshell",
- "rev": "17ed8d9744ebe70424659b0ef74ad6d41fc87071",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "devshell",
- "type": "github"
- }
- },
- "disko": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1768727946,
- "narHash": "sha256-le2GY+ZR6uRHMuOAc60sBR3gBD2BEk1qOZ3S5C/XFpU=",
- "owner": "nix-community",
- "repo": "disko",
- "rev": "558e84658d0eafc812497542ad6ca0d9654b3b0f",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "disko",
- "type": "github"
- }
- },
- "flake-compat": {
- "flake": false,
- "locked": {
- "lastModified": 1767039857,
- "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
- "owner": "edolstra",
- "repo": "flake-compat",
- "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
- "type": "github"
- },
- "original": {
- "owner": "edolstra",
- "repo": "flake-compat",
- "type": "github"
- }
- },
- "flake-parts": {
- "inputs": {
- "nixpkgs-lib": [
- "nixpkgs-lib"
- ]
- },
- "locked": {
- "lastModified": 1768135262,
- "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "flake-parts",
- "type": "github"
- }
- },
- "flake-utils": {
- "inputs": {
- "systems": [
- "systems"
- ]
- },
- "locked": {
- "lastModified": 1731533236,
- "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
- "git-hooks": {
- "inputs": {
- "flake-compat": [
- "flake-compat"
- ],
- "gitignore": [
- "gitignore"
- ],
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1767281941,
- "narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=",
- "owner": "cachix",
- "repo": "git-hooks.nix",
- "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "git-hooks.nix",
- "type": "github"
- }
- },
- "gitignore": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1762808025,
- "narHash": "sha256-XmjITeZNMTQXGhhww6ed/Wacy2KzD6svioyCX7pkUu4=",
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
- "type": "github"
- },
- "original": {
- "owner": "hercules-ci",
- "repo": "gitignore.nix",
- "type": "github"
- }
- },
- "haumea": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1708375098,
- "narHash": "sha256-DaFJp3wDHgOqx98U0SF57bXaH2Orp106c+jSdPCVu1E=",
- "owner": "nix-community",
- "repo": "haumea",
- "rev": "ec6350fd9353e7f27ce0e85d31f82e3ed73e4d70",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "haumea",
- "type": "github"
- }
- },
- "home-manager": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1768707181,
- "narHash": "sha256-GdwFfnwdUgABFpc4sAmX7GYx8eQs6cEjOPo6nBJ0YaI=",
- "owner": "nix-community",
- "repo": "home-manager",
- "rev": "83bcb17377f0242376a327e742e9404e9a528647",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "ref": "master",
- "repo": "home-manager",
- "type": "github"
- }
- },
- "impermanence": {
- "inputs": {
- "home-manager": [
- "home-manager"
- ],
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1768741755,
- "narHash": "sha256-plWg64Pku30ZDY0MV2wMlHdZbrqLeQLdCwF3LKJ2aig=",
- "owner": "nix-community",
- "repo": "impermanence",
- "rev": "5eed14a23246e1a6e008374e4df758c10187a972",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "impermanence",
- "type": "github"
- }
- },
- "ixx": {
- "inputs": {
- "flake-utils": [
- "nuschtosSearch",
- "flake-utils"
- ],
- "nixpkgs": [
- "nuschtosSearch",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1754860581,
- "narHash": "sha256-EM0IE63OHxXCOpDHXaTyHIOk2cNvMCGPqLt/IdtVxgk=",
- "owner": "NuschtOS",
- "repo": "ixx",
- "rev": "babfe85a876162c4acc9ab6fb4483df88fa1f281",
- "type": "github"
- },
- "original": {
- "owner": "NuschtOS",
- "ref": "v0.1.1",
- "repo": "ixx",
- "type": "github"
- }
- },
- "lanzaboote": {
- "inputs": {
- "crane": [
- "crane"
- ],
- "nixpkgs": [
- "nixpkgs-stable"
- ],
- "pre-commit": [
- "pre-commit-hooks"
- ],
- "rust-overlay": [
- "rust-overlay"
- ]
- },
- "locked": {
- "lastModified": 1765382359,
- "narHash": "sha256-RJmgVDzjRI18BWVogG6wpsl1UCuV6ui8qr4DJ1LfWZ8=",
- "owner": "nix-community",
- "repo": "lanzaboote",
- "rev": "e8c096ade12ec9130ff931b0f0e25d2f1bc63607",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "ref": "v1.0.0",
- "repo": "lanzaboote",
- "type": "github"
- }
- },
- "library": {
- "locked": {
- "lastModified": 1738443114,
- "narHash": "sha256-IV7n/l3rFoz5UuavrDv0a7IIOPne0jDQVmJAR8bve8U=",
- "ref": "prime",
- "rev": "65bf71bb6ef05ce684924a1dc248bb2e8e2869fb",
- "revCount": 17,
- "type": "git",
- "url": "https://git.foss-syndicate.org/vhack.eu/nix-library"
- },
- "original": {
- "ref": "prime",
- "type": "git",
- "url": "https://git.foss-syndicate.org/vhack.eu/nix-library"
- }
- },
- "nix-darwin": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1768561867,
- "narHash": "sha256-prGOZ+w3pZfGTRxworKcJliCNsewF0L4HUPjgU/6eaw=",
- "owner": "lnl7",
- "repo": "nix-darwin",
- "rev": "8b720b9662d4dd19048664b7e4216ce530591adc",
- "type": "github"
- },
- "original": {
- "owner": "lnl7",
- "repo": "nix-darwin",
- "type": "github"
- }
- },
- "nix-github-actions": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1737420293,
- "narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=",
- "owner": "nix-community",
- "repo": "nix-github-actions",
- "rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "nix-github-actions",
- "type": "github"
- }
- },
- "nix-index-database": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1765267181,
- "narHash": "sha256-d3NBA9zEtBu2JFMnTBqWj7Tmi7R5OikoU2ycrdhQEws=",
- "owner": "nix-community",
- "repo": "nix-index-database",
- "rev": "82befcf7dc77c909b0f2a09f5da910ec95c5b78f",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "nix-index-database",
- "type": "github"
- }
- },
- "nixos-generators": {
- "inputs": {
- "nixlib": [
- "nixpkgs-lib"
- ],
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1764234087,
- "narHash": "sha256-NHF7QWa0ZPT8hsJrvijREW3+nifmF2rTXgS2v0tpcEA=",
- "owner": "nix-community",
- "repo": "nixos-generators",
- "rev": "032a1878682fafe829edfcf5fdfad635a2efe748",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "nixos-generators",
- "type": "github"
- }
- },
- "nixpkgs": {
- "locked": {
- "lastModified": 1768701115,
- "narHash": "sha256-kkRg+hOTrRye6nDf6WBG8Ue7/ETUhfXdFSIVEXV2XXo=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "a48c9dbce9a434f3647d4d9eab783eca11a242c4",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixos-unstable-small",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixpkgs-lib": {
- "locked": {
- "lastModified": 1768699094,
- "narHash": "sha256-uapeGJTPPbmqjdo5zlrWFEIys1XIOQ66sKP3A5UNEOk=",
- "owner": "nix-community",
- "repo": "nixpkgs.lib",
- "rev": "78975aaec5a67ea502e15836919b89d7df96ac27",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "nixpkgs.lib",
- "type": "github"
- }
- },
- "nixpkgs-stable": {
- "locked": {
- "lastModified": 1768621446,
- "narHash": "sha256-6YwHV1cjv6arXdF/PQc365h1j+Qje3Pydk501Rm4Q+4=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "72ac591e737060deab2b86d6952babd1f896d7c5",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixos-25.11",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "nixvim": {
- "inputs": {
- "flake-parts": [
- "flake-parts"
- ],
- "nixpkgs": [
- "nixpkgs"
- ],
- "systems": [
- "systems"
- ]
- },
- "locked": {
- "lastModified": 1768486009,
- "narHash": "sha256-I7ymDe6UQooHy9I9wrafKCCDnRbox/EMWAgJgpm7fGs=",
- "owner": "nix-community",
- "repo": "nixvim",
- "rev": "03a638205b5cb04ba9c2ed6c604e137b15f07fa1",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "nixvim",
- "type": "github"
- }
- },
- "nuschtosSearch": {
- "inputs": {
- "flake-utils": [
- "flake-utils"
- ],
- "ixx": "ixx",
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1768249818,
- "narHash": "sha256-ANfn5OqIxq3HONPIXZ6zuI5sLzX1sS+2qcf/Pa0kQEc=",
- "owner": "NuschtOS",
- "repo": "search",
- "rev": "b6f77b88e9009bfde28e2130e218e5123dc66796",
- "type": "github"
- },
- "original": {
- "owner": "NuschtOS",
- "repo": "search",
- "type": "github"
- }
- },
- "poetry2nix": {
- "inputs": {
- "flake-utils": [
- "flake-utils"
- ],
- "nix-github-actions": [
- "nix-github-actions"
- ],
- "nixpkgs": [
- "nixpkgs"
- ],
- "systems": [
- "systems"
- ],
- "treefmt-nix": [
- "treefmt-nix"
- ]
- },
- "locked": {
- "lastModified": 1743690424,
- "narHash": "sha256-cX98bUuKuihOaRp8dNV1Mq7u6/CQZWTPth2IJPATBXc=",
- "owner": "nix-community",
- "repo": "poetry2nix",
- "rev": "ce2369db77f45688172384bbeb962bc6c2ea6f94",
- "type": "github"
- },
- "original": {
- "owner": "nix-community",
- "repo": "poetry2nix",
- "type": "github"
- }
- },
- "pre-commit-hooks": {
- "inputs": {
- "flake-compat": [
- "flake-compat"
- ],
- "gitignore": [
- "gitignore"
- ],
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1767281941,
- "narHash": "sha256-6MkqajPICgugsuZ92OMoQcgSHnD6sJHwk8AxvMcIgTE=",
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "rev": "f0927703b7b1c8d97511c4116eb9b4ec6645a0fa",
- "type": "github"
- },
- "original": {
- "owner": "cachix",
- "repo": "pre-commit-hooks.nix",
- "type": "github"
- }
- },
- "pyproject-build-systems": {
- "inputs": {
- "nixpkgs": [
- "beautysh",
- "nixpkgs"
- ],
- "pyproject-nix": [
- "beautysh",
- "pyproject-nix"
- ],
- "uv2nix": [
- "beautysh",
- "uv2nix"
- ]
- },
- "locked": {
- "lastModified": 1763662255,
- "narHash": "sha256-4bocaOyLa3AfiS8KrWjZQYu+IAta05u3gYZzZ6zXbT0=",
- "owner": "pyproject-nix",
- "repo": "build-system-pkgs",
- "rev": "042904167604c681a090c07eb6967b4dd4dae88c",
- "type": "github"
- },
- "original": {
- "owner": "pyproject-nix",
- "repo": "build-system-pkgs",
- "type": "github"
- }
- },
- "pyproject-nix": {
- "inputs": {
- "nixpkgs": [
- "beautysh",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1764134915,
- "narHash": "sha256-xaKvtPx6YAnA3HQVp5LwyYG1MaN4LLehpQI8xEdBvBY=",
- "owner": "pyproject-nix",
- "repo": "pyproject.nix",
- "rev": "2c8df1383b32e5443c921f61224b198a2282a657",
- "type": "github"
- },
- "original": {
- "owner": "pyproject-nix",
- "repo": "pyproject.nix",
- "type": "github"
- }
- },
- "qmk_firmware": {
- "inputs": {
- "flake-utils": [
- "flake-utils"
- ],
- "nixpkgs": [
- "nixpkgs"
- ],
- "systems": [
- "systems"
- ],
- "treefmt-nix": [
- "treefmt-nix"
- ]
- },
- "locked": {
- "lastModified": 1753517111,
- "narHash": "sha256-dv5P3ahDICDacdzEmcyxrtKgbRWhVFiKQaLEz+WniGM=",
- "ref": "prime",
- "rev": "4dff2e6ba5c9c80de3e3d2213ad28802814c3bba",
- "revCount": 39,
- "type": "git",
- "url": "https://git.foss-syndicate.org/bpeetz/qmk_layout.git"
- },
- "original": {
- "ref": "prime",
- "type": "git",
- "url": "https://git.foss-syndicate.org/bpeetz/qmk_layout.git"
- }
- },
- "root": {
- "inputs": {
- "agenix": "agenix",
- "beautysh": "beautysh",
- "crane": "crane",
- "devshell": "devshell",
- "disko": "disko",
- "flake-compat": "flake-compat",
- "flake-parts": "flake-parts",
- "flake-utils": "flake-utils",
- "git-hooks": "git-hooks",
- "gitignore": "gitignore",
- "haumea": "haumea",
- "home-manager": "home-manager",
- "impermanence": "impermanence",
- "lanzaboote": "lanzaboote",
- "library": "library",
- "nix-darwin": "nix-darwin",
- "nix-github-actions": "nix-github-actions",
- "nix-index-database": "nix-index-database",
- "nixos-generators": "nixos-generators",
- "nixpkgs": "nixpkgs",
- "nixpkgs-lib": "nixpkgs-lib",
- "nixpkgs-stable": "nixpkgs-stable",
- "nixvim": "nixvim",
- "nuschtosSearch": "nuschtosSearch",
- "poetry2nix": "poetry2nix",
- "pre-commit-hooks": "pre-commit-hooks",
- "qmk_firmware": "qmk_firmware",
- "rust-overlay": "rust-overlay",
- "serverphone": "serverphone",
- "systems": "systems",
- "templates": "templates",
- "treefmt-nix": "treefmt-nix"
- }
- },
- "rust-overlay": {
- "inputs": {
- "nixpkgs": [
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1768704795,
- "narHash": "sha256-Y33TAp2BHEcuspYvcmBXXD0qdvjftv73PwyKTDOjoSY=",
- "owner": "oxalica",
- "repo": "rust-overlay",
- "rev": "4b7472a78857ac789fb26616040f55cfcbd36c6e",
- "type": "github"
- },
- "original": {
- "owner": "oxalica",
- "repo": "rust-overlay",
- "type": "github"
- }
- },
- "serverphone": {
- "inputs": {
- "crane": [
- "crane"
- ],
- "flake-compat": [
- "flake-compat"
- ],
- "flake-utils": [
- "flake-utils"
- ],
- "nixpkgs": [
- "nixpkgs"
- ],
- "rust-overlay": [
- "rust-overlay"
- ],
- "systems": [
- "systems"
- ]
- },
- "locked": {
- "lastModified": 1697538431,
- "narHash": "sha256-M2nQ9tuvt++ukXCdr4IhGqrQqpPpmjjlOZeAKjHQung=",
- "ref": "refs/heads/prime",
- "rev": "2ac6ef2b49f4a7b8b0490a068bc2dba6767f0b7d",
- "revCount": 71,
- "type": "git",
- "url": "https://codeberg.org/vhack.eu/serverphone.git"
- },
- "original": {
- "type": "git",
- "url": "https://codeberg.org/vhack.eu/serverphone.git"
- }
- },
- "systems": {
- "locked": {
- "lastModified": 1680978846,
- "narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=",
- "owner": "nix-systems",
- "repo": "x86_64-linux",
- "rev": "2ecfcac5e15790ba6ce360ceccddb15ad16d08a8",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "x86_64-linux",
- "type": "github"
- }
- },
- "templates": {
- "inputs": {
- "flake-utils": [
- "flake-utils"
- ],
- "nixpkgs": [
- "nixpkgs"
- ],
- "systems": [
- "systems"
- ],
- "treefmt-nix": [
- "treefmt-nix"
- ]
- },
- "locked": {
- "lastModified": 1733053857,
- "narHash": "sha256-rI1qMFzbXVjfEvmf2OS4upnibXpL21its6cCXqhz86o=",
- "ref": "refs/heads/prime",
- "rev": "0294fb03df7c265f8fae24a9e775d69a953bbf03",
- "revCount": 204,
- "type": "git",
- "url": "https://codeberg.org/bpeetz/flake-templates.git"
- },
- "original": {
- "type": "git",
- "url": "https://codeberg.org/bpeetz/flake-templates.git"
- }
- },
- "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"
- }
- },
- "uv2nix": {
- "inputs": {
- "nixpkgs": [
- "beautysh",
- "nixpkgs"
- ],
- "pyproject-nix": [
- "beautysh",
- "pyproject-nix"
- ]
- },
- "locked": {
- "lastModified": 1764546642,
- "narHash": "sha256-pCzgOjGEZyH7xKmpckdJzWyO0kvTIlaTK+ed/wguv5Y=",
- "owner": "pyproject-nix",
- "repo": "uv2nix",
- "rev": "0c56de7543459a23d0ebb7977fd555ced5d842ae",
- "type": "github"
- },
- "original": {
- "owner": "pyproject-nix",
- "repo": "uv2nix",
- "type": "github"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/flake.lock.license b/flake.lock.license
deleted file mode 100644
index eae6a84c..00000000
--- a/flake.lock.license
+++ /dev/null
@@ -1,9 +0,0 @@
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/flake.nix b/flake.nix
index 206a440f..a63e03b2 100644
--- a/flake.nix
+++ b/flake.nix
@@ -11,249 +11,32 @@
{
description = "A NixOS System Configuration";
- inputs = {
- # base
- nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.11";
- nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable-small";
+ inputs = {};
- # open nixpkgs prs
+ outputs = {self, ...}: let
+ sources = import ./unflake.nix;
- library = {
- url = "git+https://git.foss-syndicate.org/vhack.eu/nix-library?ref=prime";
- };
-
- # inputs for following
- nix-darwin = {
- url = "github:lnl7/nix-darwin";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
- systems = {
- url = "github:nix-systems/x86_64-linux"; # only evaluate for this system
- };
- nuschtosSearch = {
- url = "github:NuschtOS/search";
- inputs = {
- flake-utils.follows = "flake-utils";
- nixpkgs.follows = "nixpkgs";
- };
- };
- nix-github-actions = {
- url = "github:nix-community/nix-github-actions";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
- git-hooks = {
- url = "github:cachix/git-hooks.nix";
- inputs = {
- flake-compat.follows = "flake-compat";
- gitignore.follows = "gitignore";
- nixpkgs.follows = "nixpkgs";
- };
- };
- poetry2nix = {
- url = "github:nix-community/poetry2nix";
- inputs = {
- systems.follows = "systems";
- nixpkgs.follows = "nixpkgs";
- flake-utils.follows = "flake-utils";
- treefmt-nix.follows = "treefmt-nix";
- nix-github-actions.follows = "nix-github-actions";
- };
- };
- flake-parts = {
- url = "github:hercules-ci/flake-parts";
- inputs = {
- nixpkgs-lib.follows = "nixpkgs-lib";
- };
- };
- nixpkgs-lib = {
- url = "github:nix-community/nixpkgs.lib";
- };
- haumea = {
- url = "github:nix-community/haumea";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
- gitignore = {
- url = "github:hercules-ci/gitignore.nix";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
- beautysh = {
- url = "github:lovesegfault/beautysh";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- flake-parts.follows = "flake-parts";
- git-hooks-nix.follows = "git-hooks";
- treefmt-nix.follows = "treefmt-nix";
- nix-github-actions.follows = "nix-github-actions";
- };
- };
- devshell = {
- url = "github:numtide/devshell";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
- pre-commit-hooks = {
- url = "github:cachix/pre-commit-hooks.nix";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- flake-compat.follows = "flake-compat";
- gitignore.follows = "gitignore";
- };
- };
- flake-compat = {
- url = "github:edolstra/flake-compat";
- flake = false;
- };
- crane = {
- url = "github:ipetkov/crane";
- inputs = {};
- };
- flake-utils = {
- url = "github:numtide/flake-utils";
- inputs = {
- systems.follows = "systems";
- };
- };
- rust-overlay = {
- url = "github:oxalica/rust-overlay";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
-
- # nixos modules
- home-manager = {
- url = "github:nix-community/home-manager/master";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
- disko = {
- url = "github:nix-community/disko";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
- nixvim = {
- url = "github:nix-community/nixvim";
- inputs = {
- flake-parts.follows = "flake-parts";
- nixpkgs.follows = "nixpkgs";
- systems.follows = "systems";
- };
- };
- agenix = {
- url = "github:ryantm/agenix";
- inputs = {
- darwin.follows = "nix-darwin";
- nixpkgs.follows = "nixpkgs";
- home-manager.follows = "home-manager";
- systems.follows = "systems";
- };
- };
- impermanence = {
- url = "github:nix-community/impermanence";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- home-manager.follows = "home-manager";
- };
- };
- nixos-generators = {
- url = "github:nix-community/nixos-generators";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- nixlib.follows = "nixpkgs-lib";
- };
- };
- serverphone = {
- url = "git+https://codeberg.org/vhack.eu/serverphone.git";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- systems.follows = "systems";
- flake-compat.follows = "flake-compat";
- flake-utils.follows = "flake-utils";
- rust-overlay.follows = "rust-overlay";
- crane.follows = "crane";
- };
- };
- lanzaboote = {
- url = "github:nix-community/lanzaboote/v1.0.0";
- inputs = {
- nixpkgs.follows = "nixpkgs-stable";
- crane.follows = "crane";
- rust-overlay.follows = "rust-overlay";
- pre-commit.follows = "pre-commit-hooks";
- };
- };
- nix-index-database = {
- url = "github:nix-community/nix-index-database";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
-
- # my configs
- templates = {
- url = "git+https://codeberg.org/bpeetz/flake-templates.git";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- flake-utils.follows = "flake-utils";
- systems.follows = "systems";
- treefmt-nix.follows = "treefmt-nix";
- };
- };
-
- # my bins
- qmk_firmware = {
- url = "git+https://git.foss-syndicate.org/bpeetz/qmk_layout.git?ref=prime";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- treefmt-nix.follows = "treefmt-nix";
- systems.follows = "systems";
- flake-utils.follows = "flake-utils";
- };
- };
-
- # external resources
- treefmt-nix = {
- url = "github:numtide/treefmt-nix";
- inputs = {
- nixpkgs.follows = "nixpkgs";
- };
- };
- };
-
- outputs = {
# core
- self,
- nixpkgs,
- nixpkgs-stable,
- library,
+ nixpkgs = sources.nixpkgs;
+ nixpkgs-stable = sources.nixpkgs-stable;
+ library = sources.library;
# modules
- home-manager,
- nixos-generators,
- impermanence,
- agenix,
- serverphone,
- disko,
- lanzaboote,
- nixvim,
- nix-index-database,
+ home-manager = sources.home-manager;
+ nixos-generators = sources.nixos-generators;
+ impermanence = sources.impermanence;
+ agenix = sources.agenix;
+ serverphone = sources.serverphone;
+ disko = sources.disko;
+ lanzaboote = sources.lanzaboote;
+ nixvim = sources.nixvim;
+ nix-index-database = sources.nix-index-database;
# external dependencies
- treefmt-nix,
- templates,
+ treefmt-nix = sources.treefmt-nix;
+ templates = sources.templates;
# my binaries
- qmk_firmware,
- ...
- }: let
+ qmk_firmware = sources.qmk_firmware;
+ turtle = sources.turtle;
+
system = "x86_64-linux";
packageSets = rec {
@@ -296,6 +79,7 @@
externalBinaries = {
inherit
qmk_firmware
+ turtle
;
};
@@ -304,6 +88,8 @@
outputs = import ./flake {
inherit
self
+ sources
+ nixpkgs
system
openPRsNixpkgs
packageSets
diff --git a/flake/default.nix b/flake/default.nix
index 83138f32..099a4568 100644
--- a/flake/default.nix
+++ b/flake/default.nix
@@ -9,6 +9,8 @@
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
{
self,
+ sources,
+ nixpkgs,
system,
openPRsNixpkgs,
packageSets,
@@ -29,6 +31,8 @@
nixosConfigurations = import ../hosts {
inherit
self
+ sources
+ nixpkgs
system
openPRsNixpkgs
packageSets
@@ -91,6 +95,7 @@ in {
pkgs.cocogitto
pkgs.git-bug
pkgs.reuse
+ pkgs.html2text
# secrets
pkgs.ragenix
diff --git a/hosts/by-name/apzu/configuration.nix b/hosts/by-name/apzu/configuration.nix
index a5b10e9f..9e5d97f0 100644
--- a/hosts/by-name/apzu/configuration.nix
+++ b/hosts/by-name/apzu/configuration.nix
@@ -24,7 +24,13 @@
"/var/log"
];
+ power.enable = true;
+
programs = {
+ starship.showBattery = true;
+
+ ly.batteryName = "BAT0";
+
river.init = {
mappings = {
layout = "dvorak-modified";
diff --git a/hosts/by-name/apzu/hardware.nix b/hosts/by-name/apzu/hardware.nix
index 8d481fa6..d037473a 100644
--- a/hosts/by-name/apzu/hardware.nix
+++ b/hosts/by-name/apzu/hardware.nix
@@ -13,13 +13,14 @@
];
soispha = {
+ boot.enableIsoEntry = true;
+
disks = {
enable = true;
disk = "/dev/disk/by-id/nvme-INTEL_SSDPEKNU512GZH_PHKA1481032A512A_1";
ssd = true;
swap = {
- uuid = "c94cd20a-dd3c-436f-9841-6fe92e5c8719";
- resumeOffset = "533760";
+ ram_size = "16G";
};
};
@@ -32,6 +33,12 @@
boot = {
kernelModules = ["kvm-intel" "rtw89"];
+ kernelParams = [
+ # Use the newer experimental xe driver
+ # "i915.force_probe=!9a49"
+ # "xe.force_probe=9a49"
+ ];
+
initrd.availableKernelModules = ["xhci_pci" "vmd" "nvme" "usb_storage" "sd_mod" "rtsx_pci_sdmmc"];
};
}
diff --git a/hosts/by-name/tiamat/configuration.nix b/hosts/by-name/tiamat/configuration.nix
index 18393543..baa9215a 100644
--- a/hosts/by-name/tiamat/configuration.nix
+++ b/hosts/by-name/tiamat/configuration.nix
@@ -9,9 +9,7 @@
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
{
lib,
- pkgs,
system,
- libraries,
externalBinaries,
...
}: {
diff --git a/hosts/by-name/tiamat/hardware.nix b/hosts/by-name/tiamat/hardware.nix
index 2b18a662..f2a5cec6 100644
--- a/hosts/by-name/tiamat/hardware.nix
+++ b/hosts/by-name/tiamat/hardware.nix
@@ -10,6 +10,7 @@
{modulesPath, ...}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix") # TODO: is this necessary?
+ ./legacy-disk-module/module.nix
];
soispha = {
@@ -20,7 +21,8 @@
enableFlashing = true;
};
};
-
+ };
+ legacy.soispha = {
disks = {
enable = true;
disk = "/dev/disk/by-id/nvme-CT1000P5SSD8_21032C857568";
diff --git a/hosts/by-name/tiamat/legacy-disk-module/fstrim.nix b/hosts/by-name/tiamat/legacy-disk-module/fstrim.nix
new file mode 100644
index 00000000..0fbb6c83
--- /dev/null
+++ b/hosts/by-name/tiamat/legacy-disk-module/fstrim.nix
@@ -0,0 +1,51 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ pkgs,
+ lib,
+ cfg,
+}: {
+ timers.fstrim = lib.mkIf cfg.ssd {
+ wantedBy = ["timers.target"];
+ wants = ["fstrim.service"];
+ unitConfig = {
+ Description = "Discard unused blocks once a week";
+ Documentation = "man:fstrim";
+ ConditionVirtualization = "!container";
+ ConditionPathExists = "!/etc/initrd-release";
+ };
+ timerConfig = {
+ OnCalendar = "weekly";
+ AccuracySec = "1h";
+ Persistent = "true";
+ RandomizedDelaySec = "6000";
+ };
+ };
+ services.fstrim = lib.mkIf cfg.ssd {
+ wantedBy = lib.mkForce [];
+ unitConfig = {
+ Description = "Discard unused blocks on filesystems from /etc/fstab";
+ Documentation = "man:fstrim(8)";
+ ConditionVirtualization = "!container";
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ ExecStart = "${pkgs.util-linux}/bin/fstrim --listed-in /etc/fstab:/proc/self/mountinfo --verbose --quiet-unsupported";
+ PrivateDevices = "no";
+ PrivateNetwork = "yes";
+ PrivateUsers = "no";
+ ProtectKernelTunables = "yes";
+ ProtectKernelModules = "yes";
+ ProtectControlGroups = "yes";
+ MemoryDenyWriteExecute = "yes";
+ SystemCallFilter = "@default @file-system @basic-io @system-service";
+ };
+ };
+}
diff --git a/hosts/by-name/tiamat/legacy-disk-module/hibernate.nix b/hosts/by-name/tiamat/legacy-disk-module/hibernate.nix
new file mode 100644
index 00000000..d3f8fe8b
--- /dev/null
+++ b/hosts/by-name/tiamat/legacy-disk-module/hibernate.nix
@@ -0,0 +1,55 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{pkgs}: {
+ services = {
+ hibernate-preparation = {
+ wantedBy = ["systemd-hibernate.service"];
+ unitConfig = {
+ Description = "Enable swap file and disable zram before hibernate";
+ Before = "systemd-hibernate.service";
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ User = "root";
+ ExecStart = "${pkgs.bash}/bin/bash -c \"${pkgs.util-linux}/bin/swapon /swap/swapfile && ${pkgs.util-linux}/bin/swapoff /dev/zram0\"";
+ };
+ };
+
+ hibernate-resume = {
+ wantedBy = ["systemd-hibernate.service"];
+ unitConfig = {
+ Description = "Disable swap after resuming from hibernation";
+ After = "hibernate.target";
+ };
+ serviceConfig = {
+ Type = "oneshot";
+ User = "root";
+ ExecStart = "${pkgs.util-linux}/bin/swapoff /swap/swapfile";
+ };
+ };
+ # swapoff-start = {
+ # wantedBy = ["multi-user.target"];
+ # unitConfig = {
+ # Description = "Disable hardware swap after booting";
+ # };
+ # serviceConfig = {
+ # Type = "oneshot";
+ # User = "root";
+ # ExecStart = "${pkgs.util-linux}/bin/swapoff /swap/swapfile";
+ # };
+ # };
+ systemd-hibernate.serviceConfig.Environment = "SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK=1";
+ systemd-logind.serviceConfig.Environment = "SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK=1";
+ };
+
+ sleep.settings.Sleep = {
+ HibernateDelaySec = "10m";
+ };
+}
diff --git a/hosts/by-name/tiamat/legacy-disk-module/module.nix b/hosts/by-name/tiamat/legacy-disk-module/module.nix
new file mode 100644
index 00000000..955d2026
--- /dev/null
+++ b/hosts/by-name/tiamat/legacy-disk-module/module.nix
@@ -0,0 +1,159 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ config,
+ lib,
+ pkgs,
+ modules,
+ ...
+}: let
+ # FIXME: The iso redeploy requires a bigger efi partition <2024-05-12>
+ cfg = config.legacy.soispha.disks;
+ defaultMountOptions = [
+ "compress=zstd:3" # This saves disk space, at a performance cost
+ "noatime" # should have some performance upsides, and I don't use it anyways
+ "lazytime" # make time changes in memory
+ ];
+in {
+ options.legacy.soispha.disks = {
+ enable = lib.mkEnableOption "disk setup with disko";
+
+ disk = lib.mkOption {
+ type = lib.types.path;
+ example = lib.literalExpression "/dev/disk/by-uuid/0442cb6d-f13a-4635-b487-fa76189774c5";
+ description = "The disk used for installing the OS.";
+ };
+
+ ssd = lib.mkEnableOption "ssd specific improvements, like trim";
+
+ swap = {
+ uuid = lib.mkOption {
+ type = lib.types.str;
+ example = lib.literalExpression "d1d20ae7-3d8a-44da-86da-677dbbb10c89";
+ description = "The uuid of the swapfile";
+ };
+ resumeOffset = lib.mkOption {
+ type = lib.types.str;
+ example = lib.literalExpression "134324224";
+ description = "The resume offset of the swapfile";
+ };
+ };
+ };
+
+ imports = [
+ modules.disko.nixosModules.default
+ ];
+
+ config = lib.mkIf cfg.enable {
+ systemd = lib.recursiveUpdate (import ./hibernate.nix {inherit pkgs;}) (import ./fstrim.nix {inherit pkgs lib cfg;});
+
+ disko.devices = {
+ disk = {
+ main = {
+ device = cfg.disk;
+ content = {
+ type = "gpt";
+ partitions = {
+ root = {
+ size = "100%";
+ name = "root";
+ content = {
+ type = "luks";
+ name = "nixos";
+ extraOpenArgs = ["--allow-discards"];
+ content = {
+ type = "btrfs";
+ extraArgs = ["-f" "--label nixos"]; # Override existing partitions
+ subvolumes = {
+ "nix" = {
+ mountpoint = "/nix";
+ mountOptions = defaultMountOptions;
+ };
+ "persistent-storage" = {
+ mountpoint = "/srv";
+ mountOptions = defaultMountOptions;
+ };
+ "persistent-storage@snapshots" = {
+ mountpoint = "/srv/.snapshots";
+ mountOptions = defaultMountOptions;
+ };
+ "swap" = {
+ mountpoint = "/swap";
+ mountOptions = [
+ "noatime" # should have some performance upsides, and I don't use it anyways
+ "lazytime" # make time changes in memory
+ ];
+ };
+ };
+ };
+ };
+ };
+ boot = {
+ type = "EF00";
+ size = "512M";
+ name = "boot";
+ content = {
+ type = "filesystem";
+ format = "vfat";
+ mountpoint = "/boot";
+ };
+ };
+ };
+ };
+ };
+ };
+ nodev = {
+ "/" = {
+ fsType = "tmpfs";
+ mountOptions = ["defaults" "size=25%" "mode=0755"];
+ };
+ "/tmp" = {
+ fsType = "tmpfs";
+ mountOptions = ["defaults" "size=50%" "mode=0755"];
+ };
+ "/nix/var/nix/b" = {
+ fsType = "tmpfs";
+ mountOptions = [
+ "defaults"
+ "noswap" # Otherwise, we might run into io-based slowdowns
+ "size=80%"
+ "mode=0755"
+ ];
+ };
+ };
+ };
+ fileSystems = {
+ "/srv" = {
+ neededForBoot = true;
+ };
+ "/swap" = {
+ neededForBoot = true;
+ };
+ };
+ swapDevices = [
+ #{
+ # device = "/swap/swapfile";
+ # priority = 1; # lower than zramSwap, just in case
+ # # size = 2048; # TODO: can nixos create a btrfs swapfile correctly?
+ #}
+ ];
+ zramSwap = {
+ enable = true;
+ priority = 10; # needs to be higher than hardware-swap
+ };
+ boot = {
+ kernelParams = [
+ "resume_offset=${cfg.swap.resumeOffset}"
+ "zswap.enabled=0" # zswap and zram are not really compatible
+ ];
+ resumeDevice = "/dev/disk/by-uuid/${cfg.swap.uuid}";
+ };
+ };
+}
diff --git a/hosts/default.nix b/hosts/default.nix
index 64768074..fc18d556 100644
--- a/hosts/default.nix
+++ b/hosts/default.nix
@@ -9,6 +9,8 @@
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
{
self,
+ sources,
+ nixpkgs,
system,
openPRsNixpkgs,
packageSets,
@@ -24,18 +26,19 @@
};
generateHost = _: path:
- self.inputs.nixpkgs.lib.nixosSystem {
+ nixpkgs.lib.nixosSystem {
specialArgs = {
inherit
+ self
libraries
modules
- self
;
};
modules = [
{
_module.args = {
inherit
+ sources
# extra package sources
openPRsNixpkgs
packageSets
diff --git a/inputs.nix b/inputs.nix
new file mode 100644
index 00000000..abc7822e
--- /dev/null
+++ b/inputs.nix
@@ -0,0 +1,136 @@
+let
+ nixosVersion = "26.05";
+
+ lanzabooteVersion = "v1.1.0";
+
+ w_nixpkgs = ref: {
+ type = "github";
+ owner = "nixos";
+ repo = "nixpkgs";
+ inherit ref;
+ };
+ w_tarball = url: {
+ type = "tarball";
+ inherit url;
+ };
+
+ nixos_stable = "https://channels.nixos.org/nixos-${nixosVersion}/nixexprs.tar.zst";
+ nixos_unstable = "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst";
+in {
+ _unflake.dedupRules = [
+ {
+ when = w_nixpkgs "nixpkgs-unstable";
+ set = w_tarball nixos_unstable;
+ }
+ {
+ when = w_nixpkgs "nixos-unstable-small";
+ set = w_tarball nixos_unstable;
+ }
+ {
+ when = w_nixpkgs "nixos-25.05";
+ set = w_tarball nixos_stable;
+ }
+ {
+ when = w_nixpkgs "nixos-25.11";
+ set = w_tarball nixos_stable;
+ }
+ {
+ when = w_nixpkgs "nixos-26.05";
+ set = w_tarball nixos_stable;
+ }
+
+ {
+ when = {
+ type = "github";
+ owner = "nix-community";
+ repo = "home-manager";
+ };
+
+ set = {ref = "master";};
+ }
+
+ {
+ when = {
+ type = "github";
+ owner = "nix-systems";
+ repo = "default";
+ };
+
+ set = {ref = "main";};
+ }
+ {
+ when = {
+ type = "github";
+ owner = "nix-systems";
+ repo = "x86_64-linux";
+ };
+
+ set = {
+ repo = "default";
+ ref = "main";
+ };
+ }
+
+ {
+ when = {
+ type = "github";
+ owner = "numtide";
+ repo = "treefmt-nix";
+ };
+
+ set = {ref = "main";};
+ }
+ ];
+
+ # Actual inputs
+ "agenix" = {
+ url = "github:ryantm/agenix/main";
+ };
+ "disko" = {
+ url = "github:nix-community/disko/master";
+ };
+ "devshells" = {
+ url = "git+https://git.foss-syndicate.org/bpeetz/devshells?ref=prime";
+ };
+ "flake-compat" = {
+ url = "git+https://git.lix.systems/lix-project/flake-compat?ref=main";
+ };
+ "home-manager" = {
+ url = "github:nix-community/home-manager/master";
+ };
+ "impermanence" = {
+ url = "github:nix-community/impermanence/master";
+ };
+ "lanzaboote" = {
+ url = "github:nix-community/lanzaboote/${lanzabooteVersion}";
+ };
+ "library" = {
+ url = "git+https://git.foss-syndicate.org/vhack.eu/nix-library?ref=prime";
+ };
+ "turtle" = {
+ url = "git+https://git.foss-syndicate.org/bpeetz/forks/atuin?ref=main";
+ };
+ "nix-index-database" = {
+ url = "github:nix-community/nix-index-database/main";
+ };
+ "nixos-generators" = {
+ url = "github:nix-community/nixos-generators/master";
+ };
+ "nixpkgs-stable" = {url = "tarball+${nixos_stable}";};
+ "nixpkgs" = {url = "tarball+${nixos_unstable}";};
+ "nixvim" = {
+ url = "github:nix-community/nixvim/main";
+ };
+ "qmk_firmware" = {
+ url = "git+https://git.foss-syndicate.org/bpeetz/qmk_layout?ref=prime";
+ };
+ # "serverphone" = {
+ # url = "git+https://codeberg.org/vhack.eu/serverphone?ref=prime";
+ # };
+ "templates" = {
+ url = "git+https://codeberg.org/bpeetz/flake-templates?ref=prime";
+ };
+ "treefmt-nix" = {
+ url = "github:numtide/treefmt-nix/main";
+ };
+}
diff --git a/modules/by-name/at/atuin/module.nix b/modules/by-name/at/atuin/module.nix
index 700f50fb..d68e830c 100644
--- a/modules/by-name/at/atuin/module.nix
+++ b/modules/by-name/at/atuin/module.nix
@@ -10,7 +10,8 @@
{
config,
lib,
- pkgs,
+ externalBinaries,
+ system,
...
}: let
cfg = config.soispha.programs.atuin;
@@ -22,35 +23,49 @@ in {
};
config = lib.mkIf cfg.enable {
- age.secrets.atuin_encryption_key = lib.mkIf cfg.enableAge {
- file = ./secrets/encryption_key.age;
- mode = "700";
- owner = "soispha";
- group = "users";
+ age.secrets = {
+ atuin_encryption_key = lib.mkIf cfg.enableAge {
+ file = ./secrets/encryption_key.age;
+ mode = "700";
+ owner = "soispha";
+ group = "users";
+ };
+ atuin_user_id = lib.mkIf cfg.enableAge {
+ file = ./secrets/user_id.age;
+ mode = "700";
+ owner = "soispha";
+ group = "users";
+ };
};
soispha.programs.zsh.integrations.atuin = ./atuin.zsh;
+ soispha.services.unison.pathsToIgnore = [
+ # Already synchronized by atuin sync server
+ "~/.local/share/atuin"
+ "~/.local/share/turtle"
+ ];
home-manager.users.soispha = {
- programs.atuin = {
+ imports = [
+ externalBinaries.turtle.nixosModules.home-manager
+ ];
+
+ programs.turtle = {
enable = true;
- package = pkgs.atuin-dvorak;
- # We can do this on our own.
- enableZshIntegration = false;
settings = {
- key_path = lib.mkMerge [
- (lib.mkIf cfg.enableAge "${config.age.secrets.atuin_encryption_key.path}")
- (lib.mkIf (!cfg.enableAge)
- "${config.home-manager.users.soispha.xdg.dataHome}/atuin/atuin_secret_key.key")
- ];
-
sync = lib.mkIf cfg.enableAge {
- # The v2 sync API
- records = true;
+ encryption_key_path = "${config.age.secrets.atuin_encryption_key.path}";
+ user_id_path = "${config.age.secrets.atuin_user_id.path}";
+
+ auto = true;
+ address = "https://atuin-sync.vhack.eu";
+ };
+
+ logs = {
+ enabled = true;
+ dir = "${config.home-manager.users.soispha.xdg.cacheHome}/turtle/logs";
};
- auto_sync = lib.mkIf cfg.enableAge true;
- sync_address = lib.mkIf cfg.enableAge "https://atuin-sync.vhack.eu";
# Use the rather reasonable syntax of `skim` to search.
search_mode = "skim";
@@ -72,17 +87,17 @@ in {
vim_normal = "blink-block";
};
- # Who wants software, that automatically calls home?!
- update_check = false;
-
stats = {
# This overrides the default value.
ignored_commands = [];
};
- # I currently don't want a sync deamon or a dotfiles manager running.
- deamon.enable = false;
- dotfiles.enable = false;
+ keymap.vim-normal = {
+ "h" = "cursor-left";
+ "t" = "select-next";
+ "n" = "select-previous";
+ "s" = "cursor-right";
+ };
};
};
};
diff --git a/modules/by-name/at/atuin/secrets/encryption_key.age b/modules/by-name/at/atuin/secrets/encryption_key.age
index 4c2bb7b4..a69db402 100644
--- a/modules/by-name/at/atuin/secrets/encryption_key.age
+++ b/modules/by-name/at/atuin/secrets/encryption_key.age
@@ -1,15 +1,16 @@
-----BEGIN AGE ENCRYPTED FILE-----
-YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqT1Y2RU9hQmkxWEoveitY
-dDEzQU85QkVXOCsxS3JFb0xKMngrT08xMGswCkJzcm9GanVUbytKcmI1U1lzWVM0
-eGV5OUcwU2M4UGlzZ3ZpQUJtYUJxcTQKLT4gc3NoLWVkMjU1MTkgelpFb25nIElC
-OExoMzVpV0U3czZ2aUp3a0ZDcUlTQnFhallxOTdhTE1YdnFrWVdPRzQKQmN6L1Z4
-S0ZWeWNSREFhMExMa0FOdWhnUTI5bXptVnU5SDFQZkk1R00vdwotPiBzc2gtZWQy
-NTUxOSA3SGZGVXcgbm1LZ1VwRDdjRFh0SCtTT1I2ZDFsSmhsZzVBcHMxSXhWQlNM
-cFVpU2kwRQpEUStYQVJiWSt1THA5ZEJlL2NnQ2NwZTlWWEVtYkpjb09RckxkaWdP
-djFvCi0+ICktZ3JlYXNlIGsgZnRHZHhEIDxVLQo4SkZtRm8vTEJnYVRnZXAwK3Yv
-eUQ0TGdYRVpOTmozSE52MlFqeG1HalVWb1JmZ2k5ZVJFNVpBUGdyMnlVZwotLS0g
-UjI2WVhtU0lITzVMSU1Nc2RKcnJXZVUreWxnZXoxZnBRL0xpUmd3dDhtNArERqzQ
-//hB8CunQlwCCYnISj6FaQphnWz8E3sIyi3FqH2ww9MQ4RzbVqLOOKgg4wVWRC8Y
-RmFWPl1SfJoA8DIPoTEgqQZseB+PT7CYHo4LEwrpkcQ5cndOI2JbO+HzZdk9H+tM
-GbsggKI=
+YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB1NkNRQUdYekxZaEF3WTdC
+ZDBndHBMZUVPM2dObDcvazBjaXlQT3QrUmtVCkc1TUhMd1h1YWxtaXNncGZSODg0
+QWtUTDhybFltaDBVWTE3VVBoeUtubzQKLT4gc3NoLWVkMjU1MTkgelpFb25nIHEy
+VGRMeUZDSUlGRmE0NzlnWSsvY3VHVmRxR1NpNGRmS1ZRWG9TcFdaQmsKcjhKb1VG
+V2tNZkdXTXhSb25ETUplYlZWaHU0Y0grYUtjSVVHVzhmMzV5TQotPiBzc2gtZWQy
+NTUxOSA3SGZGVXcgTTBhcUJISVVRVU1hNk5VZkNTNUZHUzY1TSs4a2ZNYytIdVVG
+TG5mRjhBawpRVG50Wkl2S2NKdUxTeVhiOTJ0RG00NCswQ01jV1lYbVpYNC90N3V1
+WXZRCi0+IDEoIS1ncmVhc2UgSUZzazpsRCBCbi0gWQpxMUJtNk13ZHptVjladTAz
+U2IxRjIyK0wvY3k1T3JiQ1ZFNlMvU0t3TWNyN09SVVlWc2FIQU9lZDZ0azIwT29z
+CjRFREVXdGozNVhKWlE5L3NxTGdDZFVvL2NDYlpRTWtrd1J0cFZwcm9OVW1Vb1pz
+SnptUWdvZ3E4VThHOXZRCi0tLSBUeW0zYnNVZkRYWDJoTTV0V2QveWpIY0p0UmxP
+UXlSNHNPbllBYitjTWpVCgUpBZTJYd2sFY9GvDwJMKt0WVrDZVE2PF5jAww8Ml/w
+rEycAm/dmIiMV5mfKOdU0aHNMl6RM/7PLHSa4G+wIRRrKQkoNcu8cqOziBRWgSOn
+JKxVFCdwlEgAo+J+qtRHkwitjOJ0ce6OOg==
-----END AGE ENCRYPTED FILE-----
diff --git a/modules/by-name/at/atuin/secrets/user_id.age b/modules/by-name/at/atuin/secrets/user_id.age
new file mode 100644
index 00000000..6ac3bcd6
--- /dev/null
+++ b/modules/by-name/at/atuin/secrets/user_id.age
@@ -0,0 +1,14 @@
+-----BEGIN AGE ENCRYPTED FILE-----
+YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0T3hFUm1NZU1RU2IxNWsx
+ZFh2ckVsTnNBZVVqMm9HaGFOTmtKMlh3M2xzClJ1N0RsbFJYYWhnYktpYnFaYWNY
+c2VGRzZoMW1GOENsV3FoTEJ1OWVDQ00KLT4gc3NoLWVkMjU1MTkgelpFb25nIDlY
+MnRoMFB4dTUyS0pIenVjelRxRERXZ2ptVURZWGttZWR5R25NMWYzekUKaVRDKzJj
+cCtRY0F6VVFGUlIyeGhLSThXMVFvNEl2Y0tMdHpSOEI4cjVtTQotPiBzc2gtZWQy
+NTUxOSA3SGZGVXcgbjNpdGZZa1NxbEw3YkV4UmxTb0dtSlFPM1htK1QzWXBaRGdv
+ZUdTYkoxSQpMcHZCMnNPR0QySkovQ0I1THVPK3Z5RlFCbGJ5Zk5teDBKbUFvY1p4
+T3lzCi0+IGtrVXxkQlItZ3JlYXNlIDBRO3cKWnk5M2MyTUExRDBYd25RdkxRQWla
+bnpTU1J0T1NMbVZldGpCbVZOTFZuV29ZRll0UDJwdHo0YWoKLS0tIFI5RG9WSDZu
+am9RYnFONE9wZ3pVaG1tVnBLNTZqK3d4NFhhT1F1bWlORzAKz44Fafb+Ck5aMxJF
+5/pNNcYE3IRUqjKTWjmSO+fBFVPnlfIIc6gKFhO4Nm05QIxqzSW08PbBhVYFH65u
+ivRjFHUg+Gp3
+-----END AGE ENCRYPTED FILE-----
diff --git a/modules/by-name/au/ausweisapp/module.nix b/modules/by-name/au/ausweisapp/module.nix
new file mode 100644
index 00000000..e74f3d91
--- /dev/null
+++ b/modules/by-name/au/ausweisapp/module.nix
@@ -0,0 +1,21 @@
+{
+ config,
+ lib,
+ libraries,
+ ...
+}: let
+ cfg = config.soispha.programs.ausweisapp;
+in {
+ options.soispha.programs.ausweisapp = {
+ enable = libraries.base.options.mkEnable "AusweisApp";
+ };
+
+ config = lib.mkIf cfg.enable {
+ soispha.impermanence.userDirectories = [
+ ".config/AusweisApp"
+ ];
+ soispha.services.unison.pathsToSync = [
+ "~/.config/AusweisApp"
+ ];
+ };
+}
diff --git a/modules/by-name/ba/backup/env.vars b/modules/by-name/ba/backup/env.vars
new file mode 100644
index 00000000..339c5a5b
--- /dev/null
+++ b/modules/by-name/ba/backup/env.vars
@@ -0,0 +1 @@
+RESTIC_COMPRESSION=max
diff --git a/modules/by-name/ba/backup/module.nix b/modules/by-name/ba/backup/module.nix
index 7a788764..e601d840 100644
--- a/modules/by-name/ba/backup/module.nix
+++ b/modules/by-name/ba/backup/module.nix
@@ -63,12 +63,23 @@ in {
soispha.impermanence.directories = lib.mkMerge [
(lib.mkIf cfg.storagebox.enable [
"/var/cache/restic-backups-storagebox"
+
+ # Generate the base systemd.mount from impermanence (we will override this, so it
+ # points to the non-admin one instead)
+ "/var/cache/restic-backups-storagebox-admin"
])
(lib.mkIf cfg.local.enable [
"/var/cache/restic-backups-local"
])
];
+ # TODO: Make this work (`systemd.mounts` is a list, so _this_ doesn't work.) <2026-07-07>
+ # systemd.mounts."/var/cache/restic-backups-storagebox-admin" = {
+ # # TODO: Don't hardcode the path, but use
+ # # `systemd.mounts."/var/cache/restic-backups-storagebox".what` instead <2026-07-07>
+ # what = "/srv/var/cache/restic-backups-storagebox";
+ # };
+
age.secrets = {
resticStorageboxSshKey = lib.mkIf cfg.storagebox.enable {
file = cfg.storagebox.sshKey;
@@ -97,6 +108,8 @@ in {
};
};
+ programs.fuse.enable = cfg.storagebox.enable || cfg.local.enable;
+
systemd.services = {
prepare-backup = {
requires = [];
@@ -154,16 +167,21 @@ in {
];
exclude = [
"${homeDir}/soispha/.cache"
+ "${homeDir}/soispha/.local/share/lutris/install"
+ "${homeDir}/soispha/.local/share/Steam/steamapps/common"
];
extraBackupArgs = [
+ # Don't scan the filesystem to determine an estimate.
+ "--no-scan"
"--verbose=2"
];
+ environmentFile = "${./env.vars}";
in {
local = lib.mkIf cfg.local.enable {
inhibitsSleep = true;
initialize = true;
- inherit paths exclude extraBackupArgs;
+ inherit paths exclude extraBackupArgs environmentFile;
passwordFile = config.age.secrets.resticLocalRepositoryPassword.path;
@@ -177,7 +195,7 @@ in {
inhibitsSleep = true;
initialize = true;
- inherit paths exclude extraBackupArgs;
+ inherit paths exclude extraBackupArgs environmentFile;
passwordFile = config.age.secrets.resticStorageboxRepositoryPassword.path;
extraOptions = [
@@ -186,11 +204,10 @@ in {
# This setting is normally passed to rclone, but we force
# the command on the remote.
- # As such, the value does not matter and must only be parseable by restic.
+ # As such, the value does not matter and must only be parse-able by restic.
repository = "rclone: ";
timerConfig = {
- Requires = "network-online.target";
OnActiveSec = "30m";
OnUnitInactiveSec = "2h";
Persistent = true;
@@ -199,9 +216,13 @@ in {
# This is only for listing, pruning and such stuff.
storagebox-admin = lib.mkIf cfg.storagebox.enable {
- inhibitsSleep = false;
+ inhibitsSleep = true;
+
+ # Don't create the repository if it doesn't exist yet.
initialize = false;
+ inherit paths exclude extraBackupArgs environmentFile;
+
passwordFile = config.age.secrets.resticStorageboxRepositoryPassword.path;
extraOptions = [
"rclone.program='ssh -p 23 ${cfg.storagebox.user}@${cfg.storagebox.user}.your-storagebox.de command_forced_on_remote'"
@@ -209,7 +230,7 @@ in {
# This setting is normally passed to rclone, but we force
# the command on the remote.
- # As such, the value does not matter and must only be parseable by restic.
+ # As such, the value does not matter and must only be parse-able by restic.
repository = "rclone: ";
timerConfig = null;
diff --git a/modules/by-name/bo/boot/iso_entry/archlive_iso.nix b/modules/by-name/bo/boot/iso_entry/archlive_iso.nix
deleted file mode 100644
index d0ae8457..00000000
--- a/modules/by-name/bo/boot/iso_entry/archlive_iso.nix
+++ /dev/null
@@ -1,86 +0,0 @@
-# nixos-config - My current NixOS configuration
-#
-# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of my nixos-config.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{pkgs ? (builtins.getFlake "nixpkgs").legacyPackages."x86_64-linux"}: let
- signing_key = import ./signing_key.nix {inherit pkgs;};
-
- checked_iso = pkgs.stdenv.mkDerivation {
- pname = "archlinux-iso";
- version = "2024.05.01";
-
- srcs = [
- (pkgs.fetchurl {
- url = "https://archlinux.org/iso/2024.05.01/archlinux-2024.05.01-x86_64.iso.sig";
- hash = "sha256-QOGYng6a7zA5EJKGotDccJ7fD2MmPPXQEdVr1kjJvi4=";
- })
- (pkgs.fetchurl {
- url = "https://mirror.informatik.tu-freiberg.de/arch/iso/latest/archlinux-2024.05.01-x86_64.iso";
- hash = "sha256-G0oE74pzUIUqEwcO5JhEKwh6YHoYhAtN19mYZ+tfakw=";
- })
- (pkgs.fetchurl {
- url = "https://archlinux.org/iso/2024.05.01/b2sums.txt";
- hash = "sha256-HSMS13hHXFKKQsCA8spa7XtirHCBTmePwhOsStVPbHw=";
- })
- ];
-
- dontUnpack = true;
-
- nativeBuildInputs = with pkgs; [
- sequoia-sq
- ];
-
- buildPhase =
- /*
- bash
- */
- ''
- cp -r "${signing_key}" ./release-key.pgp
- for src in $srcs; do
- cp -r "$src" "$(stripHash "$src")"
- done
-
- sed '2d;3d;4d' b2sums.txt > b2sums_clean.txt
-
- # As per the directions from: https://archlinux.org/download/
-
- # blake hash check
- b2sum -c ./b2sums_clean.txt
-
- # pgp signature check
- sq verify --signer-file release-key.pgp --detached archlinux-2024.05.01-x86_64.iso.sig archlinux-2024.05.01-x86_64.iso
- '';
-
- installPhase = ''
- cp archlinux-2024.05.01-x86_64.iso "$out";
- '';
- };
-in
- pkgs.stdenv.mkDerivation {
- name = "live_iso_boot_entry";
-
- src = checked_iso;
-
- dontUnpack = true;
-
- nativeBuildInputs = with pkgs; [
- libarchive # for bsdtar
- ];
-
- buildPhase = ''
- mkdir iso
- bsdtar -xf "$src" -C iso
- '';
-
- installPhase = ''
- install -D ./iso/arch/boot/x86_64/initramfs-linux.img "$out/live/initramfs-linux.img"
- install -D ./iso/arch/boot/x86_64/vmlinuz-linux "$out/live/vmlinuz-linux"
-
- install -D "$src" "$out/archlinux.iso"
- '';
- }
diff --git a/modules/by-name/bo/boot/iso_entry/signing_key.nix b/modules/by-name/bo/boot/iso_entry/signing_key.nix
deleted file mode 100644
index d9268d75..00000000
--- a/modules/by-name/bo/boot/iso_entry/signing_key.nix
+++ /dev/null
@@ -1,27 +0,0 @@
-# nixos-config - My current NixOS configuration
-#
-# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of my nixos-config.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{pkgs ? (builtins.getFlake "nixpkgs").legacyPackages."x86_64-linux"}:
-pkgs.stdenv.mkDerivation {
- name = "archlinux_signing_keys";
-
- outputHash = "sha256-evGWzkxMaZw3rlixKsyWCS/ZvNuZ+OfXQb6sgiHz9XY=";
- outputHashAlgo = "sha256";
- NIX_SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
-
- nativeBuildInputs = with pkgs; [
- sequoia-sq
- ];
-
- dontUnpack = true;
-
- buildPhase = ''
- sq --verbose --no-cert-store --no-key-store network wkd fetch pierre@archlinux.org --output "$out"
- '';
-}
diff --git a/modules/by-name/bo/boot/module.nix b/modules/by-name/bo/boot/module.nix
index 4b95aedf..80a47420 100644
--- a/modules/by-name/bo/boot/module.nix
+++ b/modules/by-name/bo/boot/module.nix
@@ -12,139 +12,215 @@
lib,
pkgs,
modules,
+ modulesPath,
+ system,
+ specialArgs,
...
}: let
cfg = config.soispha.boot;
+
+ tails = let
+ tailsPrefix = "/EFI/tails";
+ in {
+ root = "${tailsPrefix}/tails.iso";
+ initrd = "${tailsPrefix}/initrd.img";
+ vmlinuz = "${tailsPrefix}/vmlinuz-linux";
+ };
+
+ iso = pkgs.tails-iso;
+
+ # From:
+ # - The extracted ISO's boot dir
+ # - Reverse engineered from:
+ # - `<tails iso squashfs>/usr/share/initramfs-tools/init`
+ # - `<tails iso squashfs>/usr/lib/live/boot/`
+ iso_options =
+ ## General options?
+ [
+ # "config"
+ "initrd=${tails.initrd}"
+
+ "efi_pstore.pstore_disable=1"
+ "erst_disable"
+ "init_on_free=1"
+ "mce=0"
+ "mds=full,nosmt"
+ "noautologin"
+ "noprompt"
+ "page_alloc.shuffle=1"
+ "randomize_kstack_offset=on"
+ "slab_nomerge"
+ "slub_debug=FZ"
+ "spec_store_bypass_disable=on"
+ "systemd.condition_needs_update=no"
+ "timezone=Etc/UTC"
+ "vsyscall=none"
+ ]
+ ## Systemd log options
+ ++ [
+ # "systemd.log_level=debug"
+ # "systemd.log_target=console"
+ # "console=tty1"
+ # "systemd.journald.forward_to_console=1"
+ # "systemd.unit=rescue.target"
+ ]
+ ## Options for the first `init` script
+ ++ [
+ # Use the `*-live` scripts
+ "boot=live"
+
+ "splash"
+ # "plymouth.enable=0"
+
+ # "quiet"
+ # "debug"
+ ]
+ ## Options for the `*-live` `init` scripts
+ ++ [
+ "module=Tails"
+
+ # TODO: RO-makes the fromiso not work <2026-06-08>
+ # "live-boot.read-only=/dev/nvme0*"
+
+ # Don't store things persistently
+ "nopersistence"
+
+ "fromiso=/dev/nvme0n1p1/${tails.root}"
+ ];
in {
options.soispha.boot = {
enable = lib.mkEnableOption "Bootloader configuration";
- # TODO: Add this option <2024-05-16>
- # enableIsoEntry = lib.mkEnableOption "an tails iso boot entry";
+ enableIsoEntry = lib.mkEnableOption "an tails iso boot entry";
};
imports = [
modules.lanzaboote.nixosModules.lanzaboote
];
- config = lib.mkIf cfg.enable (
- # let
- # cfg = config.boot.loader.systemd-boot;
- # inherit (config.boot.loader) efi;
- #
- # esa = n: lib.strings.escapeShellArg n;
- #
- # bootMountPoint =
- # if cfg.xbootldrMountPoint != null
- # then cfg.xbootldrMountPoint
- # else efi.efiSysMountPoint;
- #
- # nixosDir = "/EFI/nixos";
- #
- # # FIXME: This system has two big problems:
- # # 1. It does not updated files, which still have the same name
- # # 2. It forgets about files, which were 'deleted' in this configuration (these just
- # # stay on disk forever) <2024-05-11>
- # copyExtraFiles = ''
- # echo "[systemd-boot] copying files to ${bootMountPoint}"
- # empty_file=$(mktemp boot_empty_file_XXX)
- #
- # ${lib.concatStrings (lib.mapAttrsToList (n: v:
- # /*
- # bash
- # */
- # ''
- # if ! [ -e ${esa "${bootMountPoint}/${n}"} ]; then
- # install -Dp "${v}" ${esa "${bootMountPoint}/${n}"}
- # install -D "$empty_file" ${esa "${bootMountPoint}/${nixosDir}/.extra-files/${n}"}
- # fi
- # '')
- # cfg.extraFiles)}
- #
- # ${lib.concatStrings (lib.mapAttrsToList (n: v:
- # /*
- # bash
- # */
- # ''
- # # if ! [ -e ${esa "${bootMountPoint}/loader/entries/${n}"} ]; then
- # install -Dp "${pkgs.writeText n v}" ${esa "${bootMountPoint}/loader/entries/${n}"}
- # install -D "$empty_file" ${esa "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/${n}"}
- # # fi
- # '')
- # cfg.extraEntries)}
- # '';
- # in
- {
- # FIXME: Reactviate this whole iso thing when a disko redeploy is done.
- # (and switch to tails instead of arch) <2024-05-12>
- #
- # system.activationScripts = {
- # copyExtraFilesForBoot = copyExtraFiles;
- # };
+ config = lib.mkIf cfg.enable {
+ # This should only be necessary for `lanzaboote`, but that is the current default in
+ # this module.
+ soispha.impermanence.directories = [
+ "/var/lib/sbctl"
+ ];
- # This should only be necessary for `lanzaboote`, but that is the current default in
- # this module.
- soispha.impermanence.directories = [
- "/var/lib/sbctl"
- ];
+ boot = {
+ initrd = {
+ kernelModules = ["nvme" "btrfs"];
+ };
- boot = {
- initrd = {
- kernelModules = ["nvme" "btrfs"];
+ kernelPackages = pkgs.linuxPackages_latest;
+
+ lanzaboote = {
+ enable = true;
+ pkiBundle = "/var/lib/sbctl";
+
+ settings = {
+ # Disable editing the kernel command line (which could allow someone to become root)
+ editor = false;
+ default = "@saved";
};
+ };
- kernelPackages = pkgs.linuxPackages_latest;
+ loader = {
+ external = lib.mkIf cfg.enableIsoEntry {
+ installHook = lib.mkForce (let
+ lanzabooteCfg = config.boot.lanzaboote;
- lanzaboote = {
- enable = true;
- pkiBundle = "/var/lib/sbctl";
+ lanzabooteInstallHook = import "${modulesPath}/../lib/eval-config.nix" {
+ inherit system specialArgs;
+ modules = [
+ modules.lanzaboote.nixosModules.lanzaboote
- settings = {
- # Disable editing the kernel command line (which could allow someone to become root)
- editor = false;
- default = "@saved";
- };
+ {
+ # Copy the relevant config into the eval-module context.
+ boot = {
+ inherit (config.boot) kernelPackages;
+
+ lanzaboote = {
+ inherit (lanzabooteCfg) enable pkiBundle;
+ settings = {
+ inherit (lanzabooteCfg.settings) editor default;
+ };
+ };
+
+ loader = {
+ inherit (config.boot.loader) timeout efi systemd-boot;
+ };
+ };
+ systemd.package = config.systemd.package;
+ }
+ ];
+ };
+
+ install = pkgs.writeShellScript "wrapped-install-tails-iso-marker" ''
+ echo "[Wrapped bootloader install] Copying tails iso..."
+ ${copyExtraFiles}
+
+ echo "[Wrapped bootloader install] Running original lanzaboote install..."
+ ${lanzabooteInstallHook.config.boot.loader.external.installHook}
+ '';
+
+ copyExtraFiles = let
+ systemdCfg = config.boot.loader.systemd-boot;
+ nixosDir = "EFI/nixos";
+
+ bootMountPoint = config.boot.loader.efi.efiSysMountPoint;
+ install = lib.getExe' pkgs.coreutils "install";
+
+ inherit (lib) mapAttrsToList;
+ inherit (lib.strings) escapeShellArg concatStrings;
+ in
+ pkgs.writeShellScript "copy-extra-files" ''
+ ${concatStrings (
+ mapAttrsToList (n: v: ''
+ ${install} -Dp "${v}" "${bootMountPoint}/"${escapeShellArg n}
+ ${install} -D /dev/null "${bootMountPoint}/${nixosDir}/.extra-files/"${escapeShellArg n}
+ '')
+ systemdCfg.extraFiles
+ )}
+
+ ${lib.getExe pkgs.sbctl} sign "${bootMountPoint}/${tails.vmlinuz}"
+
+ ${concatStrings (
+ mapAttrsToList (n: v: ''
+ ${install} -Dp "${pkgs.writeText n v}" "${bootMountPoint}/loader/entries/"${escapeShellArg n}
+ ${install} -D /dev/null "${bootMountPoint}/${nixosDir}/.extra-files/loader/entries/"${escapeShellArg n}
+ '')
+ systemdCfg.extraEntries
+ )}
+ '';
+ in
+ install);
};
- loader = {
- systemd-boot = {
- # Lanzaboote currently replaces the systemd-boot module.
- # This setting is usually set to true in configuration.nix
- # generated at installation time. So we force it to false
- # for now.
- enable = false;
+ systemd-boot = lib.mkIf cfg.enableIsoEntry {
+ # Lanzaboote currently replaces the systemd-boot module.
+ enable = false;
- # extraEntries = {
- # "live.conf" = ''
- # title Archlinux Live ISO
- # linux /live/vmlinuz-linux
- # initrd /live/initramfs-linux.img
- # options img_dev=${config.soispha.disks.disk} img_loop=/archlinux.iso copytoram
- # '';
- # };
- #
- # extraFiles = let
- # iso = import ./archlive_iso.nix {inherit pkgs;};
- # in {
- # "archlinux.iso" = "${iso}/archlinux.iso";
- # "live/initramfs-linux.img" = "${iso}/live/initramfs-linux.img";
- # "live/vmlinuz-linux" = "${iso}/live/vmlinuz-linux";
- # };
+ extraEntries = {
+ "live.conf" = ''
+ title Tails ${iso.passthru.version} Live ISO
+ linux ${tails.vmlinuz}
+ initrd ${tails.initrd}
+ options ${builtins.concatStringsSep " " iso_options}
+ '';
};
- grub = {
- enable = false;
- # theme = pkgs.nixos-grub2-theme;
- splashImage = ./boot_pictures/gnu.png;
- efiSupport = true;
- device = "nodev"; # only for efi
+ extraFiles = {
+ # TODO: Check that the ISO we use for booting is _actually_ still the one we
+ # copied there (someone might exchange it in between) <2026-06-09>
+ "${tails.root}" = "${iso}/tails.iso";
+ "${tails.vmlinuz}" = "${iso}/live/vmlinuz-linux";
+ "${tails.initrd}" = "${iso}/live/initrd.img";
};
+ };
- efi = {
- canTouchEfiVariables = true;
- efiSysMountPoint = "/boot";
- };
+ efi = {
+ canTouchEfiVariables = true;
+ efiSysMountPoint = "/boot";
};
};
- }
- );
+ };
+ };
}
diff --git a/modules/by-name/ca/calibre/module.nix b/modules/by-name/ca/calibre/module.nix
new file mode 100644
index 00000000..4188e360
--- /dev/null
+++ b/modules/by-name/ca/calibre/module.nix
@@ -0,0 +1,38 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ config,
+ lib,
+ libraries,
+ ...
+}: let
+ cfg = config.soispha.programs.calibre;
+in {
+ options.soispha.programs.calibre = {
+ enable = libraries.base.options.mkEnable "calibre";
+ };
+
+ config = lib.mkIf cfg.enable {
+ soispha.impermanence.userDirectories = [
+ ".config/calibre"
+ ];
+ soispha.services.unison.pathsToSync = [
+ "~/.config/calibre"
+ ];
+
+ # Calibre depends on multiple “AI” slop stuff, which I don't really want to have in my
+ # closure.
+ # Considering, that calibre is probably also LLM friendly (and maybe contains
+ # vibe-coded stuff), I should probably replace it, but currently I'll keep it.
+ home-manager.users.soispha.programs.calibre = {
+ enable = false;
+ };
+ };
+}
diff --git a/modules/by-name/ca/cargo/module.nix b/modules/by-name/ca/cargo/module.nix
index 980ebcba..6a0675d3 100644
--- a/modules/by-name/ca/cargo/module.nix
+++ b/modules/by-name/ca/cargo/module.nix
@@ -10,17 +10,25 @@
{
config,
lib,
+ libraries,
...
}: let
cfg = config.soispha.programs.cargo;
in {
options.soispha.programs.cargo = {
- enable = lib.mkEnableOption "cargo";
+ enable = libraries.base.options.mkEnable "cargo";
};
+
config = lib.mkIf cfg.enable {
home-manager.users.soispha = {
- home.sessionVariables = {
- CARGO_HOME = "${config.home-manager.users.soispha.xdg.dataHome}/cargo";
+ programs.cargo = {
+ enable = true;
+ package = null;
+ cargoHome = "${config.home-manager.users.soispha.xdg.dataHome}/cargo";
+
+ settings = {
+ build.build-dir = "${config.home-manager.users.soispha.xdg.cacheHome}/cargo/shared-target/";
+ };
};
};
};
diff --git a/modules/by-name/cl/cleanup/module.nix b/modules/by-name/cl/cleanup/module.nix
index 98c699eb..11e16032 100644
--- a/modules/by-name/cl/cleanup/module.nix
+++ b/modules/by-name/cl/cleanup/module.nix
@@ -21,6 +21,11 @@ in {
config = lib.mkIf cfg.enable {
programs.nano.enable = false;
+
+ services.speechd.enable = false;
+
+ system.disableInstallerTools = true;
+
environment = {
defaultPackages = lib.mkForce [];
};
diff --git a/modules/by-name/di/direnv/landrun-fish.sh b/modules/by-name/di/direnv/landrun-fish.sh
new file mode 100755
index 00000000..908306b4
--- /dev/null
+++ b/modules/by-name/di/direnv/landrun-fish.sh
@@ -0,0 +1,48 @@
+#! /usr/bin/env sh
+
+# We unfortunately still need rw access to `/tmp`, because lua hardcodes it (and the libc implementation might too).
+_new_tmp="$(mktemp --directory -t landrun_fish_XXXXX)"
+trap 'rm --recursive "$_new_tmp"' EXIT
+
+# NOTE: We need to keep the XDG_RUNTIME_DIR, as pam_systemd sets it only once upon login. <2026-08-25>
+landrun \
+ --env XDG_RUNTIME_DIR \
+ --env NIX_SHELL_LEVEL \
+ --env TERM \
+ --env SHLVL \
+ --env IN_DIRENV_SANDBOX=true \
+ --env TMPDIR="$_new_tmp" \
+ --rox /nix/store \
+ --rw "$_new_tmp" \
+ --rw "/tmp" \
+ --ro /etc/ \
+ --ro /proc/ \
+ --rw /dev/ \
+ --rw "$XDG_RUNTIME_DIR" \
+ --rw "$HOME/repos/.Trash-1000/" \
+ --rw "$HOME/.cache" \
+ --rwx "$HOME/.cache/cargo/shared-target/" \
+ --ro "$HOME/.local/share/nix/trusted-settings.json" \
+ --rw "$HOME/.local/share/nix/repl-history" \
+ --rw "$HOME/.local/share/ssh/known_hosts" \
+ --ro "$HOME/.local/share/ssh/id_ed25519.pub" \
+ --rw "$HOME/.local/share/task/" \
+ --rw "$HOME/.local/share/nvim/" \
+ --rw "$HOME/.local/state/nvim/" \
+ --rw "$HOME/.local/share/cargo/" \
+ --rw "$HOME/.local/share/ctpv/" \
+ --rw "$HOME/.local/share/direnv/" \
+ --rw "$HOME/.local/share/lf/" \
+ --ro "$HOME/.config/fish/" \
+ --ro "$HOME/.config/direnv/" \
+ --ro "$HOME/.config/task/" \
+ --ro "$HOME/.config/nvim/" \
+ --ro "$HOME/.config/turtle/" \
+ --ro "$HOME/.config/git/" \
+ --rwx "$PWD" \
+ --connect-tcp 80 \
+ --connect-tcp 22 \
+ --connect-tcp 443 \
+ -- fish --login "$@"
+
+# vim: ft=sh
diff --git a/modules/by-name/di/direnv/module.nix b/modules/by-name/di/direnv/module.nix
index 7c81e671..b35d785d 100644
--- a/modules/by-name/di/direnv/module.nix
+++ b/modules/by-name/di/direnv/module.nix
@@ -10,21 +10,97 @@
{
config,
lib,
+ pkgs,
+ sources,
...
}: let
cfg = config.soispha.programs.direnv;
+ direnvCfg = config.home-manager.users.soispha.programs.direnv;
+
+ inherit (sources) devshells nixpkgs;
+
+ pre-cache = pkgs.runCommand "pre-cache.sh" {} ''
+ cat "${./pre-cache.sh}" > $out
+
+ substituteInPlace $out \
+ --replace-fail "%DEVSHELLS_REPO%" "${devshells}" \
+ --replace-fail "%DEVSHELLS_REPO_REF%" "${devshells.sourceInfo.rev}.${builtins.readFile "${nixpkgs}/.git-revision"}"
+ '';
+
+ landrun-fish = pkgs.writeShellApplication {
+ name = "landrun-fish";
+ text = builtins.readFile ./landrun-fish.sh;
+ inheritPath = true;
+ runtimeInputs = [
+ pkgs.landrun
+ ];
+ };
+
+ direnvPkg = lib.getExe direnvCfg.package;
in {
options.soispha.programs.direnv = {
enable = lib.mkEnableOption "direnv";
};
- config.home-manager.users.soispha.programs.direnv = lib.mkIf cfg.enable {
- enable = true;
- nix-direnv.enable = true;
- config = {
- warn_timeout = 0;
- # strict_env = true;
- # disable_stdin = true;
+ config = lib.mkIf cfg.enable {
+ soispha.programs.fish.integrations.direnv =
+ # fish
+ ''
+ set --global SHOULD_SANDBOX_DIRENV
+
+ function _direnv_export_eval
+ ${direnvPkg} export fish | source
+ end
+
+ function _direnv_hook --on-variable PWD --on-event fish_postexec
+ if not set --query IN_DIRENV_SANDBOX;
+ and set --query SHOULD_SANDBOX_DIRENV;
+ and ${direnvPkg} status | grep 'Found RC path' --quiet;
+ and ${direnvPkg} status | grep 'No .envrc or .env loaded' --quiet
+
+ # We found a RC path, but no RC file is loaded. So we can start our shell and
+ # load it.
+ ${lib.getExe landrun-fish} --init-command='_direnv_export_eval'
+
+ # After the user exists (via Ctrl+D), they probably want to change the
+ # directory.
+ ll
+ else
+ _direnv_export_eval
+ end
+ end
+ '';
+
+ home-manager.users.soispha = {
+ programs.direnv = {
+ enable = true;
+
+ package = pkgs.direnv-patched;
+
+ nix-direnv = {
+ enable = true;
+ package = pkgs.nix-direnv.override {nix = config.nix.package;};
+ };
+
+ # Handled by myself (and the script is overridden)
+ enableBashIntegration = true;
+ enableZshIntegration = true;
+ enableFishIntegration = false;
+
+ config = {
+ warn_timeout = 0;
+
+ # NOTE: Something variable is undeclared in the my default loaded stuff :/. <2026-08-18>
+ # strict_env = true;
+
+ # disable_stdin = true;
+ };
+ };
+ xdg.configFile = {
+ "direnv/lib/hm-pre-cache.sh" = {
+ source = pre-cache;
+ };
+ };
};
};
}
diff --git a/modules/by-name/di/direnv/pre-cache.sh b/modules/by-name/di/direnv/pre-cache.sh
new file mode 100755
index 00000000..706dae61
--- /dev/null
+++ b/modules/by-name/di/direnv/pre-cache.sh
@@ -0,0 +1,89 @@
+#! /usr/bin/env sh
+
+_pre_cache_log() {
+ printf "pre-cache: %s\n" "$1"
+}
+_pre_cache_preflight() {
+ set -eu
+ _nix_direnv_preflight
+
+ REPO="%DEVSHELLS_REPO%"
+ system="x86_64-linux"
+ cache_dir="$XDG_CACHE_HOME/pre_cache"
+ [ -d "$cache_dir" ] || mkdir --parents "$cache_dir"
+
+ repo_ref="%DEVSHELLS_REPO_REF%"
+ final_cache="$cache_dir/$repo_ref"
+
+ if ! [ -d "$final_cache" ]; then
+ _pre_cache_log "Cache dir invalidated"
+
+ # There is not yet a final cache directory, so the ref must have changed.
+ # Therefore we delete the whole cache directory, so we invalidate old cached shells.
+ set -u
+ rm --recursive "$cache_dir"
+
+ mkdir --parents "$final_cache"
+ fi
+
+ # TODO: Direnv requires `-u` to be unset, for it's stdlib :/. <2026-08-22>
+ set +u
+}
+
+_pre_cache_load_shell() {
+ lang="$1"
+ lang_cache="$final_cache/$lang"
+
+ if ! [ -f "$lang_cache" ]; then
+ tmp_profile="$final_cache/__tmp_profile"
+ profile="$final_cache/__profile"
+
+ # It does not already exists, we need to evaluate it.
+ _nix print-dev-env --profile "$tmp_profile" "$REPO#$lang" >"$lang_cache"
+
+ _nix_add_gcroot "$tmp_profile" "$profile"
+ # rm "$tmp_profile" "$tmp_profile"* || true
+ fi
+
+ _pre_cache_log "Loaded dev-shell for $lang."
+ _nix_import_env "$lang_cache"
+}
+
+_pre_cache_build_shells() {
+ installables=""
+
+ for lang in "$@"; do
+ lang_cache="$final_cache/$lang"
+
+ if ! [ -f "$lang_cache" ]; then
+ new_part="$REPO#devShells.$system.$lang"
+
+ if [ -z "$installables" ]; then
+ installables="$new_part"
+ else
+ installables="$installables $new_part"
+ fi
+ fi
+ done
+
+ if [ -n "$installables" ]; then
+ _pre_cache_log "Building '$installables'..."
+
+ # We want shell-spliting
+ # shellcheck disable=SC2086
+ _nix build --no-link $installables
+ fi
+}
+
+pre_cache_load() {
+ _pre_cache_preflight
+
+ # Build all not-yet build shells concurrently. That speeds up the initial load.
+ _pre_cache_build_shells "$@"
+
+ for lan in "$@"; do
+ _pre_cache_load_shell "$lan"
+ done
+}
+
+# vim: ft=sh
diff --git a/modules/by-name/di/disks/hibernate.nix b/modules/by-name/di/disks/hibernate.nix
index e983f9c7..d3f8fe8b 100644
--- a/modules/by-name/di/disks/hibernate.nix
+++ b/modules/by-name/di/disks/hibernate.nix
@@ -48,7 +48,8 @@
systemd-hibernate.serviceConfig.Environment = "SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK=1";
systemd-logind.serviceConfig.Environment = "SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK=1";
};
- sleep.extraConfig = ''
- HibernateDelaySec=5m
- '';
+
+ sleep.settings.Sleep = {
+ HibernateDelaySec = "10m";
+ };
}
diff --git a/modules/by-name/di/disks/module.nix b/modules/by-name/di/disks/module.nix
index 3e9d4614..ed5c939a 100644
--- a/modules/by-name/di/disks/module.nix
+++ b/modules/by-name/di/disks/module.nix
@@ -14,10 +14,9 @@
modules,
...
}: let
- # FIXME: The iso redeploy requires a bigger efi partition <2024-05-12>
cfg = config.soispha.disks;
defaultMountOptions = [
- "compress=zstd:3" # This saves disk space, at a performance cost
+ "compress-force=zstd:15" # This saves disk space, at a performance cost
"noatime" # should have some performance upsides, and I don't use it anyways
"lazytime" # make time changes in memory
];
@@ -34,15 +33,10 @@ in {
ssd = lib.mkEnableOption "ssd specific improvements, like trim";
swap = {
- uuid = lib.mkOption {
+ ram_size = lib.mkOption {
type = lib.types.str;
- example = lib.literalExpression "d1d20ae7-3d8a-44da-86da-677dbbb10c89";
- description = "The uuid of the swapfile";
- };
- resumeOffset = lib.mkOption {
- type = lib.types.str;
- example = lib.literalExpression "134324224";
- description = "The resume offset of the swapfile";
+ example = lib.literalExpression "16G";
+ description = "The size of the ram (translates to the swapfile size)";
};
};
};
@@ -54,6 +48,12 @@ in {
config = lib.mkIf cfg.enable {
systemd = lib.recursiveUpdate (import ./hibernate.nix {inherit pkgs;}) (import ./fstrim.nix {inherit pkgs lib cfg;});
+ services.btrfs.autoScrub = {
+ enable = true;
+ fileSystems = ["/srv" "/nix"];
+ interval = "monthly";
+ };
+
disko.devices = {
disk = {
main = {
@@ -61,21 +61,44 @@ in {
content = {
type = "gpt";
partitions = {
+ ESP = {
+ # 2GiB plus 512MiB for tails ISO and normal boot stuff
+ size = "2600M";
+
+ type = "EF00";
+ content = {
+ type = "filesystem";
+ format = "vfat";
+ mountpoint = "/boot";
+ mountOptions = ["umask=0077"];
+ };
+ };
+
+ nix = {
+ size = "30G";
+ content = {
+ type = "luks";
+ name = "nixos-store";
+ extraOpenArgs = ["--allow-discards"];
+ content = {
+ type = "btrfs";
+ extraArgs = ["-f" "--label nixos-store"]; # Override existing partitions
+ mountpoint = "/nix";
+ mountOptions = defaultMountOptions;
+ };
+ };
+ };
+
root = {
size = "100%";
- name = "root";
content = {
type = "luks";
- name = "nixos";
+ name = "nixos-root";
extraOpenArgs = ["--allow-discards"];
content = {
type = "btrfs";
- extraArgs = ["-f" "--label nixos"]; # Override existing partitions
+ extraArgs = ["-f" "--label nixos-root"]; # Override existing partitions
subvolumes = {
- "nix" = {
- mountpoint = "/nix";
- mountOptions = defaultMountOptions;
- };
"persistent-storage" = {
mountpoint = "/srv";
mountOptions = defaultMountOptions;
@@ -90,21 +113,17 @@ in {
"noatime" # should have some performance upsides, and I don't use it anyways
"lazytime" # make time changes in memory
];
+ swap = {
+ swapfile = {
+ priority = -1; # lower than zramSwap, just in case
+ size = cfg.swap.ram_size;
+ };
+ };
};
};
};
};
};
- boot = {
- type = "EF00";
- size = "512M";
- name = "boot";
- content = {
- type = "filesystem";
- format = "vfat";
- mountpoint = "/boot";
- };
- };
};
};
};
@@ -130,6 +149,9 @@ in {
};
};
fileSystems = {
+ "/nix" = {
+ neededForBoot = true;
+ };
"/srv" = {
neededForBoot = true;
};
@@ -137,23 +159,14 @@ in {
neededForBoot = true;
};
};
- swapDevices = [
- #{
- # device = "/swap/swapfile";
- # priority = 1; # lower than zramSwap, just in case
- # # size = 2048; # TODO: can nixos create a btrfs swapfile correctly?
- #}
- ];
zramSwap = {
enable = true;
priority = 10; # needs to be higher than hardware-swap
};
boot = {
kernelParams = [
- "resume_offset=${cfg.swap.resumeOffset}"
"zswap.enabled=0" # zswap and zram are not really compatible
];
- resumeDevice = "/dev/disk/by-uuid/${cfg.swap.uuid}";
};
};
}
diff --git a/modules/by-name/lf/lf/wrappers/lm/lm.sh b/modules/by-name/fi/fish/config/01_show-task.fish
index 71213721..21db1112 100755..100644
--- a/modules/by-name/lf/lf/wrappers/lm/lm.sh
+++ b/modules/by-name/fi/fish/config/01_show-task.fish
@@ -1,4 +1,4 @@
-#!/usr/bin/env dash
+#!/usr/bin/env fish
# nixos-config - My current NixOS configuration
#
@@ -10,19 +10,11 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-die() {
- echo "ERROR: $1"
- exit 1
-}
+# Display current tasks
+test -z "$NVIM"; and task next
-msg() {
- echo "$1"
-}
+# Empty the default greeting
+set --global fish_greeting ""
-if [ -f "$XDG_RUNTIME_DIR/ll/last_directory" ]; then
- last_dir="$(cat "$XDG_RUNTIME_DIR/ll/last_directory")"
- cd "$last_dir" || die "$last_dir does not exist!"
-else
- msg "No last directory saved (try using ll instead)."
-fi
-# vim: ft=sh
+# Set a nice theme
+fish_config theme choose ayu
diff --git a/modules/by-name/fi/fish/config/keybindings/00__start.fish b/modules/by-name/fi/fish/config/keybindings/00__start.fish
new file mode 100644
index 00000000..75227ef1
--- /dev/null
+++ b/modules/by-name/fi/fish/config/keybindings/00__start.fish
@@ -0,0 +1 @@
+bind --erase --all
diff --git a/modules/by-name/fi/fish/config/keybindings/01_insert.fish b/modules/by-name/fi/fish/config/keybindings/01_insert.fish
new file mode 100644
index 00000000..984e0e21
--- /dev/null
+++ b/modules/by-name/fi/fish/config/keybindings/01_insert.fish
@@ -0,0 +1,86 @@
+bind --preset --mode insert ctrl-y yank
+bind --preset --mode insert alt-y yank-pop
+bind --preset --mode insert right forward-char
+bind --preset --mode insert left backward-char
+bind --preset --mode insert ctrl-right forward-token
+bind --preset --mode insert ctrl-left backward-token
+bind --preset --mode insert pageup beginning-of-history
+bind --preset --mode insert pagedown end-of-history
+bind --preset --mode insert ctrl-x fish_clipboard_copy
+bind --preset --mode insert ctrl-v fish_clipboard_paste
+bind --preset --mode insert tab complete
+bind --preset --mode insert ctrl-i complete
+bind --preset --mode insert ctrl-s pager-toggle-search
+bind --preset --mode insert shift-tab complete-and-search
+bind --preset --mode insert shift-delete history-delete or backward-delete-char
+bind --preset --mode insert down down-or-search
+bind --preset --mode insert up up-or-search
+bind --preset --mode insert shift-right forward-bigword
+bind --preset --mode insert shift-left backward-bigword
+bind --preset --mode insert alt-b prevd-or-backward-word
+bind --preset --mode insert alt-f nextd-or-forward-word
+bind --preset --mode insert alt-right nextd-or-forward-word
+bind --preset --mode insert \e\[1\;9C nextd-or-forward-word
+bind --preset --mode insert alt-left prevd-or-backward-word
+bind --preset --mode insert \e\[1\;9D prevd-or-backward-word
+bind --preset --mode insert alt-up history-token-search-backward
+bind --preset --mode insert alt-down history-token-search-forward
+bind --preset --mode insert \e\[1\;9A history-token-search-backward
+bind --preset --mode insert \e\[1\;9B history-token-search-forward
+bind --preset --mode insert alt-. history-token-search-backward
+bind --preset --mode insert alt-l __fish_list_current_token
+bind --preset --mode insert alt-o __fish_preview_current_file
+bind --preset --mode insert alt-w __fish_whatis_current_token
+bind --preset --mode insert ctrl-l 'status test-terminal-feature scroll-content-up && commandline -f scrollback-push' clear-screen
+bind --preset --mode insert ctrl-c clear-commandline
+bind --preset --mode insert ctrl-u backward-kill-line
+bind --preset --mode insert ctrl-k kill-line
+bind --preset --mode insert ctrl-w backward-kill-path-component
+bind --preset --mode insert end end-of-line
+bind --preset --mode insert home beginning-of-line
+bind --preset --mode insert alt-d 'if test "$(commandline; printf .)" = \\n.; __fish_echo dirh; else; commandline -f kill-word; end'
+bind --preset --mode insert ctrl-d delete-or-exit
+bind --preset --mode insert alt-s 'for cmd in sudo doas please run0; if command -q $cmd; fish_commandline_prepend $cmd; break; end; end'
+bind --preset --mode insert f1 __fish_man_page
+bind --preset --mode insert alt-h __fish_man_page
+bind --preset --mode insert alt-p __fish_paginate
+bind --preset --mode insert alt-# __fish_toggle_comment_commandline
+
+bind --preset --mode insert ctrl-e edit_command_buffer
+
+bind --preset --mode insert '' self-insert
+bind --preset --mode insert space self-insert expand-abbr
+bind --preset --mode insert \; self-insert expand-abbr
+bind --preset --mode insert \| self-insert expand-abbr
+bind --preset --mode insert \& self-insert expand-abbr
+bind --preset --mode insert \> self-insert expand-abbr
+bind --preset --mode insert \< self-insert expand-abbr
+bind --preset --mode insert shift-enter 'commandline -i \\n (commandline --search-field >/dev/null && echo --search-field)' expand-abbr
+bind --preset --mode insert alt-enter 'commandline -i \\n (commandline --search-field >/dev/null && echo --search-field)' expand-abbr
+bind --preset --mode insert \) self-insert expand-abbr
+bind --preset --mode insert ctrl-space 'test -n "$(commandline)" && commandline -i " " (commandline --search-field >/dev/null && echo --search-field)'
+bind --preset --mode insert shift-space 'commandline -i " " (commandline --search-field >/dev/null && echo --search-field)' expand-abbr
+bind --preset --mode insert enter execute
+bind --preset --mode insert ctrl-j execute
+bind --preset --mode insert ctrl-m execute
+bind --preset --mode insert ctrl-enter execute
+
+bind --preset --mode insert ctrl-n accept-autosuggestion
+bind --preset --mode insert delete delete-char
+bind --preset --mode insert backspace backward-delete-char
+bind --preset --mode insert shift-backspace backward-delete-char
+bind --preset --mode insert ctrl-h backward-delete-char
+
+function _bind_insert_escape
+ if commandline -P
+ commandline -f cancel
+ else
+ set fish_bind_mode default
+ if test (count (commandline --cut-at-cursor | tail -c2)) != 2
+ commandline -f backward-char
+ end
+ commandline -f repaint-mode
+ end
+end
+bind --preset --mode insert escape _bind_insert_escape
+bind --preset --mode insert ctrl-\[ _bind_insert_escape
diff --git a/modules/by-name/fi/fish/config/keybindings/02_command.fish b/modules/by-name/fi/fish/config/keybindings/02_command.fish
new file mode 100644
index 00000000..7cea46c5
--- /dev/null
+++ b/modules/by-name/fi/fish/config/keybindings/02_command.fish
@@ -0,0 +1,185 @@
+bind --preset --mode default h 'fish_vi_run_count backward-char'
+bind --preset --mode default t 'fish_vi_run_count down-or-search'
+bind --preset --mode default n 'fish_vi_run_count up-or-search'
+bind --preset --mode default s 'fish_vi_run_count forward-char'
+bind --preset --mode default left backward-char
+bind --preset --mode default down down-or-search
+bind --preset --mode default up up-or-search
+bind --preset --mode default right forward-char
+
+bind --preset --mode default b 'fish_vi_run_count backward-word'
+bind --preset --mode default B 'fish_vi_run_count backward-bigword'
+bind --preset --mode default g,e 'fish_vi_run_count backward-word-end'
+bind --preset --mode default g,E 'fish_vi_run_count backward-bigword-end'
+bind --preset --mode default w 'fish_vi_run_count forward-word-vi'
+bind --preset --mode default W 'fish_vi_run_count forward-bigword-vi'
+bind --preset --mode default e 'fish_vi_run_count forward-word-end'
+bind --preset --mode default E 'fish_vi_run_count forward-bigword-end'
+
+bind --preset --mode default ctrl-y yank
+bind --preset --mode default alt-y yank-pop
+
+bind --preset --mode default ctrl-v edit_command_buffer
+
+bind --preset --mode default ctrl-right forward-token
+bind --preset --mode default ctrl-left backward-token
+
+# bind --preset --mode default ctrl-v fish_clipboard_paste
+bind --preset --mode default \",\*,y,y fish_clipboard_copy
+bind --preset --mode default \",\*,Y fish_clipboard_copy
+bind --preset --mode default \",+,y,y fish_clipboard_copy
+bind --preset --mode default \",+,Y fish_clipboard_copy
+
+bind --preset --mode default pageup beginning-of-history
+bind --preset --mode default pagedown end-of-history
+bind --preset --mode default ctrl-x fish_vi_dec
+bind --preset --mode default escape 'set -g __fish_vi_count'
+bind --preset --mode default ctrl-\[ 'set -g __fish_vi_count'
+bind --preset --mode default tab complete
+bind --preset --mode default ctrl-i complete
+bind --preset --mode default ctrl-s pager-toggle-search
+bind --preset --mode default shift-tab complete-and-search
+bind --preset --mode default shift-delete history-delete or backward-delete-char
+bind --preset --mode default shift-right forward-bigword
+bind --preset --mode default shift-left backward-bigword
+bind --preset --mode default alt-b prevd-or-backward-word
+bind --preset --mode default alt-f nextd-or-forward-word
+bind --preset --mode default alt-right nextd-or-forward-word
+bind --preset --mode default \e\[1\;9C nextd-or-forward-word
+bind --preset --mode default alt-left prevd-or-backward-word
+bind --preset --mode default \e\[1\;9D prevd-or-backward-word
+bind --preset --mode default alt-up history-token-search-backward
+bind --preset --mode default alt-down history-token-search-forward
+bind --preset --mode default \e\[1\;9A history-token-search-backward
+bind --preset --mode default \e\[1\;9B history-token-search-forward
+bind --preset --mode default alt-. history-token-search-backward
+bind --preset --mode default alt-l __fish_list_current_token
+bind --preset --mode default alt-o __fish_preview_current_file
+bind --preset --mode default alt-w __fish_whatis_current_token
+bind --preset --mode default ctrl-l 'status test-terminal-feature scroll-content-up && commandline -f scrollback-push' clear-screen
+bind --preset --mode default --sets-mode insert ctrl-c clear-commandline repaint-mode
+bind --preset --mode default ctrl-u backward-kill-line
+bind --preset --mode default ctrl-k kill-line
+bind --preset --mode default ctrl-w backward-kill-path-component
+bind --preset --mode default end end-of-line
+bind --preset --mode default home beginning-of-line
+bind --preset --mode default alt-d 'if test "$(commandline; printf .)" = \\n.; __fish_echo dirh; else; commandline -f kill-word; end'
+bind --preset --mode default ctrl-d delete-or-exit
+bind --preset --mode default alt-s 'for cmd in sudo doas please run0; if command -q $cmd; fish_commandline_prepend $cmd; break; end; end'
+bind --preset --mode default f1 __fish_man_page
+bind --preset --mode default alt-h __fish_man_page
+bind --preset --mode default alt-p __fish_paginate
+bind --preset --mode default alt-# __fish_toggle_comment_commandline
+bind --preset --mode default :,q exit
+bind --preset --mode default 1 'fish_vi_arg_digit 1'
+bind --preset --mode default 2 'fish_vi_arg_digit 2'
+bind --preset --mode default 3 'fish_vi_arg_digit 3'
+bind --preset --mode default 4 'fish_vi_arg_digit 4'
+bind --preset --mode default 5 'fish_vi_arg_digit 5'
+bind --preset --mode default 6 'fish_vi_arg_digit 6'
+bind --preset --mode default 7 'fish_vi_arg_digit 7'
+bind --preset --mode default 8 'fish_vi_arg_digit 8'
+bind --preset --mode default 9 'fish_vi_arg_digit 9'
+bind --preset --mode default 0 'if test -n "$__fish_vi_count"; fish_vi_arg_digit 0; else; commandline -f beginning-of-line; end'
+
+
+bind --preset --mode default x __fish_vi_delete_char
+bind --preset --mode default X __fish_vi_backward_delete_char
+
+bind --preset --mode default --sets-mode insert enter execute
+bind --preset --mode default --sets-mode insert ctrl-j execute
+bind --preset --mode default --sets-mode insert ctrl-m execute
+bind --preset --mode default --sets-mode insert o 'set fish_cursor_end_mode exclusive' insert-line-under repaint-mode
+bind --preset --mode default --sets-mode insert O 'set fish_cursor_end_mode exclusive' insert-line-over repaint-mode
+bind --preset --mode default --sets-mode insert i repaint-mode
+bind --preset --mode default --sets-mode insert I beginning-of-line repaint-mode
+bind --preset --mode default --sets-mode insert a 'set fish_cursor_end_mode exclusive' forward-single-char repaint-mode
+bind --preset --mode default --sets-mode insert A 'set fish_cursor_end_mode exclusive' end-of-line repaint-mode
+bind --preset --mode default --sets-mode visual v begin-selection repaint-mode
+bind --preset --mode default g,g beginning-of-buffer
+bind --preset --mode default G end-of-buffer
+bind --preset --mode default \$ end-of-line
+bind --preset --mode default g,\$ end-of-line
+bind --preset --mode default ^ beginning-of-line
+bind --preset --mode default g,^ beginning-of-line
+bind --preset --mode default _ beginning-of-line
+bind --preset --mode default u undo
+bind --preset --mode default ctrl-r redo
+bind --preset --mode default \[ history-token-search-backward
+bind --preset --mode default \] history-token-search-forward
+bind --preset --mode default --sets-mode insert / history-pager repaint-mode
+
+bind --preset --mode default delete delete-char
+bind --preset --mode default backspace backward-char
+bind --preset --mode default ctrl-h backward-char
+bind --preset --mode default d 'fish_vi_start_operator delete'
+bind --preset --mode default c 'fish_vi_start_operator change'
+bind --preset --mode default y 'fish_vi_start_operator yank'
+bind --preset --mode default g,\~ 'fish_vi_start_operator swap-case'
+
+bind --preset --mode default D kill-line
+bind --preset --mode default d,\$ kill-line
+bind --preset --mode default d,^ backward-kill-line
+bind --preset --mode default d,0 backward-kill-line
+bind --preset --mode default d,i,w kill-inner-word
+bind --preset --mode default d,i,W kill-inner-bigword
+bind --preset --mode default d,a,w kill-a-word
+bind --preset --mode default d,a,W kill-a-bigword
+bind --preset --mode default d,i,b jump-till-matching-bracket and jump-till-matching-bracket and begin-selection jump-till-matching-bracket kill-selection end-selection
+bind --preset --mode default d,a,b jump-to-matching-bracket and jump-to-matching-bracket and begin-selection jump-to-matching-bracket kill-selection end-selection
+bind --preset --mode default d,i backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection
+bind --preset --mode default d,a backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection
+bind --preset --mode default d,\; begin-selection repeat-jump kill-selection end-selection
+bind --preset --mode default d,comma begin-selection repeat-jump-reverse kill-selection end-selection
+bind --preset --mode default \~ togglecase-char forward-single-char
+bind --preset --mode default g,u downcase-word
+bind --preset --mode default g,U upcase-word
+bind --preset --mode default J end-of-line delete-char
+bind --preset --mode default K 'man (commandline -t) 2>/dev/null; or echo -n \\a'
+bind --preset --mode default Y kill-whole-line yank
+bind --preset --mode default y,\$ kill-line yank
+bind --preset --mode default y,^ backward-kill-line yank
+bind --preset --mode default y,0 backward-kill-line yank
+bind --preset --mode default y,i,w kill-inner-word yank
+bind --preset --mode default y,i,W kill-inner-bigword yank
+bind --preset --mode default y,a,w kill-a-word yank
+bind --preset --mode default y,a,W kill-a-bigword yank
+bind --preset --mode default y,i,b jump-till-matching-bracket and jump-till-matching-bracket and begin-selection jump-till-matching-bracket fish_vi_yank_selection end-selection
+bind --preset --mode default y,a,b jump-to-matching-bracket and jump-to-matching-bracket and begin-selection jump-to-matching-bracket fish_vi_yank_selection end-selection
+bind --preset --mode default y,i backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump fish_vi_yank_selection end-selection
+bind --preset --mode default y,a backward-jump and repeat-jump-reverse and begin-selection repeat-jump fish_vi_yank_selection end-selection
+bind --preset --mode default % jump-to-matching-bracket
+bind --preset --mode default f forward-jump
+bind --preset --mode default F backward-jump
+# bind --preset --mode default t forward-jump-till
+bind --preset --mode default T backward-jump-till
+bind --preset --mode default \; repeat-jump
+bind --preset --mode default comma repeat-jump-reverse
+bind --preset --mode default p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' yank
+bind --preset --mode default P yank
+bind --preset --mode default g,p yank-pop
+bind --preset --mode default \",\*,p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' fish_clipboard_paste
+bind --preset --mode default \",\*,P fish_clipboard_paste
+bind --preset --mode default \",+,p 'set -g fish_cursor_end_mode exclusive' forward-char 'set -g fish_cursor_end_mode inclusive' fish_clipboard_paste
+bind --preset --mode default \",+,P fish_clipboard_paste
+
+bind --preset --mode default --sets-mode replace_one r repaint-mode
+bind --preset --mode default --sets-mode replace R repaint-mode
+
+bind --preset --mode default ctrl-a fish_vi_inc
+bind --preset --mode default \# __fish_toggle_comment_commandline
+
+# bind --preset --mode default --sets-mode insert s delete-char repaint-mode
+bind --preset --mode default --sets-mode insert S kill-inner-line repaint-mode
+bind --preset --mode default --sets-mode insert C kill-line repaint-mode
+bind --preset --mode default --sets-mode insert c,\$ kill-line repaint-mode
+bind --preset --mode default --sets-mode insert c,^ backward-kill-line repaint-mode
+bind --preset --mode default --sets-mode insert c,0 backward-kill-line repaint-mode
+bind --preset --mode default --sets-mode insert c,i,w kill-inner-word repaint-mode
+bind --preset --mode default --sets-mode insert c,i,W kill-inner-bigword repaint-mode
+bind --preset --mode default --sets-mode insert c,a,w kill-a-word repaint-mode
+bind --preset --mode default --sets-mode insert c,a,W kill-a-bigword repaint-mode
+bind --preset --mode default --sets-mode insert c,i,b jump-till-matching-bracket and jump-till-matching-bracket and begin-selection jump-till-matching-bracket kill-selection end-selection
+bind --preset --mode default --sets-mode insert c,a,b jump-to-matching-bracket and jump-to-matching-bracket and begin-selection jump-to-matching-bracket kill-selection end-selection
+bind --preset --mode default --sets-mode insert c,i backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection repaint-mode
+bind --preset --mode default --sets-mode insert c,a backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection repaint-mode
diff --git a/modules/by-name/fi/fish/config/keybindings/03_visual.fish b/modules/by-name/fi/fish/config/keybindings/03_visual.fish
new file mode 100644
index 00000000..3996549b
--- /dev/null
+++ b/modules/by-name/fi/fish/config/keybindings/03_visual.fish
@@ -0,0 +1,94 @@
+bind --preset --mode visual ctrl-y yank
+bind --preset --mode visual alt-y yank-pop
+bind --preset --mode visual right forward-char
+bind --preset --mode visual left backward-char
+bind --preset --mode visual ctrl-right forward-token
+bind --preset --mode visual ctrl-left backward-token
+bind --preset --mode visual pageup beginning-of-history
+bind --preset --mode visual pagedown end-of-history
+bind --preset --mode visual ctrl-x fish_clipboard_copy
+bind --preset --mode visual ctrl-v fish_clipboard_paste
+bind --preset --mode visual --sets-mode default escape end-selection repaint-mode
+bind --preset --mode visual --sets-mode default ctrl-\[ end-selection repaint-mode
+bind --preset --mode visual tab complete
+bind --preset --mode visual ctrl-i complete
+bind --preset --mode visual ctrl-s pager-toggle-search
+bind --preset --mode visual shift-tab complete-and-search
+bind --preset --mode visual shift-delete history-delete or backward-delete-char
+bind --preset --mode visual down down-or-search
+bind --preset --mode visual up up-or-search
+bind --preset --mode visual shift-right forward-bigword
+bind --preset --mode visual shift-left backward-bigword
+bind --preset --mode visual alt-b prevd-or-backward-word
+bind --preset --mode visual alt-f nextd-or-forward-word
+bind --preset --mode visual alt-right nextd-or-forward-word
+bind --preset --mode visual \e\[1\;9C nextd-or-forward-word
+bind --preset --mode visual alt-left prevd-or-backward-word
+bind --preset --mode visual \e\[1\;9D prevd-or-backward-word
+bind --preset --mode visual alt-up history-token-search-backward
+bind --preset --mode visual alt-down history-token-search-forward
+bind --preset --mode visual \e\[1\;9A history-token-search-backward
+bind --preset --mode visual \e\[1\;9B history-token-search-forward
+bind --preset --mode visual alt-. history-token-search-backward
+bind --preset --mode visual alt-l __fish_list_current_token
+bind --preset --mode visual alt-o __fish_preview_current_file
+bind --preset --mode visual alt-w __fish_whatis_current_token
+bind --preset --mode visual ctrl-l 'status test-terminal-feature scroll-content-up && commandline -f scrollback-push' clear-screen
+bind --preset --mode visual --sets-mode default ctrl-c end-selection repaint-mode
+bind --preset --mode visual ctrl-u backward-kill-line
+bind --preset --mode visual ctrl-k kill-line
+bind --preset --mode visual ctrl-w backward-kill-path-component
+bind --preset --mode visual end end-of-line
+bind --preset --mode visual home beginning-of-line
+bind --preset --mode visual alt-d 'if test "$(commandline; printf .)" = \\n.; __fish_echo dirh; else; commandline -f kill-word; end'
+bind --preset --mode visual ctrl-d delete-or-exit
+bind --preset --mode visual alt-s 'for cmd in sudo doas please run0; if command -q $cmd; fish_commandline_prepend $cmd; break; end; end'
+bind --preset --mode visual f1 __fish_man_page
+bind --preset --mode visual alt-h __fish_man_page
+bind --preset --mode visual alt-p __fish_paginate
+bind --preset --mode visual alt-# __fish_toggle_comment_commandline
+bind --preset --mode visual alt-e edit_command_buffer
+bind --preset --mode visual alt-v edit_command_buffer
+
+bind --preset --mode visual h backward-char
+bind --preset --mode visual l forward-char
+bind --preset --mode visual k up-line
+bind --preset --mode visual j down-line
+bind --preset --mode visual b backward-word
+bind --preset --mode visual B backward-bigword
+bind --preset --mode visual g,e backward-word-end
+bind --preset --mode visual g,E backward-bigword-end
+bind --preset --mode visual w forward-word-vi
+bind --preset --mode visual W forward-bigword-vi
+bind --preset --mode visual e forward-word-end
+bind --preset --mode visual E forward-bigword-end
+bind --preset --mode visual o swap-selection-start-stop repaint-mode
+bind --preset --mode visual % jump-to-matching-bracket
+bind --preset --mode visual f forward-jump
+bind --preset --mode visual t forward-jump-till
+bind --preset --mode visual F backward-jump
+bind --preset --mode visual T backward-jump-till
+bind --preset --mode visual \; repeat-jump
+bind --preset --mode visual comma repeat-jump-reverse
+bind --preset --mode visual \$ end-of-line
+bind --preset --mode visual g,\$ end-of-line
+bind --preset --mode visual ^ beginning-of-line
+bind --preset --mode visual 0 beginning-of-line
+bind --preset --mode visual g,^ beginning-of-line
+bind --preset --mode visual _ beginning-of-line
+
+bind --preset --mode visual --sets-mode default v end-selection repaint-mode
+bind --preset --mode visual --sets-mode insert i end-selection repaint-mode
+bind --preset --mode visual --sets-mode insert I end-selection beginning-of-line repaint-mode
+bind --preset --mode visual --sets-mode insert c kill-selection end-selection repaint-mode
+bind --preset --mode visual --sets-mode insert s kill-selection end-selection repaint-mode
+bind --preset --mode visual --sets-mode default d kill-selection end-selection backward-char repaint-mode
+bind --preset --mode visual --sets-mode default x kill-selection end-selection repaint-mode
+bind --preset --mode visual --sets-mode default X kill-whole-line end-selection repaint-mode
+bind --preset --mode visual --sets-mode default y fish_vi_yank_selection end-selection repaint-mode
+bind --preset --mode visual --sets-mode default \",\*,y 'fish_clipboard_copy; commandline -f end-selection repaint-mode'
+bind --preset --mode visual --sets-mode default \",+,y 'fish_clipboard_copy; commandline -f end-selection repaint-mode'
+bind --preset --mode visual --sets-mode default \~ togglecase-selection end-selection repaint-mode
+bind --preset --mode visual --sets-mode default g,u downcase-selection end-selection repaint-mode
+bind --preset --mode visual --sets-mode default g,U upcase-selection end-selection repaint-mode
+bind --preset --mode visual \# __fish_toggle_comment_commandline
diff --git a/modules/by-name/fi/fish/config/keybindings/04_replace.fish b/modules/by-name/fi/fish/config/keybindings/04_replace.fish
new file mode 100644
index 00000000..95de4cc0
--- /dev/null
+++ b/modules/by-name/fi/fish/config/keybindings/04_replace.fish
@@ -0,0 +1,9 @@
+bind --preset --mode replace '' delete-char self-insert
+bind --preset --mode replace --sets-mode insert enter execute repaint-mode
+bind --preset --mode replace --sets-mode insert ctrl-j execute repaint-mode
+bind --preset --mode replace --sets-mode insert ctrl-m execute repaint-mode
+bind --preset --mode replace --sets-mode default escape cancel repaint-mode
+bind --preset --mode replace --sets-mode default ctrl-\[ cancel repaint-mode
+bind --preset --mode replace backspace backward-char
+bind --preset --mode replace shift-backspace backward-char
+bind --preset --mode replace \# __fish_toggle_comment_commandline
diff --git a/modules/by-name/fi/fish/config/keybindings/05_operator.fish b/modules/by-name/fi/fish/config/keybindings/05_operator.fish
new file mode 100644
index 00000000..67f84d82
--- /dev/null
+++ b/modules/by-name/fi/fish/config/keybindings/05_operator.fish
@@ -0,0 +1,33 @@
+bind --preset --mode operator \; 'fish_vi_exec_motion repeat-jump'
+bind --preset --mode operator comma 'fish_vi_exec_motion repeat-jump-reverse'
+bind --preset --mode operator d 'fish_vi_exec_motion --linewise'
+bind --preset --mode operator c 'fish_vi_exec_motion --linewise'
+bind --preset --mode operator y 'fish_vi_exec_motion --linewise'
+bind --preset --mode operator \~ 'fish_vi_exec_motion --linewise'
+
+bind --preset --mode operator escape fish_vi_operator_cancel
+bind --preset --mode operator ctrl-\[ fish_vi_operator_cancel
+bind --preset --mode operator 1 'fish_vi_arg_digit 1'
+bind --preset --mode operator 2 'fish_vi_arg_digit 2'
+bind --preset --mode operator 3 'fish_vi_arg_digit 3'
+bind --preset --mode operator 4 'fish_vi_arg_digit 4'
+bind --preset --mode operator 5 'fish_vi_arg_digit 5'
+bind --preset --mode operator 6 'fish_vi_arg_digit 6'
+bind --preset --mode operator 7 'fish_vi_arg_digit 7'
+bind --preset --mode operator 8 'fish_vi_arg_digit 8'
+bind --preset --mode operator 9 'fish_vi_arg_digit 9'
+bind --preset --mode operator 0 'fish_vi_exec_motion beginning-of-line'
+bind --preset --mode operator h 'fish_vi_exec_motion backward-char'
+bind --preset --mode operator l 'fish_vi_exec_motion forward-char'
+bind --preset --mode operator k 'fish_vi_exec_motion up-line'
+bind --preset --mode operator j 'fish_vi_exec_motion down-line'
+bind --preset --mode operator b 'fish_vi_exec_motion backward-word'
+bind --preset --mode operator B 'fish_vi_exec_motion backward-bigword'
+bind --preset --mode operator g,e 'fish_vi_exec_motion backward-word-end'
+bind --preset --mode operator g,E 'fish_vi_exec_motion backward-bigword-end'
+bind --preset --mode operator w 'fish_vi_exec_motion forward-word-vi'
+bind --preset --mode operator W 'fish_vi_exec_motion forward-bigword-vi'
+bind --preset --mode operator e 'fish_vi_exec_motion forward-word-end'
+bind --preset --mode operator E 'fish_vi_exec_motion forward-bigword-end'
+bind --preset --mode operator ^ 'fish_vi_exec_motion beginning-of-line'
+bind --preset --mode operator \$ 'fish_vi_exec_motion end-of-line'
diff --git a/modules/by-name/fi/fish/config/keybindings/06_replace_one.fish b/modules/by-name/fi/fish/config/keybindings/06_replace_one.fish
new file mode 100644
index 00000000..0d2672b4
--- /dev/null
+++ b/modules/by-name/fi/fish/config/keybindings/06_replace_one.fish
@@ -0,0 +1,7 @@
+bind --preset --mode replace_one --sets-mode default '' 'set -g fish_cursor_end_mode exclusive' delete-char self-insert backward-char repaint-mode 'set -g fish_cursor_end_mode inclusive'
+bind --preset --mode replace_one --sets-mode default enter 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \\n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
+bind --preset --mode replace_one --sets-mode default ctrl-j 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \\n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
+bind --preset --mode replace_one --sets-mode default ctrl-m 'set -g fish_cursor_end_mode exclusive' 'commandline -f delete-char; commandline -i \\n; commandline -f backward-char' repaint-mode 'set -g fish_cursor_end_mode inclusive'
+bind --preset --mode replace_one --sets-mode default escape cancel repaint-mode
+bind --preset --mode replace_one --sets-mode default ctrl-\[ cancel repaint-mode
+
diff --git a/modules/by-name/fi/fish/config/keybindings/07_others.fish b/modules/by-name/fi/fish/config/keybindings/07_others.fish
new file mode 100644
index 00000000..99679857
--- /dev/null
+++ b/modules/by-name/fi/fish/config/keybindings/07_others.fish
@@ -0,0 +1,9 @@
+bind --preset --mode operator --sets-mode f f ''
+bind --preset --mode operator --sets-mode F F ''
+bind --preset --mode operator --sets-mode t t ''
+bind --preset --mode operator --sets-mode T T ''
+
+bind --preset --mode f '' get-key 'fish_vi_exec_motion commandline --forward-jump=$fish_key' 'set -eg fish_key'
+bind --preset --mode F '' get-key 'fish_vi_exec_motion commandline --backward-jump=$fish_key' 'set -eg fish_key'
+bind --preset --mode t '' get-key 'fish_vi_exec_motion commandline --forward-jump-till=$fish_key' 'set -eg fish_key'
+bind --preset --mode T '' get-key 'fish_vi_exec_motion commandline --backward-jump-till=$fish_key' 'set -eg fish_key'
diff --git a/modules/by-name/fi/fish/config/keybindings/08__end.fish b/modules/by-name/fi/fish/config/keybindings/08__end.fish
new file mode 100644
index 00000000..20efad65
--- /dev/null
+++ b/modules/by-name/fi/fish/config/keybindings/08__end.fish
@@ -0,0 +1 @@
+fish_vi_key_bindings
diff --git a/modules/by-name/fi/fish/module.nix b/modules/by-name/fi/fish/module.nix
new file mode 100644
index 00000000..590df8b9
--- /dev/null
+++ b/modules/by-name/fi/fish/module.nix
@@ -0,0 +1,85 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}: let
+ cfg = config.soispha.programs.fish;
+
+ sourceFile = path: "source ${path}\n";
+
+ extraFiles = builtins.concatStringsSep "\n" (
+ builtins.map (value:
+ if builtins.isPath value
+ then (sourceFile value)
+ else value) (
+ builtins.attrValues cfg.integrations
+ )
+ );
+in {
+ options.soispha.programs.fish = {
+ enable = lib.mkEnableOption "fish";
+
+ integrations = lib.mkOption {
+ type = lib.types.attrsOf (lib.types.either lib.types.path lib.types.str);
+ example = ''
+ {
+ atuin = ./integrations/atuin.fish;
+ }
+ '';
+ default = {};
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ # Ensure that the default shell of the user is actually enabled.
+ programs.fish = {
+ enable = true;
+ package = pkgs.fish-patched;
+ };
+
+ home-manager.users.soispha = {
+ home.sessionPath = lib.mkForce [];
+
+ programs.fish = {
+ enable = true;
+
+ package = config.programs.fish.package;
+
+ interactiveShellInit = let
+ start = lib.modules.mkBefore (
+ sourceFile ./config/01_show-task.fish
+ );
+ end = lib.modules.mkAfter (
+ sourceFile ./config/keybindings/00__start.fish
+ + sourceFile ./config/keybindings/01_insert.fish
+ + sourceFile ./config/keybindings/02_command.fish
+ + sourceFile ./config/keybindings/03_visual.fish
+ + sourceFile ./config/keybindings/04_replace.fish
+ + sourceFile ./config/keybindings/05_operator.fish
+ + sourceFile ./config/keybindings/06_replace_one.fish
+ + sourceFile ./config/keybindings/07_others.fish
+ + sourceFile ./config/keybindings/08__end.fish
+ );
+ in
+ lib.modules.mkMerge
+ [
+ start
+
+ extraFiles
+
+ end
+ ];
+ };
+ };
+ };
+}
diff --git a/modules/by-name/fo/fonts/module.nix b/modules/by-name/fo/fonts/module.nix
index 4bf9a612..f05cae5a 100644
--- a/modules/by-name/fo/fonts/module.nix
+++ b/modules/by-name/fo/fonts/module.nix
@@ -20,11 +20,12 @@ in {
fonts = lib.mkOption {
type = lib.types.listOf lib.types.package;
- example = lib.literalExpression '' [
+ example = lib.literalExpression ''
+ [
pkgs.nerd-fonts.source-code-pro
pkgs.nerd-fonts.overpass
pkgs.nerd-fonts.fira-code
- ]'';
+ ]'';
default = [
pkgs.nerd-fonts.sauce-code-pro
pkgs.nerd-fonts.overpass
diff --git a/modules/by-name/fo/foot/module.nix b/modules/by-name/fo/foot/module.nix
new file mode 100644
index 00000000..8d5cfe2b
--- /dev/null
+++ b/modules/by-name/fo/foot/module.nix
@@ -0,0 +1,75 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ config,
+ lib,
+ libraries,
+ pkgs,
+ ...
+}: let
+ cfg = config.soispha.foot;
+in {
+ options.soispha.foot = {
+ enable = libraries.base.options.mkEnable "foot terminal";
+ };
+
+ config = lib.mkIf cfg.enable {
+ # TODO: Maybe we can use the xdg-autostart mechanism for this? <2026-06-23>
+ soispha.programs.river.init.autoStart = [
+ ["${lib.getExe' pkgs.foot "footclient"}"]
+ ];
+
+ home-manager.users.soispha = {
+ systemd.user.services.foot = {
+ # Don't restart the foot server (otherwise all my open foot terminals would exit
+ # too)
+ Unit.X-SwitchMethod = "keep-old";
+
+ # TODO: This should probably be added <2026-07-02>
+ # OOMScoreAdjust=-100;
+ };
+
+ programs.foot = {
+ enable = true;
+ server.enable = true;
+ settings = {
+ main = {
+ include = "${./theme.ini}";
+ font = "SauceCodePro Nerd Font Mono:size=12";
+ letter-spacing = "-0.25";
+ };
+ "regex:hashes" = {
+ regex = "([a-fA-F0-9]{7,128})";
+ launch = "git show \${match}";
+ };
+ "regex:paths" = {
+ regex = "([^ '\"`=:\\\\[\\\\(]*/[^/: '\"`\\\\)\\\\]*)";
+ launch = "ll \${match}";
+ };
+
+ colors-dark = {
+ jump-labels = "f2f4f8 484848";
+ };
+
+ key-bindings = {
+ regex-launch = [
+ "[hashes] Control+h"
+ "[paths] Control+t"
+ ];
+ regex-copy = [
+ "[hashes] Control+Shift+h"
+ "[paths] Control+Shift+t"
+ ];
+ };
+ };
+ };
+ };
+ };
+}
diff --git a/modules/by-name/fo/foot/theme.ini b/modules/by-name/fo/foot/theme.ini
new file mode 100644
index 00000000..f5f36039
--- /dev/null
+++ b/modules/by-name/fo/foot/theme.ini
@@ -0,0 +1,40 @@
+# From https://github.com/mbadolato/iTerm2-Color-Schemes/blob/75bc70670c28b5dc97625af38deb3ae49f4363e6/foot/Carbonfox.ini
+# Other themes I considered:
+# - ayu-mirage
+# - catppuccin-mocha
+# - chiba-dark
+# - iterm
+# - kitty
+# - modus-vivendi
+# - modus-vivendi-tinted
+# - molokai
+# - tokyonight-storm
+# - visibone
+# - xterm
+# - poimandres
+# -*- conf -*-
+# VisiBone
+
+[colors-dark]
+cursor=161616 f2f4f8
+foreground=f2f4f8
+background=161616
+regular0=282828
+regular1=ee5396
+regular2=25be6a
+regular3=08bdba
+regular4=78a9ff
+regular5=be95ff
+regular6=33b1ff
+regular7=dfdfe0
+bright0=484848
+bright1=f16da6
+bright2=46c880
+bright3=2dc7c4
+bright4=8cb6ff
+bright5=c8a5ff
+bright6=52bdff
+bright7=e4e4e5
+selection-foreground=f2f4f8
+selection-background=2a2a2a
+
diff --git a/modules/by-name/gi/git/aliases.nix b/modules/by-name/gi/git/aliases.nix
index bc30ea40..948ec131 100644
--- a/modules/by-name/gi/git/aliases.nix
+++ b/modules/by-name/gi/git/aliases.nix
@@ -29,20 +29,11 @@
lg2 = "lg2-specific --all";
lg3 = "lg3-specific --all";
- lg1-specific = "log --graph --abbrev-commit --decorate \
- --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold cyan) [%G?]%C(reset)%C(auto)%d%C(reset)'";
+ lg1-specific = "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold cyan) [%G?]%C(reset)%C(auto)%d%C(reset)'";
- lg2-specific = "log --graph --abbrev-commit --decorate \
- --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(auto)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'";
+ lg2-specific = "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(auto)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'";
- lg3-specific = "log --graph --abbrev-commit --decorate \
- --format=format:'%C(bold blue)%h%C(reset)\
- - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)\
- %C(bold cyan)(committed: %cD)%C(reset) %C(auto)%d%C(reset)\
- %C(bold red)(signature: %G? by %GS, trust: %GT)%C(reset)%n\
- '' %C(white)%s%C(reset)%n''\
- %C(dim white)- %an <%ae> %C(reset) %C(dim white)(committer: %cn <%ce>)%C(reset)\
- %C(bold white)- trailers: %(trailers) %C(reset)'";
+ lg3-specific = "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset) %C(bold cyan)(committed: %cD)%C(reset) %C(auto)%d%C(reset) %C(bold red)(signature: %G? by %GS, trust: %GT)%C(reset)%n '' %C(white)%s%C(reset)%n'' %C(dim white)- %an <%ae> %C(reset) %C(dim white)(committer: %cn <%ce>)%C(reset) %C(bold white)- trailers: %(trailers) %C(reset)'";
# hard reset with commit before (use reflog to recover)
# git wipe [<commit>]
diff --git a/modules/by-name/gi/git/git_ignore.git b/modules/by-name/gi/git/git_ignore.git
index 8f29815e..9a92db69 100644
--- a/modules/by-name/gi/git/git_ignore.git
+++ b/modules/by-name/gi/git/git_ignore.git
@@ -1,4 +1,7 @@
# default nvim Session file name
Session.vim
+# The .direnv file for pre-cache devshells
+.nenvrc
+
# vim: ft=gitignore
diff --git a/modules/by-name/gi/git/module.nix b/modules/by-name/gi/git/module.nix
index 77cfce54..28e61ecb 100644
--- a/modules/by-name/gi/git/module.nix
+++ b/modules/by-name/gi/git/module.nix
@@ -9,6 +9,7 @@
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
{
lib,
+ pkgs,
config,
...
}: let
@@ -43,9 +44,19 @@ in {
};
};
+ # Add my custom git-scripts
+ home.packages = [
+ pkgs.git-edit-index # Allows you to edit the indexed version of a file
+ pkgs.git-cm # A wrapper that re-adds the last commit's subject
+ pkgs.git-cgit # Allows fast cgit settings setup
+ pkgs.stamp # Add a license header to a file
+ ];
+
programs.git = {
enable = true;
- #package = pkgs.gitAndTools.gitFull; # TODO: for git send-email support
+
+ signing.format = "openpgp";
+
settings = {
alias = import ./aliases.nix {
inherit lib;
diff --git a/modules/by-name/gp/gpg/module.nix b/modules/by-name/gp/gpg/module.nix
index d5136e92..9be4cada 100644
--- a/modules/by-name/gp/gpg/module.nix
+++ b/modules/by-name/gp/gpg/module.nix
@@ -22,7 +22,47 @@ in {
};
config = lib.mkIf cfg.enable {
- soispha.programs.qutebrowser.key = "8321 ED3A 8DB9 99A5 1F3B F80F F268 2914 EA42 DE26";
+ soispha = {
+ programs = {
+ qutebrowser.key = "8321 ED3A 8DB9 99A5 1F3B F80F F268 2914 EA42 DE26";
+
+ # fish.integrations.gpg =
+ # # fish
+ # ''
+ # set --export --global GPG_TTY $(tty)
+ #
+ # # Ensure that get gpg agent is started (necessary because ssh does not start it
+ # # automatically and has it's tty updated)
+ # gpg-connect-agent updatestartuptty /bye
+ # '';
+
+ zsh.integrations.gpg =
+ # bash
+ ''
+ export GPG_TTY=$(tty)
+
+ # Magic copied from the gpg-agent manual
+ unset SSH_AGENT_PID
+ if [ "''${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then
+ export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
+ fi
+
+
+ # Ensure that get gpg agent is started (necessary because ssh does not start it
+ # automatically and has it's tty updated)
+ gpg-connect-agent /bye
+ '';
+ };
+
+ services.unison.pathsToIgnore = [
+ # "Unknown filetype" (and there is no real reason to (try to) synchronize sockets)
+ "~/.local/share/gnupg/S.gpg-agent"
+ "~/.local/share/gnupg/S.gpg-agent.browser"
+ "~/.local/share/gnupg/S.gpg-agent.extra"
+ "~/.local/share/gnupg/S.gpg-agent.ssh"
+ "~/.local/share/gnupg/S.scdaemon"
+ ];
+ };
home-manager.users.soispha = {
programs.gpg = {
@@ -51,7 +91,8 @@ in {
services = {
gpg-agent = {
enable = true;
- enableZshIntegration = true;
+ enableZshIntegration = false;
+ enableFishIntegration = true;
enableScDaemon = true; # smartcards and such things
# Cache the key passwords
@@ -74,20 +115,5 @@ in {
};
};
};
-
- soispha.programs.zsh.integrations.gpg = ''
- export GPG_TTY=$(tty)
-
- # Magic copied from the gpg-agent manual
- unset SSH_AGENT_PID
- if [ "''${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]; then
- export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
- fi
-
-
- # Ensure that get gpg agent is started (necessary because ssh does not start it
- # automatically and has it's tty updated)
- gpg-connect-agent /bye
- '';
};
}
diff --git a/modules/by-name/gt/gtk/module.nix b/modules/by-name/gt/gtk/module.nix
new file mode 100644
index 00000000..44442077
--- /dev/null
+++ b/modules/by-name/gt/gtk/module.nix
@@ -0,0 +1,82 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ config,
+ pkgs,
+ lib,
+ packageSets,
+ libraries,
+ ...
+}: let
+ cfg = config.soispha.gtk;
+
+ homeConfig = config.home-manager.users.soispha;
+in {
+ options.soispha.gtk = {
+ enable = libraries.base.options.mkEnable "gtk themes";
+ };
+
+ config.home-manager.users.soispha = lib.mkIf cfg.enable {
+ gtk = {
+ enable = true;
+ theme = {
+ # NOTE: The `nordic` package pulls the whole plasma shell, to copy some stuff from a plasma theme :/ <2026-05-29>
+ # NOTE: Unstable removed the nordic theme, as it also supported gtk2, which is
+ # insecure. So we use stable until unstable adds it again. <2026-08-13>
+ package = packageSets.stable.nordic;
+ name = "Nordic";
+ };
+ cursorTheme = {
+ package = pkgs.nordzy-cursor-theme;
+ name = "Nordzy-cursors";
+ };
+ iconTheme = {
+ package = pkgs.nordzy-icon-theme;
+ name = "Nordzy-icon";
+ };
+ font = {
+ name = "Noto Sans";
+ size = 10;
+ };
+ gtk2 = {
+ configLocation = "${homeConfig.xdg.configHome}/gtk-2.0/gtkrc";
+ # extraConfig = ''
+ # gtk-enable-animations=1
+ # gtk-primary-button-warps-slider=0
+ # gtk-toolbar-style=3
+ # gtk-menu-images=1
+ # gtk-button-images=1
+ #
+ # '';
+ };
+ gtk3 = {
+ # extraConfig = ''
+ # gtk-button-images=true
+ # gtk-decoration-layout=icon:minimize,maximize,close
+ # gtk-enable-animations=true
+ # gtk-menu-images=true
+ # gtk-primary-button-warps-slider=false
+ # gtk-toolbar-style=3
+ # #gtk-modules=window-decorations-gtk-module:colorreload-gtk-module
+ # '';
+ extraCss = "";
+ };
+ gtk4 = {
+ theme = homeConfig.gtk.theme;
+ # extraConfig = ''
+ # gtk-application-prefer-dark-theme=true
+ # gtk-decoration-layout=icon:minimize,maximize,close
+ # gtk-enable-animations=true
+ # gtk-primary-button-warps-slider=false
+ # '';
+ };
+ };
+ };
+}
diff --git a/modules/by-name/i3/i3bar-river/module.nix b/modules/by-name/i3/i3bar-river/module.nix
index 8a2203aa..55ac59e9 100644
--- a/modules/by-name/i3/i3bar-river/module.nix
+++ b/modules/by-name/i3/i3bar-river/module.nix
@@ -110,7 +110,26 @@ in {
};
config = lib.mkIf cfg.enable {
- soispha.programs.river.init.backgroundStart = [cfg.package];
+ systemd.user.services."i3bar-river" = {
+ description = "Highly customizable Wayland bar for river";
+ partOf = ["graphical-session.target"];
+ after = ["graphical-session.target"];
+ requisite = ["graphical-session.target"];
+
+ path = [
+ pkgs.bash # `sh` is needed for starting the status command
+
+ # TODO: This should be a wrapper of the status command <2026-06-23>
+ pkgs.btrfs-progs # `btrfs` is needed by the storage block in the status command
+ ];
+
+ serviceConfig = {
+ ExecStart = "${lib.getExe cfg.package}";
+ ExecReload = "kill -SIGUSR2 $MAINPID";
+ Restart = "on-failure";
+ };
+ wantedBy = ["graphical-session.target"];
+ };
home-manager.users.soispha = {
programs.i3bar-river = {
@@ -167,7 +186,7 @@ in {
};
# The font and various sizes
- font = "SauceCodePro Nerd Font Mono:pixelsize=26";
+ font = "SauceCodePro NFM:pixelsize=26";
height = 24;
margin_top = 10;
margin_bottom = 0;
diff --git a/modules/by-name/i3/i3status-rust/module.nix b/modules/by-name/i3/i3status-rust/module.nix
index 48bcd5de..1e247100 100644
--- a/modules/by-name/i3/i3status-rust/module.nix
+++ b/modules/by-name/i3/i3status-rust/module.nix
@@ -18,7 +18,7 @@ in {
options.soispha.programs.i3status-rust = {
enable = lib.mkEnableOption "i3status-rust";
- package = lib.mkPackageOption pkgs "i3status-rust-patched" {};
+ package = lib.mkPackageOption pkgs "i3status-rust" {};
};
config = lib.mkIf cfg.enable {
@@ -63,7 +63,7 @@ in {
block = "custom";
interval = "once";
persistent = true;
- command = mkScript "mpd_song_name.sh" [pkgs.mpc pkgs.coreutils];
+ command = mkScript "mpd_song_name.sh" [pkgs.mpc pkgs.mpdpopm pkgs.coreutils];
hide_when_empty = true;
shell = "${lib.getExe pkgs.dash}";
format = " $text.str(max_width:60,rot_interval:0.5) ";
diff --git a/modules/by-name/i3/i3status-rust/scripts/mpd_song_name.sh b/modules/by-name/i3/i3status-rust/scripts/mpd_song_name.sh
index 28921520..edd5da2c 100755
--- a/modules/by-name/i3/i3status-rust/scripts/mpd_song_name.sh
+++ b/modules/by-name/i3/i3status-rust/scripts/mpd_song_name.sh
@@ -16,8 +16,14 @@ while true; do
if [ "$state" = "playing" ]; then
song="$(mpc --format '[[%artist% - ]%title%]|[%file%]' current)"
progress="$(mpc status "%currenttime%/%totaltime%")"
+ base_rating="$(mpdpopm rating get)"
- echo "$song :: $progress"
+ rating=""
+ if [ "$base_rating" != 0 ]; then
+ rating=" ($base_rating)"
+ fi
+
+ echo "$song :: ${progress}${rating}"
else
# The song has stopped, we are done displaying it.
echo ""
diff --git a/modules/by-name/im/impermanence/module.nix b/modules/by-name/im/impermanence/module.nix
index 78ad570f..43943363 100644
--- a/modules/by-name/im/impermanence/module.nix
+++ b/modules/by-name/im/impermanence/module.nix
@@ -59,22 +59,24 @@ in {
inherit (cfg) directories;
users.soispha = {
- directories = [
- # TODO: These should all be moved to their respective modules <2024-12-08>
- ".local/share"
+ directories =
+ [
+ # TODO: These should all be moved to their respective modules <2024-12-08>
+ ".local/share"
- ".local/state/nvim"
- ".local/state/mpv"
- ".local/state/wireplumber"
+ ".local/state/nvim"
+ ".local/state/mpv"
+ ".local/state/wireplumber"
- ".config/Signal"
+ ".config/Signal"
- ".cache"
+ ".cache"
- "media"
- "repos"
- "documents"
- ] ++ cfg.userDirectories;
+ "media"
+ "repos"
+ "documents"
+ ]
+ ++ cfg.userDirectories;
};
files = [
diff --git a/modules/by-name/lf/lf/colors b/modules/by-name/lf/lf/colors
index 17201e64..ed211581 100644
--- a/modules/by-name/lf/lf/colors
+++ b/modules/by-name/lf/lf/colors
@@ -58,7 +58,7 @@ ex 03;01;95 # EXEC
fi 00 # FILE
# links to hide
-~/.mozilla 01;08;30
+~/.pki 01;08;30
~/.ssh 01;08;30
# archives or compressed (dircolors defaults)
diff --git a/modules/by-name/lf/lf/commands/base.sh b/modules/by-name/lf/lf/commands/base.sh
index 7003d76c..e0b6216c 100755
--- a/modules/by-name/lf/lf/commands/base.sh
+++ b/modules/by-name/lf/lf/commands/base.sh
@@ -1,4 +1,4 @@
-#!/usr/bin/env sh
+#!/usr/bin/env bash
# nixos-config - My current NixOS configuration
#
@@ -41,7 +41,7 @@ prompt() {
# set -- "$@" "$file"
# done < "$(echo "$fx" | tmp)"
tmp() {
- __base_tmp_temporary_file="$(mktemp -t --tmpdir="$__base_tmp_temporary_directory" lf_commands_tmp_fun_XXXXXXXX )"
+ __base_tmp_temporary_file="$(mktemp -t --tmpdir="$__base_tmp_temporary_directory" lf_commands_tmp_fun_XXXXXXXX)"
cat >"$__base_tmp_temporary_file"
echo "$__base_tmp_temporary_file"
}
@@ -50,7 +50,6 @@ trap 'rm --recursive "$__base_tmp_temporary_directory"' EXIT
# Run a lf command on the current lf client
# All arguments will run in like they were typed directly into lf.
-# # TODO(@bpeetz): Escape the single quotes in the input arguments. <2025-02-02>
#
# # Type
# lf_cmd :: [String]
@@ -61,6 +60,11 @@ trap 'rm --recursive "$__base_tmp_temporary_directory"' EXIT
lf_cmd() {
arguments=""
for arg in "$@"; do
+ # We _can_ use the fancy bash syntax, but it's really hard to correctly quote the
+ # single quote in bash, and I don't really feel like it's worth it.
+ # shellcheck disable=SC2001
+ arg="$(echo "$arg" | sed "s/'/\\\'/g")"
+
if [ -z "$arguments" ]; then
arguments="'$arg'"
else
diff --git a/modules/by-name/lf/lf/commands/default.nix b/modules/by-name/lf/lf/commands/default.nix
index 055bfa2c..a6c4a3f9 100644
--- a/modules/by-name/lf/lf/commands/default.nix
+++ b/modules/by-name/lf/lf/commands/default.nix
@@ -16,11 +16,24 @@
pkgs.writeShellApplication {
inherit name;
text = builtins.readFile ./base.sh + builtins.readFile ./scripts/${name}.sh;
- runtimeInputs = [pkgs.lf pkgs.mktemp pkgs.coreutils] ++ dependencies;
+ runtimeInputs =
+ [
+ pkgs.lf
+ pkgs.mktemp
+ pkgs.coreutils
+ pkgs.gnused
+ ]
+ ++ dependencies;
inheritPath = keepPath;
}
+ "/bin/${name}";
+ syscmd = command: ''
+ ''${{
+ printf "%s\n" "SYSCMD::${command}" >>"$LL_SYSCMD_SOCKET"
+ }}
+ '';
+
# closes the lf tui
shell = args: ''
''${{
@@ -54,14 +67,17 @@ in {
pkgs.xz
pkgs.p7zip
pkgs.zip
+ pkgs.gzip
];
};
archive_decompress = pipe {
name = "archive_decompress";
dependencies = [
pkgs.gnutar
- pkgs.unzip
+ pkgs.openjdk_headless
pkgs.p7zip
+ pkgs.xz
+ pkgs.gzip
];
};
@@ -69,6 +85,12 @@ in {
name = "cd_project_root";
dependencies = [pkgs.git];
};
+ cd_lf_make_map = shell {
+ name = "cd_lf_make_map";
+ dependencies = [
+ pkgs.lf-make-map
+ ];
+ };
chmod = pipe {
name = "chmod";
@@ -129,6 +151,9 @@ in {
];
};
+ syscmd_direnv_sandbox_enable = syscmd "DIRENV::SANDBOX::ENABLE";
+ syscmd_direnv_sandbox_disable = syscmd "DIRENV::SANDBOX::DISABLE";
+
stripspace = pipe {
name = "stripspace";
dependencies = [pkgs.gnused];
@@ -153,9 +178,6 @@ in {
dependencies = [
pkgs.conceal
pkgs.fzf
- pkgs.gawk
- pkgs.trashy
- pkgs.gnused
];
};
diff --git a/modules/by-name/lf/lf/commands/scripts/archive_decompress.sh b/modules/by-name/lf/lf/commands/scripts/archive_decompress.sh
index 11d634b1..10eb0bc5 100755
--- a/modules/by-name/lf/lf/commands/scripts/archive_decompress.sh
+++ b/modules/by-name/lf/lf/commands/scripts/archive_decompress.sh
@@ -25,9 +25,12 @@ echo "$fx" | while read -r file; do
*.tar.gz | *.tgz) tar --extract --gzip --verbose --file="$file" ;;
*.tar.xz | *.txz) tar --extract --xz --verbose --file="$file" ;;
*.tar*) tar --extract --verbose --file="$file" ;;
- *.zip) unzip "$file" ;;
+ *.zip) jar --verbose --extract --file "$file" ;;
*.7z) 7z x "$file" ;;
*) die "'$file' is not a supported file for unarchiving." ;;
esac
+
+ lf_cmd echo "decompression of '$file' done."
done
+
# vim: ft=sh
diff --git a/modules/by-name/lf/lf/commands/scripts/cd_lf_make_map.sh b/modules/by-name/lf/lf/commands/scripts/cd_lf_make_map.sh
new file mode 100755
index 00000000..00befd3f
--- /dev/null
+++ b/modules/by-name/lf/lf/commands/scripts/cd_lf_make_map.sh
@@ -0,0 +1,27 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+# shellcheck shell=sh
+
+# shellcheck disable=SC2269
+f="$f"
+# shellcheck disable=SC2269
+fx="$fx"
+# shellcheck disable=SC2269
+fs="$fs"
+# shellcheck disable=SC2269
+id="$id"
+
+root="$(lf-make-map --depth 4 interactive ~/media ~/repos ~/documents ~/.config ~/.local)"
+if [ "$root" ]; then
+ lf_cmd cd "$root" || die "Bug: Failed to cd to selected path at '$root'"
+fi
+
+# vim: ft=sh
diff --git a/modules/by-name/lf/lf/commands/scripts/trash_restore.sh b/modules/by-name/lf/lf/commands/scripts/trash_restore.sh
index 7b078c26..789ab59a 100755
--- a/modules/by-name/lf/lf/commands/scripts/trash_restore.sh
+++ b/modules/by-name/lf/lf/commands/scripts/trash_restore.sh
@@ -19,9 +19,5 @@ fs="$fs"
# shellcheck disable=SC2269
id="$id"
-while IFS="$(printf '\n')" read -r file; do
- set -- "$@" "$(pwd)/$file"
-done <"$(conceal list | fzf --multi --ansi | awk '{$1="";$2=""; print $0}' | sed 's/^\s*//' | tmp)"
-
-[ "$#" -ne 0 ] && trash restore --match=exact "$@"
+conceal restore --finder=fzf
# vim: ft=sh
diff --git a/modules/by-name/lf/lf/ctpv/helpers.sh b/modules/by-name/lf/lf/ctpv/helpers.sh
index 71524082..d0bba315 100644
--- a/modules/by-name/lf/lf/ctpv/helpers.sh
+++ b/modules/by-name/lf/lf/ctpv/helpers.sh
@@ -31,6 +31,7 @@ chafa_run() {
# TODO: Animation support would be nice, but it would need to be supported in chafa by
# allowing us to make a call to ctpvclear after each image is displayed. <2024-11-25>
chafa --size "${w}x${h}" --animate=off "$1" | sed 's/#/\n#/g'
+ exiftool "$1"
}
send_image() {
diff --git a/modules/by-name/lf/lf/ctpv/prev/application/archive/default.nix b/modules/by-name/lf/lf/ctpv/prev/application/archive/default.nix
index aee5c732..a5fc2c50 100644
--- a/modules/by-name/lf/lf/ctpv/prev/application/archive/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/application/archive/default.nix
@@ -14,14 +14,19 @@
matches.mime = [
"application/gzip"
"application/java-archive"
+ "application/vnd.android.package-archive"
"application/vnd.debian.binary-package"
"application/x-7z-compressed"
+ "application/x-archive"
"application/x-bzip2"
+ "application/x-cpio"
+ "application/x-lz4"
"application/x-rar"
"application/x-tar"
"application/x-xz"
"application/zip"
"application/zlib"
+ "application/zstd"
];
matches.extension = [
@@ -89,6 +94,10 @@
pkgs.xz
pkgs.zip
+ pkgs.cabextract
+
+ pkgs.coreutils
+
# Unfree stuff
# pkgs.lha
# pkgs.rar
diff --git a/modules/by-name/lf/lf/ctpv/prev/application/epub/default.nix b/modules/by-name/lf/lf/ctpv/prev/application/epub/default.nix
index d28503e2..43e683a9 100644
--- a/modules/by-name/lf/lf/ctpv/prev/application/epub/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/application/epub/default.nix
@@ -18,6 +18,7 @@
pkgs.bk
pkgs.epub-thumbnailer
pkgs.chafa
+ pkgs.exiftool
pkgs.gnused
];
};
diff --git a/modules/by-name/lf/lf/ctpv/prev/application/pdf/default.nix b/modules/by-name/lf/lf/ctpv/prev/application/pdf/default.nix
index bc8abf8f..cb96d7a9 100644
--- a/modules/by-name/lf/lf/ctpv/prev/application/pdf/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/application/pdf/default.nix
@@ -16,6 +16,7 @@
dependencies = [
pkgs.poppler-utils # for `pdftoppm`
pkgs.chafa
+ pkgs.exiftool
pkgs.gnused
pkgs.coreutils
];
diff --git a/modules/by-name/lf/lf/ctpv/prev/application/x-pem-file/default.nix b/modules/by-name/lf/lf/ctpv/prev/application/x-pem-file/default.nix
index fb82aa8a..28b69890 100644
--- a/modules/by-name/lf/lf/ctpv/prev/application/x-pem-file/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/application/x-pem-file/default.nix
@@ -11,7 +11,9 @@
soispha.programs.lf.ctpv.previewers = {
pem = {
previewer = ./pem.sh;
- matches.mime = ["application/x-pem-file"];
+ matches.mime = [
+ "application/x-pem-file"
+ ];
dependencies = [
pkgs.openssl
];
diff --git a/modules/by-name/lf/lf/ctpv/prev/audio/default.nix b/modules/by-name/lf/lf/ctpv/prev/audio/default.nix
index 7ae5d7fd..b9d4ff0d 100644
--- a/modules/by-name/lf/lf/ctpv/prev/audio/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/audio/default.nix
@@ -14,9 +14,10 @@
matches.mime = ["audio/*"];
dependencies = [
pkgs.ffmpegthumbnailer
- pkgs.ffmpeg
+ pkgs.ffmpeg-headless
pkgs.chafa
+ pkgs.exiftool
pkgs.gnused
pkgs.coreutils
];
diff --git a/modules/by-name/lf/lf/ctpv/prev/font/default.nix b/modules/by-name/lf/lf/ctpv/prev/font/default.nix
index 76a284f4..e2ce2cc2 100644
--- a/modules/by-name/lf/lf/ctpv/prev/font/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/font/default.nix
@@ -14,6 +14,7 @@
matches.mime = [
"font/*"
"application/vnd.ms-opentype"
+ "application/x-font-pf2"
# TODO: This should be added (ext: 'eot') <2024-12-04>
# "application/vnd.ms-fontobject"
@@ -21,6 +22,7 @@
dependencies = [
pkgs.fontforge # for `fontimage`
pkgs.chafa
+ pkgs.exiftool
pkgs.gnused
pkgs.coreutils
];
diff --git a/modules/by-name/lf/lf/ctpv/prev/image/default.nix b/modules/by-name/lf/lf/ctpv/prev/image/default.nix
index 79037d5a..765c7c83 100644
--- a/modules/by-name/lf/lf/ctpv/prev/image/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/image/default.nix
@@ -14,6 +14,7 @@
matches.mime = ["image/*"];
dependencies = [
pkgs.chafa
+ pkgs.exiftool
pkgs.gnused
];
};
diff --git a/modules/by-name/lf/lf/ctpv/prev/image/svg+xml/default.nix b/modules/by-name/lf/lf/ctpv/prev/image/svg+xml/default.nix
index 25bd6f77..12e65337 100644
--- a/modules/by-name/lf/lf/ctpv/prev/image/svg+xml/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/image/svg+xml/default.nix
@@ -16,6 +16,7 @@
dependencies = [
pkgs.imagemagick
pkgs.chafa
+ pkgs.exiftool
pkgs.gnused
];
};
diff --git a/modules/by-name/lf/lf/ctpv/prev/inode/default.nix b/modules/by-name/lf/lf/ctpv/prev/inode/directory/default.nix
index fe5ee3e1..baf0314f 100644
--- a/modules/by-name/lf/lf/ctpv/prev/inode/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/inode/directory/default.nix
@@ -15,11 +15,5 @@
matches.mime = ["inode/directory"];
dependencies = [pkgs.coreutils];
};
- symlink = {
- previewer = ./symlink.sh;
- priority = 1;
- matches.mime = ["inode/symlink"];
- dependencies = [];
- };
};
}
diff --git a/modules/by-name/lf/lf/ctpv/prev/inode/ls.sh b/modules/by-name/lf/lf/ctpv/prev/inode/directory/ls.sh
index 73e036eb..73e036eb 100644
--- a/modules/by-name/lf/lf/ctpv/prev/inode/ls.sh
+++ b/modules/by-name/lf/lf/ctpv/prev/inode/directory/ls.sh
diff --git a/modules/by-name/lf/lf/ctpv/prev/inode/fifo/default.nix b/modules/by-name/lf/lf/ctpv/prev/inode/fifo/default.nix
new file mode 100644
index 00000000..70cb9132
--- /dev/null
+++ b/modules/by-name/lf/lf/ctpv/prev/inode/fifo/default.nix
@@ -0,0 +1,19 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{...}: {
+ soispha.programs.lf.ctpv.previewers = {
+ fifo = {
+ previewer = ./fifo.sh;
+ priority = 1;
+ matches.mime = ["inode/fifo"];
+ dependencies = [];
+ };
+ };
+}
diff --git a/modules/by-name/lf/lf/wrappers/ll/default.nix b/modules/by-name/lf/lf/ctpv/prev/inode/fifo/fifo.sh
index 6e4ee336..594d40db 100644
--- a/modules/by-name/lf/lf/wrappers/ll/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/inode/fifo/fifo.sh
@@ -1,3 +1,5 @@
+#! /usr/bin/env dash
+
# nixos-config - My current NixOS configuration
#
# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
@@ -7,12 +9,8 @@
#
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{writeShellApplication}:
-writeShellApplication {
- name = "ll";
- text = builtins.readFile ./ll.sh;
- # This is sourced in the shell
- inheritPath = true;
- bashOptions = [];
-}
+# shellcheck disable=SC2269
+f="$f"
+
+printf "<fifo no preview>\n"
diff --git a/modules/by-name/lf/lf/ctpv/prev/inode/symlink/default.nix b/modules/by-name/lf/lf/ctpv/prev/inode/symlink/default.nix
new file mode 100644
index 00000000..0c1cb286
--- /dev/null
+++ b/modules/by-name/lf/lf/ctpv/prev/inode/symlink/default.nix
@@ -0,0 +1,19 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{...}: {
+ soispha.programs.lf.ctpv.previewers = {
+ symlink = {
+ previewer = ./symlink.sh;
+ priority = 1;
+ matches.mime = ["inode/symlink"];
+ dependencies = [];
+ };
+ };
+}
diff --git a/modules/by-name/lf/lf/ctpv/prev/inode/symlink.sh b/modules/by-name/lf/lf/ctpv/prev/inode/symlink/symlink.sh
index 2629f62a..2629f62a 100644
--- a/modules/by-name/lf/lf/ctpv/prev/inode/symlink.sh
+++ b/modules/by-name/lf/lf/ctpv/prev/inode/symlink/symlink.sh
diff --git a/modules/by-name/lf/lf/ctpv/prev/inode/x-empty/default.nix b/modules/by-name/lf/lf/ctpv/prev/inode/x-empty/default.nix
new file mode 100644
index 00000000..670ad38b
--- /dev/null
+++ b/modules/by-name/lf/lf/ctpv/prev/inode/x-empty/default.nix
@@ -0,0 +1,19 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{...}: {
+ soispha.programs.lf.ctpv.previewers = {
+ empty = {
+ previewer = ./empty.sh;
+ priority = 1;
+ matches.mime = ["inode/x-empty"];
+ dependencies = [];
+ };
+ };
+}
diff --git a/modules/by-name/lf/lf/wrappers/lm/default.nix b/modules/by-name/lf/lf/ctpv/prev/inode/x-empty/empty.sh
index 42bdc687..da2cd9a9 100644
--- a/modules/by-name/lf/lf/wrappers/lm/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/inode/x-empty/empty.sh
@@ -1,3 +1,5 @@
+#! /usr/bin/env dash
+
# nixos-config - My current NixOS configuration
#
# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
@@ -7,12 +9,8 @@
#
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{writeShellApplication}:
-writeShellApplication {
- name = "lm";
- text = builtins.readFile ./lm.sh;
- # This is sourced in the shell
- inheritPath = true;
- bashOptions = [];
-}
+# shellcheck disable=SC2269
+f="$f"
+
+printf "<empty file no preview>\n"
diff --git a/modules/by-name/lf/lf/ctpv/prev/message/rfc822/default.nix b/modules/by-name/lf/lf/ctpv/prev/message/rfc822/default.nix
new file mode 100644
index 00000000..3cb0fcf5
--- /dev/null
+++ b/modules/by-name/lf/lf/ctpv/prev/message/rfc822/default.nix
@@ -0,0 +1,26 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{pkgs, ...}: {
+ soispha.programs.lf.ctpv.previewers = {
+ rfc822 = {
+ previewer = ./rfc822.sh;
+ priority = 1;
+ matches.mime = [
+ "message/rfc822"
+ "application/mbox"
+ ];
+ dependencies = [
+ pkgs.coreutils
+ # pkgs.meli
+ # pkgs.util-linux
+ ];
+ };
+ };
+}
diff --git a/modules/by-name/lf/lf/ctpv/prev/message/rfc822/rfc822.sh b/modules/by-name/lf/lf/ctpv/prev/message/rfc822/rfc822.sh
new file mode 100644
index 00000000..228e01b7
--- /dev/null
+++ b/modules/by-name/lf/lf/ctpv/prev/message/rfc822/rfc822.sh
@@ -0,0 +1,22 @@
+#! /usr/bin/env dash
+
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+# shellcheck disable=SC2269
+f="$f"
+# shellcheck disable=SC2269
+cache_f="$cache_f"
+
+# TODO: Actually use `meli`, currently it will just not produce output, because the script
+# never stops <2026-02-19>
+# hide_script_env meli --config /dev/null view "$f"
+
+cat "$f"
diff --git a/modules/by-name/lf/lf/ctpv/prev/text/default.nix b/modules/by-name/lf/lf/ctpv/prev/text/default.nix
index ca042646..868a7460 100644
--- a/modules/by-name/lf/lf/ctpv/prev/text/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/text/default.nix
@@ -24,6 +24,9 @@
matches.mime = [
"text/*"
"application/postscript"
+ "application/javascript"
+ "application/x-setupscript"
+ "application/x-wine-extension-ini"
];
dependencies = [
pkgs.bat
diff --git a/modules/by-name/lf/lf/ctpv/prev/video/default.nix b/modules/by-name/lf/lf/ctpv/prev/video/default.nix
index d500fbd9..dc51dbdf 100644
--- a/modules/by-name/lf/lf/ctpv/prev/video/default.nix
+++ b/modules/by-name/lf/lf/ctpv/prev/video/default.nix
@@ -15,6 +15,7 @@
dependencies = [
pkgs.ffmpegthumbnailer
pkgs.chafa
+ pkgs.exiftool
pkgs.gnused
];
};
diff --git a/modules/by-name/lf/lf/keybindings/default.nix b/modules/by-name/lf/lf/keybindings/default.nix
index fbc33f6f..7d90a944 100644
--- a/modules/by-name/lf/lf/keybindings/default.nix
+++ b/modules/by-name/lf/lf/keybindings/default.nix
@@ -11,54 +11,114 @@
uid,
downloadDir,
}: {
- # Remove some defaults
- "'\"'" = null;
- ";" = null;
- "\"'\"" = null;
- c = null;
- d = null;
- e = null;
- j = null;
- k = null;
- m = null;
- f = null;
+ # v o visual-change
+ # v <esc> visual-discard
+ # v o visual-change
+ # v V visual-accept
+
+ "\\!" = "shell-wait";
+ "\\$" = "shell";
+ "\\%" = "shell-pipe";
+ "\\&" = "shell-async";
+ "\\:" = "read";
+
+ "<f-1>" = "help";
+
+ "<left>" = "updir";
+ "<right>" = "open";
+ "<down>" = "down";
+ "<up>" = "up";
+
+ "<c-f>" = "page-down";
+ "<pgdn>" = "page-down";
+ "<pgup>" = "page-up";
+ "<c-b>" = "page-up";
+
+ "[" = "jump-prev";
+ "]" = "jump-next";
+ q = "quit";
# Sorting
- kn = ":set sortby natural; set info";
- ks = ":set sortby size; set info size";
- kt = ":set sortby time; set info time";
- ka = ":set sortby atime; set info atime";
- kc = ":set sortby ctime; set info ctime";
- ke = ":set sortby ext; set info";
+ k = {
+ a = ":{{ set sortby atime; set info atime; }}";
+ b = ":{{ set sortby btime; set info btime; }}";
+ c = ":{{ set sortby ctime; set info ctime; }}";
+ e = ":{{ set sortby ext; set info; }}";
+ n = ":{{ set sortby natural; set info; }}";
+ s = ":{{ set sortby size; set info size; }}";
+ t = ":{{ set sortby time; set info time; }}";
+ };
+ z = {
+ a = "set info size:time";
+ h = "set hidden!";
+ n = "set info";
+ r = "set reverse!";
+ s = "set info size";
+ t = "set info time";
+ };
# Searching
l = "search-next";
L = "search-prev";
- # File Openers
- ee = "\$\$EDITOR \"$f\"";
- es = "\$ nvim -S \"$f\"";
+ "/" = "search";
+ "?" = "search-back";
+ F = "find-back";
+ "," = "find-prev";
+
+ # File edit
+ e = {
+ e = "\${{ $EDITOR \"$f\" }}";
+ s = "\${{ nvim -S \"$f\" }}";
+ };
+
u = "view_file";
- cc = "\$sudo -e \"$f\"";
- fe = "execute";
- fl = "follow_link";
- cp = "set_clipboard_path";
+
+ f = {
+ e = "execute";
+ l = "follow_link";
+ };
+
+ c = {
+ h = "chmod";
+ p = "set_clipboard_path";
+ s = "stripspace";
+ };
# Archive Mappings
- au = "archive_decompress";
- aa = "archive_compress";
+ a = {
+ a = "archive_compress";
+ u = "archive_decompress";
+ };
+
+ D = {
+ D = "delete";
+ };
+ d = {
+ # Trash Mappings
+ d = "trash";
- # Trash Mappings
- dd = "trash";
- jc = "trash_clear";
- jr = "trash_restore";
+ # Dragon Mapping
+ i = "dragon_individual";
+ r = "dragon";
+ s = "dragon_stay";
+ };
- # Dragon Mapping
- dr = "dragon";
- ds = "dragon_stay";
- di = "dragon_individual";
+ # that is just here for documentation
+ # i = "ctpv preview"
- cs = "stripspace";
+ # [o]perate on system commands
+ o = {
+ d = {
+ e = "syscmd_direnv_sandbox_enable";
+ d = "syscmd_direnv_sandbox_disable";
+ };
+ };
+
+ j = {
+ c = "trash_clear";
+ r = "trash_restore";
+ };
# Vim keys
h = "updir";
@@ -67,37 +127,47 @@
s = "open";
# Basic Functions
- "." = "set hidden!";
- DD = "delete";
p = "paste";
x = "cut";
y = "copy";
"<enter>" = "open";
- mk = "mk_link";
- mf = "mk_file";
- md = "mk_directory";
- ms = "mk_script";
-
- ch = "chmod";
- bg = "set_wallpaper";
- r = ":rename; cmd-end";
- H = "cd_project_root";
- R = "reload";
+ # Selecting
+ v = "invert";
+ V = "visual";
+ "<space>" = ":{{ toggle; down; }}";
C = "clear";
U = "unselect";
- # Movement
- gjr = "cd ~/.local/share/Trash/files";
- gus = "cd /run/user/${builtins.toString uid}";
+ m = {
+ d = "mk_directory";
+ f = "mk_file";
+ l = "mk_link";
+ s = "mk_script";
+ };
+
+ b = {
+ g = "set_wallpaper";
+ };
+
+ r = ":{{ rename; cmd-end; }}";
+ "<c-r>" = "reload";
+ R = "reload";
+ "<c-l>" = "redraw";
- gc = "cd ~/.config";
- gl = "cd ~/.local";
- gE = "cd /etc";
- gD = "cd ${downloadDir}";
+ # Change cursor position
+ H = "top";
+ G = "bottom";
- "gU." = "cd /usr";
- gUs = " cd /usr/share";
+ # (walking) Movement
+ w = {
+ d = "cd ${downloadDir}";
+ e = "cd /etc";
+ H = "cd ~";
+ h = "cd_project_root";
+ t = "cd /tmp";
+ u = "cd /run/user/${builtins.toString uid}";
+ };
- gt = "cd /tmp";
+ g = "cd_lf_make_map";
}
diff --git a/modules/by-name/lf/lf/module.nix b/modules/by-name/lf/lf/module.nix
index 8dfd0c52..2d80f68e 100644
--- a/modules/by-name/lf/lf/module.nix
+++ b/modules/by-name/lf/lf/module.nix
@@ -14,12 +14,36 @@
shell_library,
system,
lib,
+ libraries,
...
}: let
- commands = import ./commands {inherit pkgs sysLib shell_library system;};
- keybindings = import ./keybindings {inherit (cfg.keymaps) uid downloadDir;};
+ real_commands = import ./commands {inherit pkgs sysLib shell_library system;};
+ keybindings_raw = import ./keybindings {inherit (cfg.keymaps) uid downloadDir;};
- packages = import ./wrappers {inherit pkgs;};
+ # HACK: There is no (easy) way for me to make sure, that the `clearmaps` command is
+ # generated in the lf config before the keymapping commands.
+ # This command injection works around that. <2026-07-21>
+ commands = libraries.extra.warnMerge real_commands {
+ __clearmaps_injection = ''
+ :{{
+ clearmaps
+ }};
+ clearmaps
+ '';
+ } "lf command map";
+
+ process = prefix: attrs:
+ lib.mapAttrsToList (name: value: let
+ new_prefix =
+ if prefix == null
+ then name
+ else prefix + name;
+ in
+ if (builtins.isAttrs value)
+ then (process new_prefix value) ++ [(lib.nameValuePair new_prefix null)]
+ else (lib.nameValuePair new_prefix value))
+ attrs;
+ keybindings = builtins.listToAttrs (lib.lists.flatten (process null keybindings_raw));
cfg = config.soispha.programs.lf;
in {
@@ -54,16 +78,28 @@ in {
"d /.Trash 1777 root root"
];
+ soispha.programs.fish.integrations.lf =
+ # fish
+ ''
+ function ll
+ . ${./wrappers/ll.fish} $argv
+ end
+
+ function lm
+ . ${./wrappers/lm.fish} $argv
+ end
+ '';
+
home-manager.users.soispha = {
xdg.configFile = {
"lf/icons".source = ./icons;
"lf/colors".source = ./colors;
};
- programs.zsh.shellAliases = {
- ll = ". ${lib.getExe packages.ll}";
- lm = ". ${lib.getExe packages.lm}";
- };
+ # programs.zsh.shellAliases = {
+ # ll = ". ${lib.getExe packages.ll}";
+ # lm = ". ${lib.getExe packages.lm}";
+ # };
programs.lf = {
enable = true;
@@ -85,7 +121,6 @@ in {
drawbox = true;
- # errorfmt = "\\033[1;91m==> ERROR:\\033[0m\\033[1;93m%s\\033[0m";
errorfmt = "\\033[1;91m%s\\033[0m";
hidden = true; # show hidden files
icons = true;
@@ -94,22 +129,6 @@ in {
shell = "${lib.getExe pkgs.dash}";
shellopts = "-eu"; # e: exit on error; u: error for unset variables
};
- extraConfig = ''
- # Dynamically generate the cd mappings.
- # This code dependends on the fact, that the lf server was started previously
- # and keep running through `autoquit = false`.
- # (Otherwise, the remote command is silently dropped: https://github.com/gokcehan/lf/issues/495)
- &{{
- tmp="$(mktemp -t lf_make_map_dynamic_mapping_source_XXXXX)"
- ${lib.getExe pkgs.lf-make-map} --depth 4 generate ~/media ~/repos ~/documents >"$tmp"
-
- lf -remote "send $id source $tmp"
- sleep 1
- lf -remote "send $id source $tmp"
-
- rm "$tmp"
- }}
- '';
};
};
};
diff --git a/modules/by-name/lf/lf/wrappers/default.nix b/modules/by-name/lf/lf/wrappers/default.nix
deleted file mode 100644
index 7257a66e..00000000
--- a/modules/by-name/lf/lf/wrappers/default.nix
+++ /dev/null
@@ -1,4 +0,0 @@
-{pkgs}: {
- ll = pkgs.callPackage ./ll {};
- lm = pkgs.callPackage ./lm {};
-}
diff --git a/modules/by-name/lf/lf/wrappers/ll.fish b/modules/by-name/lf/lf/wrappers/ll.fish
new file mode 100644
index 00000000..0ee34755
--- /dev/null
+++ b/modules/by-name/lf/lf/wrappers/ll.fish
@@ -0,0 +1,41 @@
+#!/usr/bin/env fish
+
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+begin
+ test -d "$XDG_RUNTIME_DIR/ll"; or mkdir "$XDG_RUNTIME_DIR/ll"
+ set last_directory "$XDG_RUNTIME_DIR/ll/last_directory"
+
+ set --export LL_SYSCMD_SOCKET "$(mktemp -t fish_ll_syscmd_socket_XXXXXX)"
+ begin
+ command lf -last-dir-path="$last_directory" "$argv"
+
+ cat "$LL_SYSCMD_SOCKET" | while read -l cmd
+ switch "$cmd"
+ case "SYSCMD::DIRENV::SANDBOX::ENABLE"
+ set --global SHOULD_SANDBOX_DIRENV
+ case "SYSCMD::DIRENV::SANDBOX::DISABLE"
+ set --erase SHOULD_SANDBOX_DIRENV
+ case '*'
+ # Just pass things along, that we don't know about
+ printf "%s\n\r" "$cmd" >&2
+ end
+ end
+ end
+ rm "$LL_SYSCMD_SOCKET"
+ set --erase LL_SYSCMD_SOCKET
+
+
+ set dir "$(cat "$last_directory")"
+ if not cd "$dir"
+ echo "ll: Failed to cd to '$dir'. Does it exist?"
+ end
+end
diff --git a/modules/by-name/lf/lf/wrappers/ll/ll.sh b/modules/by-name/lf/lf/wrappers/ll/ll.sh
deleted file mode 100755
index ce29fd97..00000000
--- a/modules/by-name/lf/lf/wrappers/ll/ll.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env dash
-
-# nixos-config - My current NixOS configuration
-#
-# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of my nixos-config.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
-last_directory="$(mktemp -t ll_last_directory_XXXXXXX)"
-cleanup() {
- rm "$last_directory"
-}
-trap cleanup EXIT
-
-command lf -last-dir-path="$last_directory" "$@"
-
-dir="$(cat "$last_directory")"
-if cd "$dir"; then
- [ -d "$XDG_RUNTIME_DIR/ll" ] || mkdir "$XDG_RUNTIME_DIR/ll"
- echo "$dir" >"$XDG_RUNTIME_DIR/ll/last_directory"
-else
- die "$dir does not exist!"
-fi
-
-# vim: ft=sh
diff --git a/modules/by-name/lf/lf/wrappers/lm.fish b/modules/by-name/lf/lf/wrappers/lm.fish
new file mode 100644
index 00000000..b590c143
--- /dev/null
+++ b/modules/by-name/lf/lf/wrappers/lm.fish
@@ -0,0 +1,20 @@
+#!/usr/bin/env fish
+
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+begin
+ if test -f "$XDG_RUNTIME_DIR/ll/last_directory"
+ set last_dir "$(cat "$XDG_RUNTIME_DIR/ll/last_directory")"
+ cd "$last_dir"; or echo "$last_dir does not exist!"
+ else
+ echo "No last directory saved (try using ll instead)."
+ end
+end
diff --git a/modules/by-name/lo/locale/module.nix b/modules/by-name/lo/locale/module.nix
index 3c9c646c..0aa812ec 100644
--- a/modules/by-name/lo/locale/module.nix
+++ b/modules/by-name/lo/locale/module.nix
@@ -43,6 +43,9 @@ in {
LC_TIME = "sv_SE.UTF-8";
LC_COLLATE = "C.UTF-8";
};
+ extraLocales = [
+ "fr_FR.UTF-8/UTF-8"
+ ];
};
# Layout
diff --git a/modules/by-name/lu/lutris/module.nix b/modules/by-name/lu/lutris/module.nix
new file mode 100644
index 00000000..eabc11fb
--- /dev/null
+++ b/modules/by-name/lu/lutris/module.nix
@@ -0,0 +1,55 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ lib,
+ config,
+ libraries,
+ pkgs,
+ ...
+}: let
+ cfg = config.soispha.programs.lutris;
+in {
+ options.soispha.programs.lutris = {
+ enable = libraries.base.options.mkEnable "Lutris";
+ };
+
+ config = lib.mkIf cfg.enable {
+ soispha.impermanence.userDirectories = [
+ ".config/lutris"
+ ];
+ soispha.services.unison.pathsToSync = [
+ "~/.config/lutris"
+ ];
+
+ # TODO: This should also have something, to avoid giving the games network access: 'https://github.com/lutris/docs/blob/master/DisableNetworkAccess.md' <2026-07-10>
+ home-manager.users.soispha = {
+ programs.lutris = {
+ enable = true;
+ extraPackages = [
+ # Show fps and such in game
+ pkgs.mangohud
+
+ pkgs.winetricks
+
+ # Runs game in it's own compositor (avoids issues with all the resolution stuff)
+ pkgs.gamescope
+
+ pkgs.gamemode
+ pkgs.umu-launcher
+ ];
+ steamPackage = lib.mkIf config.programs.steam.enable config.programs.steam.package;
+ defaultWinePackage = pkgs.proton-ge-bin;
+ protonPackages = [pkgs.proton-ge-bin];
+
+ # winePackages = [pkgs.wineWow64Packages.full];
+ };
+ };
+ };
+}
diff --git a/modules/by-name/ly/ly/blackhole-smooth-240x67.dur b/modules/by-name/ly/ly/blackhole-smooth-240x67.dur
new file mode 100644
index 00000000..d65c878f
--- /dev/null
+++ b/modules/by-name/ly/ly/blackhole-smooth-240x67.dur
Binary files differ
diff --git a/modules/by-name/ly/ly/module.nix b/modules/by-name/ly/ly/module.nix
new file mode 100644
index 00000000..e2d3b0ae
--- /dev/null
+++ b/modules/by-name/ly/ly/module.nix
@@ -0,0 +1,130 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ lib,
+ config,
+ pkgs,
+ ...
+}: let
+ cfg = config.soispha.programs.ly;
+
+ # HACK: We search for the generate river desktop file, which should exist <2026-03-13>
+ riverDesktop =
+ lib.lists.findFirst
+ (x: lib.strings.hasSuffix "river.desktop" x)
+ null
+ config.home-manager.users.soispha.home.packages;
+
+ shellDesktop = pkgs.writeText "zsh.desktop" ''
+ [Desktop Entry]
+ Comment=A simple shell session
+ DesktopNames=null
+ Exec=zsh
+ Name=zsh shell
+ Terminal=true
+ '';
+
+ # TODO: This could use the `services.display-manager.sessionPackages` option <2026-06-23>
+ customSessionsDir =
+ pkgs.runCommand "custom-sessions" {}
+ /*
+ bash
+ */
+ ''
+ mkdir "$out";
+ cp "${riverDesktop}/share/applications/river.desktop" "$out"
+ cp "${shellDesktop}" "$out/zsh.desktop"
+ '';
+
+ setup = pkgs.writeShellApplication {
+ name = "setup";
+
+ bashOptions = [];
+
+ runtimeInputs = [
+ pkgs.findutils
+ pkgs.systemd
+ ];
+ inheritPath = true;
+
+ text = builtins.readFile ./setup.sh;
+ };
+in {
+ options.soispha.programs.ly = {
+ enable = lib.mkEnableOption "ly";
+
+ batteryName = lib.mkOption {
+ type = lib.types.nullOr lib.types.str;
+ example = "BAT0";
+ default = null;
+ description = "The name of the battery, if null, will not show a batter percentage.";
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ assertions = [
+ {
+ assertion = riverDesktop != null;
+ message = ''
+ There seems to be no `river.desktop` file generated. You will not be able to
+ log-into river without it.
+ '';
+ }
+ ];
+
+ home-manager.users.soispha = {
+ home.sessionVariables = {
+ XCOMPOSECACHE = "${config.home-manager.users.soispha.xdg.cacheHome}/X11/xcompose";
+ };
+ };
+
+ services.displayManager.ly = {
+ enable = true;
+ x11Support = false;
+
+ settings = {
+ battery_id = cfg.batteryName;
+
+ # Stop animations after 10 secs.
+ animation_timeout_sec = 60;
+
+ animation = "dur_file";
+ dur_file_path = "${./blackhole-smooth-240x67.dur}";
+ full_color = true;
+
+ # NOTE: This does the same as the default nixos setup cmd, but we don't
+ # immidiately start the `graphical-session.target`. That is started by river's
+ # init. <2026-06-23>
+ setup_cmd = "${lib.getExe setup}";
+
+ custom_sessions = "${customSessionsDir}";
+
+ # Clear the screen before starting up (otherwise error messages might linger on
+ # it.)
+ start_cmd = "clear";
+
+ # Clear the password on failure.
+ clear_password = true;
+
+ session_log = "${config.home-manager.users.soispha.xdg.dataHome}/ly/ly-session.log";
+
+ clock = "%c";
+
+ hibernate_cmd = "systemctl hibernate";
+ inactivity_cmd = "systemctl suspend-then-hibernate";
+ restart_cmd = "reboot";
+ shutdown_cmd = "shutdown $PLATFORM_SHUTDOWN_ARG now";
+ sleep_cmd = "systemctl suspend";
+
+ inactivity_delay = 120; # unit is seconds
+ };
+ };
+ };
+}
diff --git a/modules/by-name/ly/ly/setup.sh b/modules/by-name/ly/ly/setup.sh
new file mode 100644
index 00000000..6e8bed2b
--- /dev/null
+++ b/modules/by-name/ly/ly/setup.sh
@@ -0,0 +1,47 @@
+#! /usr/bin/env bash
+
+# Shared environment setup for graphical sessions.
+
+# shellcheck disable=1091
+. /etc/profile
+
+cd "$HOME" || {
+ echo 1>&2 "No HOME :("
+ exit 1
+}
+
+if [ -z "$_DID_SYSTEMD_CAT" ]; then
+ export _DID_SYSTEMD_CAT=1
+ exec systemd-cat -t xsession "$0" "$@"
+fi
+
+
+# Import environment variables into the systemd user environment.
+/run/current-system/systemd/bin/systemctl --user import-environment DBUS_SESSION_BUS_ADDRESS DISPLAY XAUTHORITY XDG_SESSION_ID
+
+# Speed up application start by 50-150ms according to
+# https://kdemonkey.blogspot.com/2008/04/magic-trick.html
+compose_cache="${XCOMPOSECACHE:-$HOME/.compose-cache}"
+mkdir -p "$compose_cache"
+# To avoid accidentally deleting a wrongly set up XCOMPOSECACHE directory,
+# defensively try to delete cache *files* only, following the file format specified in
+# https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/modules/im/ximcp/imLcIm.c#L353-358
+# sprintf (*res, "%s/%c%d_%03x_%08x_%08x", dir, _XimGetMyEndian(), XIM_CACHE_VERSION, (unsigned int)sizeof (DefTree), hash, hash2);
+find "$compose_cache" -maxdepth 1 -regextype posix-extended -regex '.*/[Bl][0-9]+_[0-9a-f]{3}_[0-9a-f]{8}_[0-9a-f]{8}' -delete
+unset compose_cache
+
+# Work around KDE errors when a user first logs in and
+# .local/share doesn't exist yet.
+mkdir -p "${XDG_DATA_HOME:-$HOME/.local/share}"
+
+unset _DID_SYSTEMD_CAT
+
+
+if test "$1"; then
+ # Run the supplied session command.
+ exec "$@"
+else
+ # TODO: Do we need this? Should not the session always exist?
+ echo "error: unknown session $1" 1>&2
+ exit 1
+fi
diff --git a/modules/by-name/me/mergiraf/module.nix b/modules/by-name/me/mergiraf/module.nix
index 474a0ae3..693969e6 100644
--- a/modules/by-name/me/mergiraf/module.nix
+++ b/modules/by-name/me/mergiraf/module.nix
@@ -23,6 +23,7 @@ in {
home-manager.users.soispha = {
programs.mergiraf = {
enable = true;
+ enableGitIntegration = true;
};
};
};
diff --git a/modules/by-name/mp/mpd/module.nix b/modules/by-name/mp/mpd/module.nix
index b7c7ab5a..474d81c4 100644
--- a/modules/by-name/mp/mpd/module.nix
+++ b/modules/by-name/mp/mpd/module.nix
@@ -62,6 +62,11 @@ in {
services.mpd = {
enable = true;
+
+ package = pkgs.mpd.override {
+ ffmpeg = pkgs.ffmpeg-headless;
+ };
+
network = {
listenAddress = socketPath;
};
@@ -71,6 +76,7 @@ in {
extraConfig = ''
metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc,comment"
+
# Updated by the beets `mpdupdate` plugin
auto_update "no"
@@ -82,6 +88,8 @@ in {
replaygain "track"
replaygain_limit "yes"
+ sticker_file "${cfg.directories.data}/sticker.sql"
+
#database {
# plugin "simple"
# path "~/.local/share/mpd/db
diff --git a/modules/by-name/mp/mpd/mpc.nix b/modules/by-name/mp/mpd/mpc.nix
index cd4a4ec0..5fbeddc5 100644
--- a/modules/by-name/mp/mpd/mpc.nix
+++ b/modules/by-name/mp/mpd/mpc.nix
@@ -14,10 +14,28 @@
...
}: let
cfg = config.soispha.services.mpd.mpc;
+
+ mpp = pkgs.mpp.override {
+ # Removes the currently playing song from the disk and storage
+ mpp-beetrm = pkgs.mpp-beetrm.override {
+ beets = cfg.beetsPkg;
+ };
+
+ # Works like normal `mpc searchadd` but uses the `beets` query syntax
+ mpp-searchadd = pkgs.mpp-searchadd.override {
+ beets = cfg.beetsPkg;
+ };
+ };
in {
options.soispha.services.mpd.mpc = {
enable = lib.mkEnableOption "mpc with extensions";
+ mppPackage = lib.mkOption {
+ type = lib.types.package;
+ description = "The package to use, when calling `mpp`";
+ default = mpp;
+ };
+
beetsPkg = lib.mkOption {
type = lib.types.package;
description = "The package to use, when calling `beet`";
@@ -26,18 +44,8 @@ in {
config = lib.mkIf cfg.enable {
home-manager.users.soispha.home.packages = [
- pkgs.mpp # Wrapper around `mpc` that allows the usage of `mpc-{beetsrm,lyrics,searchadd}` (below) without the `-`
-
- # Removes the currently playing song from the disk and storage
- (pkgs.mpp-beetrm.override {
- beets = cfg.beetsPkg;
- })
- # Works like normal `mpc searchadd` but uses the `beets` query syntax
- (pkgs.mpp-searchadd.override {
- beets = cfg.beetsPkg;
- })
- # Displays the lyrics of the currently playing song
- pkgs.mpp-lyrics
+ # Wrapper around `mpc` that allows the usage of `mpc-{beetsrm,lyrics,searchadd}`
+ cfg.mppPackage
];
};
}
diff --git a/modules/by-name/mp/mpdpopm/module.nix b/modules/by-name/mp/mpdpopm/module.nix
new file mode 100644
index 00000000..82943fe8
--- /dev/null
+++ b/modules/by-name/mp/mpdpopm/module.nix
@@ -0,0 +1,68 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ config,
+ pkgs,
+ libraries,
+ lib,
+ ...
+}: let
+ cfg = config.soispha.services.mpdpopm;
+
+ settingsFormat = pkgs.formats.json {};
+in {
+ options.soispha.services.mpdpopm = {
+ enable = libraries.base.options.mkEnable "mpdpopm";
+
+ settings = lib.mkOption {
+ # Setting this type allows for correct merging behavior
+ inherit (settingsFormat) type;
+ default = {};
+ description = ''
+ Configuration for foo, see
+ <link xlink:href="https://example.com/docs/foo"/>
+ for supported settings.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ soispha.services.mpdpopm.settings = {
+ version = "1";
+ log = "${config.home-manager.users.soispha.xdg.dataHome}/mpdpopm/log";
+
+ conn.Local = {
+ path = config.home-manager.users.soispha.home.sessionVariables.MPD_HOST;
+ };
+
+ # Automatically start in DJ mode.
+ mode = "Dj";
+
+ local_music_dir = config.soispha.services.mpd.directories.music;
+ };
+
+ home-manager.users.soispha = {
+ systemd.user.services.mpdpopm = {
+ Unit = {
+ Description = "mpdpopm ratings and playcounts for MPD";
+ Requires = ["mpd.service"];
+ After = ["mpd.service"];
+ };
+
+ Service = {
+ Restart = "on-failure";
+ ExecStart = "${lib.getExe' pkgs.mpdpopm "mpdpopmd"} --config ${settingsFormat.generate "config.json" cfg.settings}";
+ };
+
+ Install = {WantedBy = ["default.target"];};
+ };
+ };
+ };
+}
diff --git a/modules/by-name/mp/mpv/module.nix b/modules/by-name/mp/mpv/module.nix
index 4416b295..496dcd47 100644
--- a/modules/by-name/mp/mpv/module.nix
+++ b/modules/by-name/mp/mpv/module.nix
@@ -19,36 +19,46 @@ in {
enable = lib.mkEnableOption "mpv";
};
- config.home-manager.users.soispha = lib.mkIf cfg.enable {
- programs.mpv = {
- enable = true;
+ config = lib.mkIf cfg.enable {
+ soispha.services.unison.pathsToSync = [
+ "~/.local/state/mpv"
+ ];
- bindings = {
- q = "quit 0";
- "Ctrl+c" = "quit 1";
- "Shift+q" = "quit-watch-later 1";
- };
- config = {
- osd-bar = true;
+ home-manager.users.soispha = {
+ programs.mpv = {
+ enable = true;
- border = false;
- };
- scriptOpts = {
- osc = {
- scalewindowed = 0.8;
- hidetimeout = 300;
+ package = pkgs.mpv.override {
+ mpv-unwrapped = pkgs.mpv-unwrapped.override {
+ ffmpeg = pkgs.ffmpeg-headless;
+ };
+ };
+
+ bindings = {
+ q = "quit 0";
+ "Ctrl+c" = "quit 1";
+ "Shift+q" = "quit-watch-later 1";
+ };
+
+ config = {
+ osd-bar = true;
+
+ border = false;
};
- thumbfast = {
- hwdec = true;
- network = false;
- spawn_first = false;
- max_height = 250;
- max_width = 250;
+ scriptOpts = {
+ osc = {
+ scalewindowed = 0.8;
+ hidetimeout = 300;
+ };
+ thumbfast = {
+ hwdec = true;
+ network = false;
+ spawn_first = false;
+ max_height = 250;
+ max_width = 250;
+ };
};
};
- scripts = [
- pkgs.mpvScripts.thumbfast
- ];
};
};
}
diff --git a/modules/by-name/ni/nix-index/command_not_found.fish b/modules/by-name/ni/nix-index/command_not_found.fish
new file mode 100644
index 00000000..bbdb2230
--- /dev/null
+++ b/modules/by-name/ni/nix-index/command_not_found.fish
@@ -0,0 +1,111 @@
+#!/usr/bin/env fish
+
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+# This was taken from the
+# `${pkgs.nix-index}/etc/profile.d/command-not-found.sh` file on 2024-02-28
+
+function _log
+ printf $argv >&2
+end
+
+function _bail
+ __fish_default_command_not_found_handler $argv
+ return "$status"
+end
+
+function _common_prefix
+ set --function prefix
+
+ for str in $argv
+ set position 1
+ for c in (string split '' "$str")
+ if test -z $prefix[$position]
+ set prefix[$position] "$c"
+ else if test $prefix[$position] != "$c"
+ if test $position = 1
+ # The first letter of the prefix mis-matched.
+ # As such there is no shared prefix, and we can simple bail early.
+ echo ""
+ return
+ else
+ set prefix $prefix[1..(math $position - 1)]
+ end
+ break
+ end
+
+ set position (math $position + 1)
+ end
+ end
+
+ echo "$(string join "" $prefix)"
+end
+
+function fish_command_not_found
+ # Only run if it's an interactive shell
+ if not status is-interactive
+ return (_bail $argv)
+ end
+
+ # The nixpkgs flake should always be available on NixOS
+ set --local toplevel nixpkgs
+ set --local cmd "$argv[1]"
+
+ set --local attrs "$(nix-locate --minimal --no-group --type x --type s --whole-name --at-root "/bin/$cmd")"
+
+ if test -n "$attrs"
+ set --function len "$(echo "$attrs" | count)"
+ else
+ set --function len 0
+ end
+
+ switch "$len"
+ case 0
+ case 1 or '*'
+ _log "The program '%s' is currently not installed.\n" "$cmd"
+ end
+
+ switch "$len"
+ case 0
+ return (_bail $argv)
+ case 1
+ # If only one package provides this, then we can invoke it
+ # without asking the user.
+
+ # These will not return 127 if they worked correctly.
+
+ _log "A shell will be opened with it.\n"
+
+ if nix build "$toplevel#$attrs" --no-link
+ nix shell "$toplevel#$attrs"
+ return "$status"
+ else
+ _log "Failed to build: '%s#%s'\n" "$toplevel" "$attrs"
+ return (_bail $argv)
+ end
+ case '*'
+ _log "It is provided by several packages. You can run it load it with:\n"
+
+ set --local counter 0
+ set --local attrs $(echo $attrs)
+ for attr in $attrs
+ _log "%3s)" "$counter"
+ _log " nix shell %s#%s\n" "$toplevel" "$attr";
+
+ set counter "$(math "$counter" + 1)"
+ end
+
+ commandline "nix shell $toplevel#$(_common_prefix $attrs)"
+ end
+
+ # command not found should always exit with 127
+ return 127
+end
diff --git a/modules/by-name/ni/nix-index/command_not_found.sh b/modules/by-name/ni/nix-index/command_not_found.zsh
index 579f9db4..579f9db4 100644
--- a/modules/by-name/ni/nix-index/command_not_found.sh
+++ b/modules/by-name/ni/nix-index/command_not_found.zsh
diff --git a/modules/by-name/ni/nix-index/module.nix b/modules/by-name/ni/nix-index/module.nix
index 5ddaece3..72be6b52 100644
--- a/modules/by-name/ni/nix-index/module.nix
+++ b/modules/by-name/ni/nix-index/module.nix
@@ -20,7 +20,11 @@ in {
};
config = lib.mkIf cfg.enable {
- soispha.programs.zsh.integrations.nix-index = ./command_not_found.sh;
+ soispha.programs = {
+ zsh.integrations.nix-index = ./command_not_found.zsh;
+ fish.integrations.nix-index = ./command_not_found.fish;
+ };
+
home-manager.users.soispha = {
imports = [
modules.nix-index-database.homeModules.nix-index
diff --git a/modules/by-name/ni/nix/module.nix b/modules/by-name/ni/nix/module.nix
index 2b91f59b..b7e64989 100644
--- a/modules/by-name/ni/nix/module.nix
+++ b/modules/by-name/ni/nix/module.nix
@@ -9,63 +9,74 @@
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
{
pkgs,
+ libraries,
+ config,
+ lib,
# flakes
+ sources,
self,
system,
externalDependencies,
...
-}: {
- # TODO(@bpeetz): Modularize <2025-02-08>
+}: let
+ inherit (sources) nixpkgs;
- nix = {
- package = pkgs.lixPackageSets.latest.lix;
+ cfg = config.soispha.nix;
+in {
+ options.soispha.nix = {
+ enable = libraries.base.options.mkEnable "nix";
+ };
- # Disable nix channels (this is a remnant of old days)
- channel.enable = false;
+ config = lib.mkIf cfg.enable {
+ nix = {
+ package = pkgs.lixPackageSets.latest.lix;
- registry = {
- nixpkgs.flake = self.inputs.nixpkgs;
- n.flake =
- self.inputs.nixpkgs
- // {
- # Otherwise nixpkgs's config and overlays are not available:
+ # Disable nix channels (this is a remnant of old days)
+ channel.enable = false;
- # Both attrs exists, so we just override both and hope
- outputs.legacyPackages."${system}" = pkgs;
- legacyPackages."${system}" = pkgs;
- };
+ registry = {
+ nixpkgs.flake = nixpkgs;
+ n.flake =
+ nixpkgs
+ // {
+ # Otherwise nixpkgs's config and overlays are not available:
- t.flake = externalDependencies.templates;
+ # Both attrs exists, so we just override both and hope
+ outputs.legacyPackages."${system}" = pkgs;
+ legacyPackages."${system}" = pkgs;
+ };
- my_flake.flake = self;
- m.flake = self;
- };
+ t.flake = externalDependencies.templates;
- gc = {
- automatic = true;
- dates = "weekly";
- options = "--delete-older-than 7d";
- };
+ my_flake.flake = self;
+ m.flake = self;
+ };
+
+ gc = {
+ automatic = true;
+ dates = "weekly";
+ options = "--delete-older-than 7d";
+ };
- settings = {
- auto-optimise-store = true;
- experimental-features = [
- "nix-command"
- "flakes"
- #"ca-derivations"
- ];
+ settings = {
+ # Don't warn about dirty git trees (It is usually absolutely useless)
+ warn-dirty = false;
- use-xdg-base-directories = true;
+ auto-optimise-store = true;
+ experimental-features = [
+ "nix-command"
+ "flakes"
+ ];
- #substituters = ["https://cache.ngi0.nixos.org/"];
- #trusted-public-keys = ["cache.ngi0.nixos.org-1:KqH5CBLNSyX184S9BKZJo1LxrxJ9ltnY2uAs5c/f1MA="];
+ use-xdg-base-directories = true;
- fallback = true; # Build from source, if binary can't be substituted
+ fallback = true; # Build from source, if binary can't be substituted
- keep-failed = false; # keep failed tmp build dirs
- pure-eval = true; # restrict file system and network access to hash
+ keep-failed = false; # keep failed tmp build dirs
+ pure-eval = true; # restrict file system and network access to hash
- sandbox-fallback = false; # Don't disable the sandbox, if the kernel doesn't support it
+ sandbox-fallback = false; # Don't disable the sandbox, if the kernel doesn't support it
+ };
};
};
}
diff --git a/modules/by-name/ni/nixos-shell/module.nix b/modules/by-name/ni/nixos-shell/module.nix
index 8dda2890..1a0190c8 100644
--- a/modules/by-name/ni/nixos-shell/module.nix
+++ b/modules/by-name/ni/nixos-shell/module.nix
@@ -11,10 +11,11 @@
lib,
config,
pkgs,
- self,
+ sources,
...
}: let
cfg = config.soispha.nixos-shell;
+ inherit (sources) nixpkgs;
in {
options.soispha.nixos-shell = {
enable = lib.mkEnableOption "nixos-shell";
@@ -85,7 +86,7 @@ in {
};
config = let
- vmSystem = self.inputs.nixpkgs.lib.nixosSystem {
+ vmSystem = nixpkgs.lib.nixosSystem {
inherit (cfg.configuration) specialArgs;
modules = [
diff --git a/modules/by-name/ni/nixpkgs/module.nix b/modules/by-name/ni/nixpkgs/module.nix
index 502bcff2..1ded8444 100644
--- a/modules/by-name/ni/nixpkgs/module.nix
+++ b/modules/by-name/ni/nixpkgs/module.nix
@@ -22,7 +22,13 @@ in {
example = "x86_64-linux";
type = lib.types.str;
};
+ unfreePackageNames = lib.mkOption {
+ description = "Names of unfree packages to allow";
+ example = "[ steam steam-unwrapped ]";
+ type = lib.types.listOf lib.types.str;
+ };
};
+
config = let
myPkgsOverlay = self: super: packageSets.soispha;
in
@@ -36,15 +42,10 @@ in {
];
config = {
- # TODO: this fails because of the root tempsize, which should be increased
- # contentAddressedByDefault = true;
-
hostSystem = cfg.systemName;
allowUnfreePredicate = pkg:
- builtins.elem (lib.getName pkg) [
- "pypemicro" # required by pynitrokey
- ];
+ builtins.elem (lib.getName pkg) cfg.unfreePackageNames;
};
};
};
diff --git a/modules/by-name/nv/nvim/module.nix b/modules/by-name/nv/nvim/module.nix
index 81d7febf..860610bd 100644
--- a/modules/by-name/nv/nvim/module.nix
+++ b/modules/by-name/nv/nvim/module.nix
@@ -42,6 +42,10 @@ in {
};
config = lib.mkIf cfg.enable {
+ soispha.services.unison.pathsToSync = [
+ "~/.local/state/nvim"
+ ];
+
home-manager.users.soispha = {
imports = [
modules.nixvim.homeModules.nixvim
diff --git a/modules/by-name/nv/nvim/plgs/femaco/lua/femaco.lua b/modules/by-name/nv/nvim/plgs/femaco/lua/femaco.lua
index 50a6cb23..4dc9ce97 100644
--- a/modules/by-name/nv/nvim/plgs/femaco/lua/femaco.lua
+++ b/modules/by-name/nv/nvim/plgs/femaco/lua/femaco.lua
@@ -8,7 +8,6 @@
-- You should have received a copy of the License along with this program.
-- If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-
local clip_val = require("femaco.utils").clip_val
require("femaco").setup({
@@ -19,7 +18,7 @@ require("femaco").setup({
prepare_buffer = function(opts)
local buf = vim.api.nvim_create_buf(false, false)
return vim.api.nvim_open_win(buf, true, opts)
- end;
+ end,
-- should return options passed to nvim_open_win
-- @param code_block: data about the code-block with the keys
@@ -28,42 +27,34 @@ require("femaco").setup({
-- * lang
float_opts = function(code_block)
return {
- relative = "cursor";
- width = clip_val(5, 120, vim.api.nvim_win_get_width(0) - 10); -- TODO how to offset sign column etc?
- height = clip_val(5, #code_block.lines, vim.api.nvim_win_get_height(0) - 6);
- anchor = "NW";
- row = 0;
- col = 0;
- style = "minimal";
- border = "rounded";
- zindex = 1;
+ relative = "cursor",
+ width = clip_val(5, 120, vim.api.nvim_win_get_width(0) - 10), -- TODO how to offset sign column etc?
+ height = clip_val(5, #code_block.lines, vim.api.nvim_win_get_height(0) - 6),
+ anchor = "NW",
+ row = 0,
+ col = 0,
+ style = "minimal",
+ border = "rounded",
+ zindex = 1,
}
- end;
+ end,
-- return filetype to use for a given lang
-- lang can be nil
- ft_from_lang = function(lang)
- return lang
- end;
+ ft_from_lang = function(lang) return lang end,
-- what to do after opening the float
- post_open_float = function(winnr)
- vim.wo.signcolumn = "no"
- end;
+ post_open_float = function(winnr) vim.wo.signcolumn = "no" end,
-- create the path to a temporary file
- create_tmp_filepath = function(filetype)
- return os.tmpname()
- end;
+ create_tmp_filepath = function(filetype) return os.tmpname() end,
-- if a newline should always be used, useful for multiline injections
-- which separators needs to be on separate lines such as markdown, neorg etc
-- @param base_filetype: The filetype which FeMaco is called from, not the
-- filetype of the injected language (this is the current buffer so you can
-- get it from vim.bo.filetyp).
- ensure_newline = function(base_filetype)
- return base_filetype == "nix"
- end;
+ ensure_newline = function(base_filetype) return base_filetype == "nix" end,
-- Return true if the indentation should be normalized. Useful when the
-- injected language inherits indentation from the construction scope (e.g. an
@@ -73,7 +64,5 @@ require("femaco").setup({
-- @param base_filetype: The filetype which FeMaco is called from, not the
-- filetype of the injected language (this is the current buffer, so you can
-- get it from vim.bo.filetype).
- normalize_indent = function(base_filetype)
- return base_filetype == "nix"
- end;
+ normalize_indent = function(base_filetype) return base_filetype == "nix" end,
})
diff --git a/modules/by-name/nv/nvim/plgs/lsp/servers/default.nix b/modules/by-name/nv/nvim/plgs/lsp/servers/default.nix
index b6e47ca4..0e7be6e8 100644
--- a/modules/by-name/nv/nvim/plgs/lsp/servers/default.nix
+++ b/modules/by-name/nv/nvim/plgs/lsp/servers/default.nix
@@ -11,13 +11,14 @@
imports = [
./servers/bashls.nix
./servers/ccls.nix
- ./servers/quick-lint-js.nix
./servers/ltex.nix
./servers/lua-ls.nix
./servers/nil-ls.nix
./servers/openscad.nix
+ ./servers/quick-lint-js.nix
./servers/ruff-lsp.nix
./servers/rust-analyzer.nix
./servers/texlab.nix
+ ./servers/zls.nix
];
}
diff --git a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/openscad.nix b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/openscad.nix
index 3d55adfa..1d516c9d 100644
--- a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/openscad.nix
+++ b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/openscad.nix
@@ -21,10 +21,25 @@ in {
lua
*/
''
- require('lspconfig').openscad_lsp.setup{
- cmd = {"openscad-lsp", "--stdio", "--fmt-style", "WebKit"},
+ vim.lsp.config['openscad_lsp'] = {
+ cmd = {"openscad-lsp", "--stdio", "--fmt-style", "WebKit"},
}
+ vim.lsp.enable("openscad_lsp")
'';
+
+ autoCmd = [
+ {
+ command = "set filetype=openscad";
+ event = [
+ "BufRead"
+ "BufNewFile"
+ ];
+ pattern = [
+ "*.scad"
+ ];
+ }
+ ];
+
extraPackages = [
pkgs.openscad-lsp
pkgs.clang-tools # Need to satisfy `clang-format` (which is used by openscad-lsp)
diff --git a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/quick-lint-js.nix b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/quick-lint-js.nix
index 404784f1..097abfbe 100644
--- a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/quick-lint-js.nix
+++ b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/quick-lint-js.nix
@@ -21,9 +21,9 @@ in {
lua
*/
''
- require('lspconfig').quick_lint_js.setup{
- }
+ vim.lsp.enable('quick_lint_js')
'';
+
extraPackages = [
pkgs.quick-lint-js
];
diff --git a/modules/by-name/nv/nvim/plgs/lsp/servers/servers/zls.nix b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/zls.nix
new file mode 100644
index 00000000..aab7a135
--- /dev/null
+++ b/modules/by-name/nv/nvim/plgs/lsp/servers/servers/zls.nix
@@ -0,0 +1,30 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ lib,
+ config,
+ pkgs,
+ ...
+}: let
+ cfg = config.soispha.programs.nvim;
+in {
+ home-manager.users.soispha.programs.nixvim = lib.mkIf cfg.enable {
+ extraConfigLuaPost =
+ /*
+ lua
+ */
+ ''
+ vim.lsp.enable("zls")
+ '';
+ extraPackages = [
+ pkgs.zls
+ ];
+ };
+}
diff --git a/modules/by-name/nv/nvim/plgs/luasnip/snippets/all.lua b/modules/by-name/nv/nvim/plgs/luasnip/snippets/all.lua
index 371f5539..8c30063e 100644
--- a/modules/by-name/nv/nvim/plgs/luasnip/snippets/all.lua
+++ b/modules/by-name/nv/nvim/plgs/luasnip/snippets/all.lua
@@ -20,21 +20,20 @@ local get_comment_string = function(comment_type)
local utils = require("Comment.utils")
-- use the `Comments.nvim` API to fetch the comment string for the region (eq. '--%s' or '--[[%s]]' for `lua`)
- local cstring =
- calculate_comment_string({ ctype = comment_type; range = utils.get_region(); })
+ local cstring = calculate_comment_string({ ctype = comment_type, range = utils.get_region() })
if cstring == nil then
-- TODO: Use `vim.bo.commentstring` <2025-05-02>
-- Use some useful default values.
- return { ["begin"] = "#"; ["end"] = ""; }
+ return { ["begin"] = "#", ["end"] = "" }
end
-- as we want only the strings themselves and not strings ready for using `format` we want to split the left and right side
local left, right = utils.unwrap_cstr(cstring)
-- create a `{left, right}` table for it
- return { ["begin"] = left; ["end"] = right; }
+ return { ["begin"] = left, ["end"] = right }
end
--- Wraps a table of snippet nodes in two comment function nodes.
@@ -45,18 +44,13 @@ end
local wrap_snippet_in_comments = function(comment_type, nodes)
local output = {}
- table.insert(output, ls.function_node(function()
- return get_comment_string(comment_type)["begin"]
- end))
-
+ table.insert(output, ls.function_node(function() return get_comment_string(comment_type)["begin"] end))
for _, v in ipairs(nodes) do
table.insert(output, v)
end
- table.insert(output, ls.function_node(function()
- return get_comment_string(comment_type)["end"]
- end))
+ table.insert(output, ls.function_node(function() return get_comment_string(comment_type)["end"] end))
return output
end
@@ -91,42 +85,36 @@ local function pair(pair_begin, pair_end, file_types, condition_function)
-- It would be nice, if it would support both an empty array (`{}`) and nil <2023-08-27>
-- file_types = file_types or {};
- return ls.snippet(
- {
- trig = pair_begin;
- wordTrig = false;
- snippetType = "autosnippet";
- },
- {
- ls.text_node({ pair_begin; });
- ls.dynamic_node(1, get_visual);
- ls.text_node({ pair_end; });
- },
- {
- condition = function()
- local filetype_check = true
+ return ls.snippet({
+ trig = pair_begin,
+ wordTrig = false,
+ snippetType = "autosnippet",
+ }, {
+ ls.text_node({ pair_begin }),
+ ls.dynamic_node(1, get_visual),
+ ls.text_node({ pair_end }),
+ }, {
+ condition = function()
+ local filetype_check = true
- if file_types ~= nil then
- filetype_check = file_types[vim.bo.filetype] or false
- end
+ if file_types ~= nil then filetype_check = file_types[vim.bo.filetype] or false end
- return (not condition_function(pair_begin, pair_end)) and filetype_check
- end;
- }
- )
+ return (not condition_function(pair_begin, pair_end)) and filetype_check
+ end,
+ })
end
local auto_pairs = {
- pair("(", ")", nil, char_count_same);
- pair("{", "}", nil, char_count_same);
- pair("[", "]", nil, char_count_same);
- pair("<", ">", { ["rust"] = true; ["tex"] = true; }, char_count_same);
- pair("'", "'", nil, even_count);
- pair("\"", "\"", nil, even_count);
- pair("`", "`", nil, even_count);
+ pair("(", ")", nil, char_count_same),
+ pair("{", "}", nil, char_count_same),
+ pair("[", "]", nil, char_count_same),
+ pair("<", ">", { ["rust"] = true, ["tex"] = true }, char_count_same),
+ pair("'", "'", nil, even_count),
+ pair("\"", "\"", nil, even_count),
+ pair("`", "`", nil, even_count),
}
-ls.add_snippets("all", auto_pairs, { type = "snippets"; key = "auto_pairs"; })
+ls.add_snippets("all", auto_pairs, { type = "snippets", key = "auto_pairs" })
-- }}}
-- todo_comments {{{
@@ -134,9 +122,7 @@ local read_git_config = function(config_value)
local command = string.format("git config \"%s\"", config_value)
local handle = io.popen(command)
- if handle == nil then
- return error(string.format("Failed to call `%s`.", command))
- end
+ if handle == nil then return error(string.format("Failed to call `%s`.", command)) end
local result = handle:read("*a")
handle:close()
@@ -199,14 +185,17 @@ local todo_snippet = function(trig, name, comment_type, mark_function)
local date_node, signature_node = mark_function()
- local nodes = fmt("{} {}{}: {} {} {}", wrap_snippet_in_comments(comment_type, {
- ls.text_node(name);
- signature_node;
- ls.insert_node(1, "content");
- date_node;
- }))
+ local nodes = fmt(
+ "{} {}{}: {} {} {}",
+ wrap_snippet_in_comments(comment_type, {
+ ls.text_node(name),
+ signature_node,
+ ls.insert_node(1, "content"),
+ date_node,
+ })
+ )
- return ls.snippet(context, nodes, { ctype = comment_type; })
+ return ls.snippet(context, nodes, { ctype = comment_type })
end
---@param trigger string: The luasnip trigger
@@ -214,26 +203,18 @@ end
---@param name string: All aliases for a name
---@return table: All possible snippets build from the marks
local process_marks = function(trigger, name, comment_type)
- local username = function()
- return handle_from_name(read_git_config("user.name"))
- end
+ local username = function() return handle_from_name(read_git_config("user.name")) end
local marks = {
- signature = function()
- return ls.text_node("(" .. username() .. ")"), ls.text_node("")
- end;
+ signature = function() return ls.text_node("(" .. username() .. ")"), ls.text_node("") end,
date_signature = function()
return ls.text_node("<" .. os.date("%Y-%m-%d") .. ">"), ls.text_node("(" .. username() .. ")")
- end;
+ end,
- date = function()
- return ls.text_node("<" .. os.date("%Y-%m-%d") .. ">"), ls.text_node("")
- end;
+ date = function() return ls.text_node("<" .. os.date("%Y-%m-%d") .. ">"), ls.text_node("") end,
- empty = function()
- return ls.text_node(""), ls.text_node("")
- end;
+ empty = function() return ls.text_node(""), ls.text_node("") end,
}
local output = {}
@@ -247,20 +228,20 @@ local process_marks = function(trigger, name, comment_type)
end
local todo_snippet_specs = {
- { { trig = "todo"; }; { "TODO"; }; { ctype = 1; }; };
- { { trig = "fix"; }; { "FIXME"; "ISSUE"; }; { ctype = 1; }; };
- { { trig = "hack"; }; { "HACK"; }; { ctype = 1; }; };
- { { trig = "warn"; }; { "WARNING"; }; { ctype = 1; }; };
- { { trig = "perf"; }; { "PERFORMANCE"; "OPTIMIZE"; }; { ctype = 1; }; };
- { { trig = "note"; }; { "NOTE"; "INFO"; }; { ctype = 1; }; };
+ { { trig = "todo" }, { "TODO" }, { ctype = 1 } },
+ { { trig = "fix" }, { "FIXME", "ISSUE" }, { ctype = 1 } },
+ { { trig = "hack" }, { "HACK" }, { ctype = 1 } },
+ { { trig = "warn" }, { "WARNING" }, { ctype = 1 } },
+ { { trig = "perf" }, { "PERFORMANCE", "OPTIMIZE" }, { ctype = 1 } },
+ { { trig = "note" }, { "NOTE", "INFO" }, { ctype = 1 } },
-- NOTE: Block commented todo-comments
- { { trig = "todob"; }; { "TODO"; }; { ctype = 2; }; };
- { { trig = "fixb"; }; { "FIXME"; "ISSUE"; }; { ctype = 2; }; };
- { { trig = "hackb"; }; { "HACK"; }; { ctype = 2; }; };
- { { trig = "warnb"; }; { "WARNING"; }; { ctype = 2; }; };
- { { trig = "perfb"; }; { "PERF"; "PERFORMANCE"; "OPTIM"; "OPTIMIZE"; }; { ctype = 2; }; };
- { { trig = "noteb"; }; { "NOTE"; "INFO"; }; { ctype = 2; }; };
+ { { trig = "todob" }, { "TODO" }, { ctype = 2 } },
+ { { trig = "fixb" }, { "FIXME", "ISSUE" }, { ctype = 2 } },
+ { { trig = "hackb" }, { "HACK" }, { ctype = 2 } },
+ { { trig = "warnb" }, { "WARNING" }, { ctype = 2 } },
+ { { trig = "perfb" }, { "PERF", "PERFORMANCE", "OPTIM", "OPTIMIZE" }, { ctype = 2 } },
+ { { trig = "noteb" }, { "NOTE", "INFO" }, { ctype = 2 } },
}
local todo_comment_snippets = {}
@@ -271,7 +252,7 @@ for _, v in ipairs(todo_snippet_specs) do
end
end
-ls.add_snippets("all", todo_comment_snippets, { type = "snippets"; key = "todo_comments"; })
+ls.add_snippets("all", todo_comment_snippets, { type = "snippets", key = "todo_comments" })
-- }}}
-- spdx snippets {{{
@@ -283,27 +264,30 @@ local generate_spdx_snippet = function(comment_type, spdx_license_expr, trigger)
context.name = trigger .. " spdx snippet expr"
context.trig = trigger
-
local nodes = {
- fmt("{} SPDX-SnippetBegin {}", wrap_snippet_in_comments(comment_type, {}));
+ fmt("{} SPDX-SnippetBegin {}", wrap_snippet_in_comments(comment_type, {})),
- fmt("{} SPDX-SnippetCopyrightText: {} {} <{}> {}",
- wrap_snippet_in_comments(comment_type, {
- ls.insert_node(1, "year");
- ls.insert_node(2, "author");
- ls.insert_node(3, "email");
- })
- );
+ fmt(
+ "{} SPDX-SnippetCopyrightText: {} {} <{}> {}",
+ wrap_snippet_in_comments(comment_type, {
+ ls.insert_node(1, "year"),
+ ls.insert_node(2, "author"),
+ ls.insert_node(3, "email"),
+ })
+ ),
- fmt("{} SPDX-License-Identifier: {} {}", wrap_snippet_in_comments(comment_type, {
- ls.text_node(spdx_license_expr);
- }));
+ fmt(
+ "{} SPDX-License-Identifier: {} {}",
+ wrap_snippet_in_comments(comment_type, {
+ ls.text_node(spdx_license_expr),
+ })
+ ),
- { ls.insert_node(4, "content"); };
+ { ls.insert_node(4, "content") },
- fmt("{} SPDX-SnippetEnd {}", wrap_snippet_in_comments(comment_type, {}));
+ fmt("{} SPDX-SnippetEnd {}", wrap_snippet_in_comments(comment_type, {})),
- { ls.insert_node(0); };
+ { ls.insert_node(0) },
}
local newline_nodes = {}
@@ -313,16 +297,16 @@ local generate_spdx_snippet = function(comment_type, spdx_license_expr, trigger)
end
-- luasnip requires newlines to be encoded like this:
- table.insert(newline_nodes, ls.text_node({ ""; ""; }))
+ table.insert(newline_nodes, ls.text_node({ "", "" }))
end
- return ls.snippet(context, newline_nodes, { ctype = comment_type; })
+ return ls.snippet(context, newline_nodes, { ctype = comment_type })
end
local spdx = {
- { trigger = "spdx-AGPL3+"; license = "AGPL-3.0-or-later"; };
- { trigger = "spdx-GPL3+"; license = "GPL-3.0-or-later"; };
- { trigger = "spdx-MIT"; license = "MIT"; };
+ { trigger = "spdx-AGPL3+", license = "AGPL-3.0-or-later" },
+ { trigger = "spdx-GPL3+", license = "GPL-3.0-or-later" },
+ { trigger = "spdx-MIT", license = "MIT" },
}
local spdx_snippets = {}
@@ -334,5 +318,5 @@ for _, value in ipairs(spdx) do
table.insert(spdx_snippets, snippet)
end
-ls.add_snippets("all", spdx_snippets, { type = "snippets"; key = "spdx_snippets"; })
+ls.add_snippets("all", spdx_snippets, { type = "snippets", key = "spdx_snippets" })
-- }}}
diff --git a/modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/delimiter.lua b/modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/delimiter.lua
index bcd128f7..02d285b7 100644
--- a/modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/delimiter.lua
+++ b/modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/delimiter.lua
@@ -19,23 +19,23 @@ local get_visual = function(_, parent)
end
end
-local translation_table = { ["("] = ")"; ["{"] = "}"; ["["] = "]"; }
+local translation_table = { ["("] = ")", ["{"] = "}", ["["] = "]" }
-- Return snippet tables
return {
-- LEFT/RIGHT ALL BRACES
ls.snippet(
{
- trig = "([^%a])l([%(%[%{])";
- regTrig = true;
- wordTrig = false;
- snippetType = "autosnippet";
+ trig = "([^%a])l([%(%[%{])",
+ regTrig = true,
+ wordTrig = false,
+ snippetType = "autosnippet",
},
fmt("{}\\left{}{}\\right{}", {
- ls.function_node(function(_, snip) return snip.captures[1] end);
- ls.function_node(function(_, snip) return snip.captures[2] end);
- ls.dynamic_node(1, get_visual);
- ls.function_node(function(_, snip) return translation_table[snip.captures[2]] end);
+ ls.function_node(function(_, snip) return snip.captures[1] end),
+ ls.function_node(function(_, snip) return snip.captures[2] end),
+ ls.dynamic_node(1, get_visual),
+ ls.function_node(function(_, snip) return translation_table[snip.captures[2]] end),
})
- );
+ ),
}
diff --git a/modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/greek.lua b/modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/greek.lua
index 21aa7414..a98f6e8c 100644
--- a/modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/greek.lua
+++ b/modules/by-name/nv/nvim/plgs/luasnip/snippets/tex/greek.lua
@@ -12,38 +12,38 @@ local ls = require("luasnip")
-- Return snippet tables
return {
- ls.snippet({ trig = ";a"; snippetType = "autosnippet"; }, { ls.text_node("\\alpha"); });
- ls.snippet({ trig = ";b"; snippetType = "autosnippet"; }, { ls.text_node("\\beta"); });
- ls.snippet({ trig = ";g"; snippetType = "autosnippet"; }, { ls.text_node("\\gamma"); });
- ls.snippet({ trig = ";G"; snippetType = "autosnippet"; }, { ls.text_node("\\Gamma"); });
- ls.snippet({ trig = ";d"; snippetType = "autosnippet"; }, { ls.text_node("\\delta"); });
- ls.snippet({ trig = ";D"; snippetType = "autosnippet"; }, { ls.text_node("\\Delta"); });
- ls.snippet({ trig = ";e"; snippetType = "autosnippet"; }, { ls.text_node("\\epsilon"); });
- ls.snippet({ trig = ";ve"; snippetType = "autosnippet"; }, { ls.text_node("\\varepsilon"); });
- ls.snippet({ trig = ";z"; snippetType = "autosnippet"; }, { ls.text_node("\\zeta"); });
- ls.snippet({ trig = ";h"; snippetType = "autosnippet"; }, { ls.text_node("\\eta"); });
- ls.snippet({ trig = ";o"; snippetType = "autosnippet"; }, { ls.text_node("\\theta"); });
- ls.snippet({ trig = ";vo"; snippetType = "autosnippet"; }, { ls.text_node("\\vartheta"); });
- ls.snippet({ trig = ";O"; snippetType = "autosnippet"; }, { ls.text_node("\\Theta"); });
- ls.snippet({ trig = ";k"; snippetType = "autosnippet"; }, { ls.text_node("\\kappa"); });
- ls.snippet({ trig = ";l"; snippetType = "autosnippet"; }, { ls.text_node("\\lambda"); });
- ls.snippet({ trig = ";L"; snippetType = "autosnippet"; }, { ls.text_node("\\Lambda"); });
- ls.snippet({ trig = ";m"; snippetType = "autosnippet"; }, { ls.text_node("\\mu"); });
- ls.snippet({ trig = ";n"; snippetType = "autosnippet"; }, { ls.text_node("\\nu"); });
- ls.snippet({ trig = ";x"; snippetType = "autosnippet"; }, { ls.text_node("\\xi"); });
- ls.snippet({ trig = ";X"; snippetType = "autosnippet"; }, { ls.text_node("\\Xi"); });
- ls.snippet({ trig = ";i"; snippetType = "autosnippet"; }, { ls.text_node("\\pi"); });
- ls.snippet({ trig = ";I"; snippetType = "autosnippet"; }, { ls.text_node("\\Pi"); });
- ls.snippet({ trig = ";r"; snippetType = "autosnippet"; }, { ls.text_node("\\rho"); });
- ls.snippet({ trig = ";s"; snippetType = "autosnippet"; }, { ls.text_node("\\sigma"); });
- ls.snippet({ trig = ";S"; snippetType = "autosnippet"; }, { ls.text_node("\\Sigma"); });
- ls.snippet({ trig = ";t"; snippetType = "autosnippet"; }, { ls.text_node("\\tau"); });
- ls.snippet({ trig = ";f"; snippetType = "autosnippet"; }, { ls.text_node("\\phi"); });
- ls.snippet({ trig = ";vf"; snippetType = "autosnippet"; }, { ls.text_node("\\varphi"); });
- ls.snippet({ trig = ";F"; snippetType = "autosnippet"; }, { ls.text_node("\\Phi"); });
- ls.snippet({ trig = ";c"; snippetType = "autosnippet"; }, { ls.text_node("\\chi"); });
- ls.snippet({ trig = ";p"; snippetType = "autosnippet"; }, { ls.text_node("\\psi"); });
- ls.snippet({ trig = ";P"; snippetType = "autosnippet"; }, { ls.text_node("\\Psi"); });
- ls.snippet({ trig = ";w"; snippetType = "autosnippet"; }, { ls.text_node("\\omega"); });
- ls.snippet({ trig = ";W"; snippetType = "autosnippet"; }, { ls.text_node("\\Omega"); });
+ ls.snippet({ trig = ";a", snippetType = "autosnippet" }, { ls.text_node("\\alpha") }),
+ ls.snippet({ trig = ";b", snippetType = "autosnippet" }, { ls.text_node("\\beta") }),
+ ls.snippet({ trig = ";g", snippetType = "autosnippet" }, { ls.text_node("\\gamma") }),
+ ls.snippet({ trig = ";G", snippetType = "autosnippet" }, { ls.text_node("\\Gamma") }),
+ ls.snippet({ trig = ";d", snippetType = "autosnippet" }, { ls.text_node("\\delta") }),
+ ls.snippet({ trig = ";D", snippetType = "autosnippet" }, { ls.text_node("\\Delta") }),
+ ls.snippet({ trig = ";e", snippetType = "autosnippet" }, { ls.text_node("\\epsilon") }),
+ ls.snippet({ trig = ";ve", snippetType = "autosnippet" }, { ls.text_node("\\varepsilon") }),
+ ls.snippet({ trig = ";z", snippetType = "autosnippet" }, { ls.text_node("\\zeta") }),
+ ls.snippet({ trig = ";h", snippetType = "autosnippet" }, { ls.text_node("\\eta") }),
+ ls.snippet({ trig = ";o", snippetType = "autosnippet" }, { ls.text_node("\\theta") }),
+ ls.snippet({ trig = ";vo", snippetType = "autosnippet" }, { ls.text_node("\\vartheta") }),
+ ls.snippet({ trig = ";O", snippetType = "autosnippet" }, { ls.text_node("\\Theta") }),
+ ls.snippet({ trig = ";k", snippetType = "autosnippet" }, { ls.text_node("\\kappa") }),
+ ls.snippet({ trig = ";l", snippetType = "autosnippet" }, { ls.text_node("\\lambda") }),
+ ls.snippet({ trig = ";L", snippetType = "autosnippet" }, { ls.text_node("\\Lambda") }),
+ ls.snippet({ trig = ";m", snippetType = "autosnippet" }, { ls.text_node("\\mu") }),
+ ls.snippet({ trig = ";n", snippetType = "autosnippet" }, { ls.text_node("\\nu") }),
+ ls.snippet({ trig = ";x", snippetType = "autosnippet" }, { ls.text_node("\\xi") }),
+ ls.snippet({ trig = ";X", snippetType = "autosnippet" }, { ls.text_node("\\Xi") }),
+ ls.snippet({ trig = ";i", snippetType = "autosnippet" }, { ls.text_node("\\pi") }),
+ ls.snippet({ trig = ";I", snippetType = "autosnippet" }, { ls.text_node("\\Pi") }),
+ ls.snippet({ trig = ";r", snippetType = "autosnippet" }, { ls.text_node("\\rho") }),
+ ls.snippet({ trig = ";s", snippetType = "autosnippet" }, { ls.text_node("\\sigma") }),
+ ls.snippet({ trig = ";S", snippetType = "autosnippet" }, { ls.text_node("\\Sigma") }),
+ ls.snippet({ trig = ";t", snippetType = "autosnippet" }, { ls.text_node("\\tau") }),
+ ls.snippet({ trig = ";f", snippetType = "autosnippet" }, { ls.text_node("\\phi") }),
+ ls.snippet({ trig = ";vf", snippetType = "autosnippet" }, { ls.text_node("\\varphi") }),
+ ls.snippet({ trig = ";F", snippetType = "autosnippet" }, { ls.text_node("\\Phi") }),
+ ls.snippet({ trig = ";c", snippetType = "autosnippet" }, { ls.text_node("\\chi") }),
+ ls.snippet({ trig = ";p", snippetType = "autosnippet" }, { ls.text_node("\\psi") }),
+ ls.snippet({ trig = ";P", snippetType = "autosnippet" }, { ls.text_node("\\Psi") }),
+ ls.snippet({ trig = ";w", snippetType = "autosnippet" }, { ls.text_node("\\omega") }),
+ ls.snippet({ trig = ";W", snippetType = "autosnippet" }, { ls.text_node("\\Omega") }),
}
diff --git a/modules/by-name/nv/nvim/plgs/neorg/default.nix b/modules/by-name/nv/nvim/plgs/neorg/default.nix
index 5ed87f0e..b69f5a58 100644
--- a/modules/by-name/nv/nvim/plgs/neorg/default.nix
+++ b/modules/by-name/nv/nvim/plgs/neorg/default.nix
@@ -55,7 +55,7 @@ in {
__empty = null;
};
"core.dirman".config = {
- workspaces = { };
+ workspaces = {};
};
"core.export".config = {
__empty = null;
diff --git a/modules/by-name/nv/nvim/plgs/nvim-cmp/default.nix b/modules/by-name/nv/nvim/plgs/nvim-cmp/default.nix
index 315f3fc7..48883bd3 100644
--- a/modules/by-name/nv/nvim/plgs/nvim-cmp/default.nix
+++ b/modules/by-name/nv/nvim/plgs/nvim-cmp/default.nix
@@ -14,6 +14,12 @@
}: let
cfg = config.soispha.programs.nvim;
in {
+ soispha.nixpkgs.unfreePackageNames = [
+ # the plugin is lacking an license and is thus unfree, effectively
+ # its okay though (TODO: investigate <2026-05-11>)?
+ "cmp-calc"
+ ];
+
home-manager.users.soispha.programs.nixvim = lib.mkIf cfg.enable {
opts.completeopt = [
"menu" # Show completions in a menu
diff --git a/modules/by-name/pl/plymouth/module.nix b/modules/by-name/pl/plymouth/module.nix
new file mode 100644
index 00000000..5e4735b9
--- /dev/null
+++ b/modules/by-name/pl/plymouth/module.nix
@@ -0,0 +1,33 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ config,
+ pkgs,
+ lib,
+ libraries,
+ ...
+}: let
+ cfg = config.soispha.services.plymouth;
+in {
+ options.soispha.services.plymouth = {
+ enable = libraries.base.options.mkEnable "plymouth";
+ };
+
+ config = lib.mkIf cfg.enable {
+ boot.plymouth = {
+ enable = true;
+
+ theme = "rings";
+ themePackages = [
+ pkgs.adi1090x-plymouth-themes
+ ];
+ };
+ };
+}
diff --git a/modules/by-name/po/power/module.nix b/modules/by-name/po/power/module.nix
index 3489853c..5a547101 100644
--- a/modules/by-name/po/power/module.nix
+++ b/modules/by-name/po/power/module.nix
@@ -21,22 +21,23 @@ in {
config = lib.mkIf cfg.enable {
# see this for reference: https://github.com/NixOS/nixpkgs/issues/211345
services = {
- # conflicts with tlp
- power-profiles-daemon.enable = false;
-
- thermald.enable = true;
-
- tlp = {
+ tuned = {
enable = true;
- settings = {
- CPU_BOOST_ON_AC = 1;
- CPU_BOOST_ON_BAT = 0;
- CPU_SCALING_GOVERNOR_ON_AC = "performance";
- CPU_SCALING_GOVERNOR_ON_BAT = "powersave";
- SATA_LINKPWR_ON_AC = "max_performance";
- SATA_LINKPWR_ON_BAT = "min_power";
- };
};
+
+ # thermald.enable = true;
+ #
+ # tlp = {
+ # enable = true;
+ # settings = {
+ # CPU_BOOST_ON_AC = 1;
+ # CPU_BOOST_ON_BAT = 0;
+ # CPU_SCALING_GOVERNOR_ON_AC = "performance";
+ # CPU_SCALING_GOVERNOR_ON_BAT = "powersave";
+ # SATA_LINKPWR_ON_AC = "max_performance";
+ # SATA_LINKPWR_ON_BAT = "min_power";
+ # };
+ # };
};
};
}
diff --git a/modules/by-name/qu/qutebrowser/include/redirects.py b/modules/by-name/qu/qutebrowser/include/redirects.py
index 63a44ecf..2588b9e0 100644
--- a/modules/by-name/qu/qutebrowser/include/redirects.py
+++ b/modules/by-name/qu/qutebrowser/include/redirects.py
@@ -34,7 +34,7 @@ def farside_redir(target: str, url: QUrl) -> bool:
# Any return value other than a literal 'False' means we redirect
REDIRECT_MAP: typing.Dict[str, typing.Callable[..., typing.Optional[bool]]] = {
- "reddit.com": operator.methodcaller("setHost", "redlib.vhack.eu"),
+ "reddit.com": partial(farside_redir, "redlib"),
# Source: https://libredirect.github.io/
"medium.com": partial(farside_redir, "scribe"),
"stackoverflow.com": partial(farside_redir, "anonymousoverflow"),
diff --git a/modules/by-name/qu/qutebrowser/module.hm.nix b/modules/by-name/qu/qutebrowser/module.hm.nix
index 3f0f0d52..41e8952d 100644
--- a/modules/by-name/qu/qutebrowser/module.hm.nix
+++ b/modules/by-name/qu/qutebrowser/module.hm.nix
@@ -280,7 +280,7 @@ in {
options.programs.qutebrowser = {
enable = lib.mkEnableOption "qutebrowser";
- package = lib.mkPackageOption pkgs "qutebrowser" {};
+ package = lib.mkPackageOption pkgs "qutebrowser" {nullable = true;};
loadAutoconfig = lib.mkOption {
type = lib.types.bool;
@@ -429,7 +429,7 @@ in {
in
lib.mkIf cfg.enable {
home = {
- packages = [cfg.package];
+ packages = lib.mkIf (cfg.package != null) [cfg.package];
file = {
".qutebrowser/config.py" = lib.mkIf pkgs.stdenv.hostPlatform.isDarwin {
diff --git a/modules/by-name/qu/qutebrowser/module.nix b/modules/by-name/qu/qutebrowser/module.nix
index dab06237..d7689793 100644
--- a/modules/by-name/qu/qutebrowser/module.nix
+++ b/modules/by-name/qu/qutebrowser/module.nix
@@ -2,7 +2,7 @@
config,
lib,
pkgs,
- self,
+ sources,
...
}: let
cfg = config.soispha.programs.qutebrowser;
@@ -80,7 +80,7 @@ in {
config = lib.mkIf cfg.enable {
home-manager.users.soispha = {
disabledModules = [
- "${self.inputs.home-manager}/modules/programs/qutebrowser.nix"
+ "${sources.home-manager}/modules/programs/qutebrowser.nix"
];
imports = [
./module.hm.nix
@@ -90,7 +90,7 @@ in {
programs.qutebrowser = {
enable = true;
- package = pkgs.hello; # TODO: Set to null, once supported <2025-06-06>
+ package = null;
settings = import ./settings {
inherit lib pkgs;
diff --git a/modules/by-name/qu/qutebrowser/settings/default.nix b/modules/by-name/qu/qutebrowser/settings/default.nix
index 282c5816..b0259c22 100644
--- a/modules/by-name/qu/qutebrowser/settings/default.nix
+++ b/modules/by-name/qu/qutebrowser/settings/default.nix
@@ -502,11 +502,11 @@ in {
"@du" = duckduckgo;
# NIX
- "@np" = "https://search.nixos.org/packages?type=packages&query={}"; # Nix packages
+ "@np" = "https://search.nixos.org/packages?channel=unstable&type=packages&query={}"; # Nix packages
"@ng" = "https://noogle.dev/q?term={}"; # Nix functions
- "@no" = "https://search.nixos.org/options?type=options&query={}"; # NixOS options
+ "@no" = "https://search.nixos.org/options?channel=unstable&type=options&query={}"; # NixOS options
"@nh" = "https://home-manager-options.extranix.com/?query={}&release=master"; # Home-Manager options
"@ni" = "https://github.com/NixOS/nixpkgs/issues?q=is%3Aissue+is%3Aopen+{}"; # Nixpkgs issues
diff --git a/modules/by-name/ri/river/keymap.nix b/modules/by-name/ri/river/keymap.nix
index ca7ab69d..897d11df 100644
--- a/modules/by-name/ri/river/keymap.nix
+++ b/modules/by-name/ri/river/keymap.nix
@@ -37,16 +37,17 @@
};
cfg = config.soispha.programs.river;
+ inherit (config.soispha.services.mpd.mpc) mppPackage;
in {
soispha.programs.river.init.mappings.keymap = lib.mkIf cfg.enable (lib.fixedPoints.fix
(self: {
"<LEFT_SUPER>" = {
# Spawn standard programs
"r" = {
- "a" = mkSpawn pkgs.alacritty "" {};
+ "a" = mkSpawn' pkgs.foot "footclient" "" {};
"b" = mkSpawn pkgs.tskm "open select" {once = true;};
"k" = mkSpawn pkgs.keepassxc "" {once = true;};
- "s" = mkSpawn pkgs.signal-desktop "" {once = true;};
+ # "s" = mkSpawn pkgs.signal-desktop "" {once = true;};
"p" = mkSpawn pkgs.screenshot_persistent "" {once = true;};
"i" = mkSpawn pkgs.anki "" {once = true;};
};
@@ -68,7 +69,7 @@ in {
"x" = {
"q" = ["exit"];
"l" = mkSpawn pkgs.lock "" {once = true;};
- "h" = mkSpawn' pkgs.procps "pkill" "--signal USR1 i3bar-river" {once = true;};
+ "h" = mkSpawn pkgs.systemd-toggle "--user i3bar-river.service" {once = true;};
};
# Media control
@@ -76,9 +77,11 @@ in {
"u" = mkSpawn' pkgs.wireplumber "wpctl" "set-volume @DEFAULT_SINK@ 5%+" {};
"d" = mkSpawn' pkgs.wireplumber "wpctl" "set-volume @DEFAULT_SINK@ 5%-" {};
"m" = mkSpawn' pkgs.wireplumber "wpctl" "set-mute @DEFAULT_SINK@ toggle" {};
- "n" = mkSpawn pkgs.mpp "next" {};
- "p" = mkSpawn pkgs.mpp "prev" {};
- "t" = mkSpawn pkgs.mpp "toggle" {};
+ "n" = mkSpawn mppPackage "next" {};
+ "p" = mkSpawn mppPackage "prev" {};
+ "t" = mkSpawn mppPackage "toggle" {};
+ "g" = mkSpawn' pkgs.mpdpopm "mpdpopm" "rating inc" {once = true;};
+ "b" = mkSpawn' pkgs.mpdpopm "mpdpopm" "rating decr" {once = true;};
};
# Select tags for view.
diff --git a/modules/by-name/ri/river/module.nix b/modules/by-name/ri/river/module.nix
index 75725a8d..f9ecea0f 100644
--- a/modules/by-name/ri/river/module.nix
+++ b/modules/by-name/ri/river/module.nix
@@ -19,6 +19,8 @@
esa = lib.strings.escapeShellArg;
riverctl = lib.getExe' cfg.package "riverctl";
+ river-start = pkgs.callPackage ./river-start/package.nix {};
+
mkOutputFlags = output: flags: let
expandedFlags = builtins.concatStringsSep " " (lib.attrsets.mapAttrsToList (flag: value: "--${esa flag} ${esa value}") flags);
in ''
@@ -33,7 +35,9 @@
else builtins.map esa input
);
in "err_fail ${program} &";
- longRunningPrograms = builtins.concatStringsSep "\n" (builtins.map mkLrProgram cfg.init.backgroundStart);
+ longRunningPrograms =
+ builtins.concatStringsSep "\n" (builtins.map mkLrProgram
+ cfg.init.autoStart);
keymapFormat = pkgs.formats.json {};
keymapGenerate = name: value:
@@ -161,12 +165,14 @@ in {
'';
};
- backgroundStart = lib.mkOption {
- type = lib.types.listOf (lib.types.either lib.types.package (lib.types.listOf lib.types.str));
- description = "List of programs to start in the background";
+ autoStart = lib.mkOption {
+ type =
+ lib.types.listOf (lib.types.either lib.types.package (lib.types.listOf
+ lib.types.str));
+ description = "List of programs to be started at river start";
example = ''
[
- pkgs.gammastep
+ ''${lib.getExe pkgs.foot}
]
'';
};
@@ -197,6 +203,17 @@ in {
services.udev.packages = lib.mkIf cfg.unicodeInput.enable [externalBinaries.qmk_firmware.packages.${system}.qmk_unicode_type];
home-manager.users.soispha = {
+ xdg.desktopEntries."river" = {
+ name = "river";
+ exec = lib.getExe river-start;
+ comment = "A non-monolithic Wayland compositor";
+
+ settings = {
+ X-DesktopNames = "river";
+ Type = "Application";
+ };
+ };
+
home.sessionVariables = {
WM = "river";
XDG_CURRENT_DESKTOP = "river";
@@ -211,7 +228,8 @@ in {
};
home.packages = [
- (pkgs.callPackage ./river-start/package.nix {})
+ river-start
+ pkgs.sw
];
xdg.configFile."river/init" = {
@@ -221,14 +239,49 @@ in {
# ${text}
${other_stuff}
'';
+
+ sessionVars =
+ (builtins.attrNames config.environment.sessionVariables)
+ ++ (builtins.attrNames config.home-manager.users.soispha.home.sessionVariables)
+ ++ [
+ "WAYLAND_DISPLAY"
+ "DISPLAY"
+ "XDG_RUNTIME_DIR"
+ ];
+
+ part = acc: vars: let
+ firstTen = lib.lists.take 5 vars;
+ in
+ if firstTen == []
+ then acc
+ else part (acc ++ [firstTen]) (lib.lists.removePrefix firstTen vars);
+
+ partedSessionVars = part [] sessionVars;
+
+ mkEnvSet = prefix: vars: let
+ stringVars = builtins.concatStringsSep " " vars;
+ in ''err_fail ${riverctl} spawn "${prefix} ${stringVars}"'';
+
+ dbusEnvs =
+ builtins.map
+ (mkEnvSet "${lib.getExe' pkgs.dbus "dbus-update-activation-environment"} --verbose --systemd")
+ partedSessionVars;
+ systemdUserEnvs =
+ builtins.map
+ (mkEnvSet "systemctl --user --verbose import-environment")
+ partedSessionVars;
in
builtins.readFile ./init_base.sh
+
# bash
mkHeading "Environment variables" ''
- err_fail ${riverctl} spawn "${lib.getExe' pkgs.dbus "dbus-update-activation-environment"} --verbose --systemd SEATD_SOCK DISPLAY WAYLAND_DISPLAY DESKTOP_SESSION=river XDG_CURRENT_DESKTOP=river"
export XDG_CURRENT_DESKTOP=river DESKTOP_SESSION=river;
''
+ + mkHeading "Informing dbus about changed env-vars" (builtins.concatStringsSep "\n" dbusEnvs)
+ + mkHeading "Informing systemd user about changed env-vars" (builtins.concatStringsSep "\n" systemdUserEnvs)
+ + mkHeading "Starting graphical-session-target" ''
+ err_fail systemctl --user start nixos-fake-graphical-session.target
+ ''
+ mkHeading "Key Mappings" keymappings
+ mkHeading "Rules" ruleSetup
+ mkHeading "General Settings" generalSettings
diff --git a/modules/by-name/ss/ssh/module.nix b/modules/by-name/ss/ssh/module.nix
index f7218e36..39c203fd 100644
--- a/modules/by-name/ss/ssh/module.nix
+++ b/modules/by-name/ss/ssh/module.nix
@@ -15,16 +15,16 @@
}: let
cfg = config.soispha.programs.ssh;
- mkDefaultMatchBlock = userKnownHostsFile: {
- addKeysToAgent = "no";
- compression = true;
- controlMaster = "no";
- controlPersist = "no";
- forwardAgent = false;
- hashKnownHosts = false;
- serverAliveCountMax = 3;
- serverAliveInterval = 240;
- inherit userKnownHostsFile;
+ mkDefaultMatchBlock = UserKnownHostsFile: {
+ AddKeysToAgent = "no";
+ Compression = true;
+ ControlMaster = "no";
+ ControlPersist = "no";
+ ForwardAgent = false;
+ HashKnownHosts = false;
+ ServerAliveCountMax = 3;
+ ServerAliveInterval = 240;
+ inherit UserKnownHostsFile;
};
in {
options.soispha.programs.ssh = {
@@ -48,19 +48,35 @@ in {
enable = true;
enableDefaultConfig = false;
- matchBlocks."*" = mkDefaultMatchBlock (
- builtins.toString (pkgs.writeTextFile {
- name = "root-known-hosts";
- text = cfg.rootKnownHosts;
- })
- );
+ settings = {
+ "Host *" = mkDefaultMatchBlock (
+ builtins.toString (pkgs.writeTextFile {
+ name = "root-known-hosts";
+ text = cfg.rootKnownHosts;
+ })
+ );
+ "Host *.your-storagebox.de" = {
+ # Don't try to authenticate via password for these, because we need to use the
+ # forced commands, and they are only provided via the ssh key login.
+ # So password login is a footgun (it is, however, valid to use it for non-root
+ # use-cases)
+ PasswordAuthentication = false;
+ };
+ };
};
soispha.programs.ssh = {
enable = true;
enableDefaultConfig = false;
- matchBlocks."*" = mkDefaultMatchBlock "${config.home-manager.users.soispha.xdg.dataHome}/ssh/known_hosts";
+ settings = {
+ "Host *" = mkDefaultMatchBlock "${config.home-manager.users.soispha.xdg.dataHome}/ssh/known_hosts";
+ "Host *.your-storagebox.de" = {
+ # Port 22 is for sftp, and when we connect as user, we probably want an
+ # interactive shell.
+ Port = 23;
+ };
+ };
};
};
};
diff --git a/modules/by-name/st/starship/module.nix b/modules/by-name/st/starship/module.nix
new file mode 100644
index 00000000..a271d645
--- /dev/null
+++ b/modules/by-name/st/starship/module.nix
@@ -0,0 +1,157 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ lib,
+ pkgs,
+ config,
+ libraries,
+ ...
+}: let
+ cfg = config.soispha.programs.starship;
+in {
+ options.soispha.programs.starship = {
+ enable = libraries.base.options.mkEnable "starship";
+
+ showBattery = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ description = "Whether to show the current battery percentage in the prompt";
+ };
+ };
+
+ config.home-manager.users.soispha = lib.mkIf cfg.enable {
+ programs.starship = {
+ enable = true;
+ enableZshIntegration = true;
+ enableFishIntegration = true;
+
+ settings = {
+ add_newline = false;
+ format = lib.concatStrings [
+ "$directory"
+ "$username"
+ "\${custom.in_direnv_sandbox}"
+ "$cmd_duration"
+ "$status"
+ "$character"
+ ];
+
+ right_format = lib.concatStrings (
+ [
+ "$git_metrics"
+ "$git_branch"
+ "$git_status"
+ "$git_commit"
+ "$git_state"
+ "$time"
+ ]
+ ++ lib.optionals cfg.showBattery ["$battery"]
+ );
+
+ scan_timeout = 20;
+
+ character = {
+ # success_symbol = "[❯](bold blue)";
+ # a = "⬢";
+ success_symbol = "[](bold blue)";
+ error_symbol = "[](bold red)";
+ };
+
+ status = {
+ disabled = false;
+ format = "([($common_meaning )($status)( $signal_name)]($style) )";
+ };
+
+ time = {
+ disabled = false;
+ format = "[\\[$time\\]]($style)";
+ };
+
+ username = {
+ format = "as [$user]($style) ";
+ };
+
+ git_branch = {
+ format = "[($symbol$branch(:$remote_branch) )]($style)";
+ };
+ git_status = {
+ disabled = true;
+ format = "([\\[$ahead_behind\\]]($style) )";
+ ahead = "⇡$count";
+ diverged = "⇕⇡$ahead_count⇣$behind_count";
+ behind = "⇣$count";
+ };
+ git_metrics = {
+ disabled = true;
+ ignore_submodules = true;
+ };
+ git_state = {
+ format = "[($state( $progress_current of $progress_total))]($style) ";
+ rebase = "[rebasing](bold magenta)";
+ merge = "[merging](bold yellow)";
+ revert = "[reverting](bold blue)";
+ cherry_pick = "[picking](bold red)";
+ bisect = "[bisecting](bold red)";
+ am = "[applying](bold green)";
+ am_or_rebase = "[applying/rebasing](bold yellow)";
+ };
+ git_commit = {
+ disabled = true;
+ tag_disabled = false;
+ tag_symbol = "v";
+ format = "[(\\[$tag\\] )]($style)";
+ };
+
+ directory = {
+ truncate_to_repo = true;
+ read_only = " 󰌾";
+ before_repo_root_style = "black bold dimmed";
+ };
+
+ cmd_duration = {
+ min_time = 2000; # Milliseconds
+ style = "bold white";
+ };
+
+ custom = {
+ in_direnv_sandbox = {
+ symbol = " ";
+ style = "bold white";
+ when = ''test "$IN_DIRENV_SANDBOX" = true'';
+ shell = ["${lib.getExe pkgs.dash}"];
+ description = "Show if a shell is run in a direnv landrun sandbox";
+ };
+ };
+
+ battery = {
+ # '󰁹 '
+ # '󰂄 '
+ # '󰂃 '
+ # '󰁽 '
+ # '󰂎 '
+ display = [
+ {
+ threshold = 10;
+ style = "bold red";
+ }
+ {
+ threshold = 30;
+ style = "bold blue";
+ }
+ {
+ threshold = 50;
+ style = "bold green";
+ }
+ ];
+ };
+ };
+ };
+ };
+}
diff --git a/modules/by-name/st/steam/module.nix b/modules/by-name/st/steam/module.nix
index 87cdc709..f44aa0dd 100644
--- a/modules/by-name/st/steam/module.nix
+++ b/modules/by-name/st/steam/module.nix
@@ -19,15 +19,17 @@ in {
};
config = lib.mkIf cfg.enable {
- nixpkgs.config.allowUnfreePredicate = pkg:
- builtins.elem (lib.getName pkg) [
- # NOTE: These are not really applied. Needs to be at
- # <modules/by-name/ni/nixpkgs/config.nix> for some reason. <2025-04-25>
- "steam"
- "steam-unwrapped"
- "steam-original"
- "steam-run"
- ];
+ soispha.nixpkgs.unfreePackageNames = [
+ "steam"
+ "steam-unwrapped"
+ "steam-original"
+ "steam-run"
+ ];
+
+ soispha.services.unison.pathsToIgnore = [
+ # That is just to big to be synchronized (# TODO: Work around that <2024-08-31> )
+ "~/.local/share/Steam"
+ ];
programs.steam = {
enable = true;
diff --git a/modules/common/abstract-nord.png b/modules/by-name/sw/swaybg/images/abstract-nord.png
index 5ef498bf..5ef498bf 100644
--- a/modules/common/abstract-nord.png
+++ b/modules/by-name/sw/swaybg/images/abstract-nord.png
Binary files differ
diff --git a/modules/common/abstract-nord.png.license b/modules/by-name/sw/swaybg/images/abstract-nord.png.license
index eae6a84c..eae6a84c 100644
--- a/modules/common/abstract-nord.png.license
+++ b/modules/by-name/sw/swaybg/images/abstract-nord.png.license
diff --git a/modules/by-name/sw/swaybg/module.nix b/modules/by-name/sw/swaybg/module.nix
new file mode 100644
index 00000000..fa6a079a
--- /dev/null
+++ b/modules/by-name/sw/swaybg/module.nix
@@ -0,0 +1,35 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ config,
+ lib,
+ pkgs,
+ libraries,
+ ...
+}: let
+ cfg = config.soispha.programs.swaybg;
+in {
+ options.soispha.programs.swaybg = {
+ enable = libraries.base.options.mkEnable "swaybg";
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd.user.services."swaybg" = {
+ description = "Background display for river";
+ partOf = ["graphical-session.target"];
+ after = ["graphical-session.target"];
+ requisite = ["graphical-session.target"];
+ serviceConfig = {
+ ExecStart = "${lib.getExe pkgs.swaybg} --image ${./images/abstract-nord.png}";
+ };
+ wantedBy = ["graphical-session.target"];
+ };
+ };
+}
diff --git a/modules/by-name/sw/swayidle/module.nix b/modules/by-name/sw/swayidle/module.nix
new file mode 100644
index 00000000..9bda534f
--- /dev/null
+++ b/modules/by-name/sw/swayidle/module.nix
@@ -0,0 +1,45 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ config,
+ pkgs,
+ lib,
+ libraries,
+ ...
+}: let
+ cfg = config.soispha.programs.swayidle;
+ swaylock = lib.getExe pkgs.swaylock;
+in {
+ options.soispha.programs.swayidle = {
+ enable = libraries.base.options.mkEnable "swayidle";
+ };
+
+ config = lib.mkIf cfg.enable {
+ home-manager.users.soispha = {
+ services.swayidle = {
+ enable = true;
+ events = {
+ "before-sleep" = "${swaylock} -f ";
+ };
+
+ timeouts = [
+ {
+ timeout = 180;
+ command = "${swaylock} -fF";
+ }
+ {
+ timeout = 360;
+ command = "systemctl suspend-then-hibernate";
+ }
+ ];
+ };
+ };
+ };
+}
diff --git a/modules/by-name/ta/taskwarrior/module.nix b/modules/by-name/ta/taskwarrior/module.nix
index d757be90..d8129bdc 100644
--- a/modules/by-name/ta/taskwarrior/module.nix
+++ b/modules/by-name/ta/taskwarrior/module.nix
@@ -110,6 +110,11 @@ in {
inherit mkHook;
};
+ soispha.services.unison.pathsToIgnore = [
+ # Already synchronized by TaskChampion sync server
+ "~/.local/share/task"
+ ];
+
age.secrets.taskwarrior_sync_server_encryption_key = lib.mkIf (cfg.enable && cfg.enableAge) {
file = ./secrets/sync_server_encryption_key.age;
mode = "700";
@@ -138,7 +143,7 @@ in {
tags = true;
};
- news.version = "3.4.1";
+ news.version = "3.5.0";
regex = true;
weekstart = "Monday";
diff --git a/modules/by-name/un/unison/module.nix b/modules/by-name/un/unison/module.nix
index 8d156b00..8e943304 100644
--- a/modules/by-name/un/unison/module.nix
+++ b/modules/by-name/un/unison/module.nix
@@ -84,6 +84,17 @@ in {
};
config = lib.mkIf cfg.enable {
+ soispha.services.unison.pathsToIgnore = let
+ prefix = "/home/soispha/";
+ dataDir =
+ if lib.strings.hasPrefix prefix cfg.dataDir
+ then "~/${lib.strings.removePrefix prefix cfg.dataDir}"
+ else builtins.throw "Wrong prefix in unison";
+ in [
+ # Should not be synchronized
+ dataDir
+ ];
+
home-manager.users.soispha = {
home.sessionVariables = {
UNISON = cfg.dataDir;
diff --git a/modules/by-name/us/users/module.nix b/modules/by-name/us/users/module.nix
index 65b75d2e..d9097fe1 100644
--- a/modules/by-name/us/users/module.nix
+++ b/modules/by-name/us/users/module.nix
@@ -35,9 +35,6 @@ in {
};
config = lib.mkIf cfg.enable {
- # Ensure that the default shell of the user is actually enabled.
- programs.zsh.enable = true;
-
users = {
mutableUsers = false;
@@ -46,7 +43,7 @@ in {
isNormalUser = true;
home = "/home/soispha";
createHome = true;
- shell = pkgs.zsh;
+ shell = config.home-manager.users.soispha.programs.fish.package;
initialHashedPassword = cfg.hashedPassword;
extraGroups = cfg.groups ++ lib.optional cfg.enableDeprecatedPlugdev "plugdev";
diff --git a/modules/by-name/ve/version/module.nix b/modules/by-name/ve/version/module.nix
index fc03b107..d666695d 100644
--- a/modules/by-name/ve/version/module.nix
+++ b/modules/by-name/ve/version/module.nix
@@ -10,19 +10,19 @@
{
config,
lib,
- self,
+ sources,
...
}: let
cfg = config.soispha.version;
+
+ inherit (sources) nixpkgs;
in {
options.soispha.version = {
- enable = lib.mkEnableOption "storing the git revision in /etc/nixos_git_rev";
+ enable = lib.mkEnableOption "storing the git revision in /etc/nixpkgs-git-revision";
};
config = lib.mkIf cfg.enable {
- environment.etc.nixos_git_rev = {
- text = builtins.toString (self.longRev
- or self.lastModified
- or "unknown");
+ environment.etc.nixpkgs-git-revision = {
+ source = "${nixpkgs}/.git-revision";
};
};
}
diff --git a/modules/by-name/xd/xdg/module.nix b/modules/by-name/xd/xdg/module.nix
index 9ff71ca0..9430d82f 100644
--- a/modules/by-name/xd/xdg/module.nix
+++ b/modules/by-name/xd/xdg/module.nix
@@ -41,42 +41,70 @@
};
cfg = config.soispha.xdg;
+
+ inherit (config.home-manager.users.soispha.home) homeDirectory;
in {
options.soispha.xdg = {
enable = lib.mkEnableOption "xdg";
};
config = lib.mkIf cfg.enable {
- home-manager.users.soispha = {
- xdg = {
- configFile."xdg-desktop-portal-termfilechooser/config".source = tfcConfigFile;
+ home-manager.users.soispha.xdg = {
+ enable = true;
- desktopEntries = {
- url-handler = {
- name = "url-handler";
- genericName = "Web Browser";
- exec = "${lib.getExe url-handler} %u";
- terminal = false;
- categories = [
- "Application"
- "Network"
- "WebBrowser"
- ];
- mimeType = [
- "text/html"
- "text/xml"
- "x-scheme-handler/http"
- "x-scheme-handler/https"
- "x-scheme-handler/about"
- "x-scheme-handler/unknown"
- ];
- };
+ configFile."xdg-desktop-portal-termfilechooser/config".source = tfcConfigFile;
+
+ configHome = "${homeDirectory}/.config";
+ dataHome = "${homeDirectory}/.local/share";
+ stateHome = "${homeDirectory}/.local/state";
+ cacheHome = "${homeDirectory}/.cache";
+
+ userDirs = {
+ enable = true;
+
+ # TODO: We shouldn't build on these variables to exist, but currently my config
+ # might expect them. So let's just keep it. See here for a full explanation:
+ # https://github.com/nix-community/home-manager/pull/7937#issuecomment-3372232126
+ # <2026-04-14>
+ setSessionVariables = true;
+
+ createDirectories = true;
+
+ documents = "${homeDirectory}/documents/general";
+ download = "/tmp/download";
+ music = "${homeDirectory}/media/music";
+ pictures = "${homeDirectory}/media/pictures";
+ videos = "${homeDirectory}/media/videos";
+ projects = "${homeDirectory}/repos";
+
+ templates = null;
+ publicShare = null;
+ desktop = null;
+ };
+
+ desktopEntries = {
+ url-handler = {
+ name = "url-handler";
+ genericName = "Web Browser";
+ exec = "${lib.getExe url-handler} %u";
+ terminal = false;
+ categories = [
+ "Application"
+ "Network"
+ "WebBrowser"
+ ];
+ mimeType = [
+ "text/html"
+ "text/xml"
+ "x-scheme-handler/http"
+ "x-scheme-handler/https"
+ "x-scheme-handler/about"
+ "x-scheme-handler/unknown"
+ ];
};
};
};
- services.dbus.enable = true;
-
xdg = {
mime = {
enable = true;
@@ -98,6 +126,7 @@ in {
wlr = {
enable = true;
};
+
config = {
common = {
default = ["wlr"];
@@ -118,6 +147,8 @@ in {
};
};
+ services.dbus.enable = true;
+
systemd.user.services.xdg-desktop-portal-termfilechooser = {
serviceConfig.ExecStart = [
""
diff --git a/modules/by-name/yt/yt/module.nix b/modules/by-name/yt/yt/module.nix
index 81bacf44..f001c030 100644
--- a/modules/by-name/yt/yt/module.nix
+++ b/modules/by-name/yt/yt/module.nix
@@ -113,6 +113,10 @@ in {
config = {
home-manager.users.soispha = lib.mkIf cfg.enable {
+ home.packages = [
+ pkgs.yt
+ ];
+
xdg.configFile = {
"yt/mpv.conf".text = renderOptions mpvConf;
"yt/mpv.input.conf".text = renderBindings mpvInputConfig;
diff --git a/modules/by-name/zs/zsh/module.nix b/modules/by-name/zs/zsh/module.nix
index fee2eae3..cd2e917f 100644
--- a/modules/by-name/zs/zsh/module.nix
+++ b/modules/by-name/zs/zsh/module.nix
@@ -42,6 +42,9 @@ in {
};
config = lib.mkIf cfg.enable {
+ # Ensure that the default shell of the user is actually enabled.
+ programs.zsh.enable = true;
+
environment.variables = {
ZDOTDIR = zDotDir;
};
diff --git a/modules/common/default.nix b/modules/common/default.nix
index f9831351..4855c7ab 100644
--- a/modules/common/default.nix
+++ b/modules/common/default.nix
@@ -13,15 +13,6 @@
config,
pkgs,
lib,
- # Needed for nixos-shell
- libraries,
- modules,
- openPRsNixpkgs,
- packageSets,
- system,
- self,
- externalDependencies,
- externalBinaries,
...
}: {
# TODO(@bpeetz): Move this file to default options in their respective modules. <2025-05-31>
@@ -50,49 +41,7 @@
];
};
- nixos-shell = {
- enable = lib.mkDefault true;
- configuration = {
- specialArgs = {
- inherit
- libraries
- modules
- ;
- };
- value = lib.mkMerge [
- {
- _module.args = {
- inherit
- # extra package sources
- openPRsNixpkgs
- packageSets
- # extra information
- system
- # nix registry
- self
- externalDependencies
- # bins
- # TODO: Integrate these into `pkgs/by-name` <2024-05-22>
- externalBinaries
- ;
- };
- }
-
- {
- require = [
- ./nixos_shell_configuration.nix
-
- ../../modules
- ../../modules/common
- ];
- }
- ];
- };
- mounts = {};
- };
-
polkit.enable = true;
- power.enable = true;
xdg.enable = true;
services = {
@@ -147,27 +96,14 @@
config.home-manager.users.soispha.home.file);
in
[
- # TODO(@bpeetz): Move these to their respective modules <2025-05-09>
- # Already synchronized by TaskChampion sync server
- "~/.local/share/task"
- # Already synchronized by atuin sync server
- "~/.local/share/atuin"
+ # TODO(@bpeetz): Move that to its respective module (after migrating it to
+ # by-name) <2025-05-09>
# Already synchronized by mbsync
"~/.local/share/maildir"
-
- # Should not be synchronized
- "~/.local/share/unison"
-
- # These are just to big to be synchronized (# TODO: Work around that <2024-08-31> )
- "~/media/music/beets.old"
- "~/media/music/deerix"
- "~/.local/share/Steam"
]
++ homeManagerSymlinks;
pathsToSync = [
- "~/.local/state/mpv"
- "~/.local/state/nvim"
"~/.local/share"
"~/.local/.Trash-1000"
@@ -194,12 +130,12 @@
enable = true;
enableAge = lib.mkDefault true;
};
- cargo.enable = true;
direnv.enable = true;
git.enable = true;
imv.enable = true;
less.enable = true;
lf.enable = true;
+ ly.enable = lib.mkDefault true;
gpg.enable = true;
river = {
enable = lib.mkDefault true;
@@ -263,17 +199,10 @@
pointer-1133-49970-Logitech_Gaming_Mouse_G502 = [["pointer-accel" "0"] ["accel-profile" "none"]];
pointer-12951-6505-ZSA_Technology_Labs_Moonlander_Mark_I = [["pointer-accel" "0"] ["accel-profile" "none"]];
};
- backgroundStart = [
- # TODO(@bpeetz): Move these to systemd units/their own modules <2025-05-18>
-
- ["${lib.getExe pkgs.swaybg}" "--image" "${./abstract-nord.png}"]
- pkgs.swayidle
- pkgs.alacritty
- ];
};
};
mpv.enable = true;
- steam.enable = false;
+ steam.enable = true;
ssh.enable = true;
swaylock.enable = true;
timewarrior.enable = true;
@@ -291,6 +220,7 @@
yt.enable = true;
zathura.enable = true;
zsh.enable = true;
+ fish.enable = true;
};
version.enable = true;
diff --git a/modules/common/nixos_shell_configuration.nix b/modules/common/nixos_shell_configuration.nix
deleted file mode 100644
index 1bec0145..00000000
--- a/modules/common/nixos_shell_configuration.nix
+++ /dev/null
@@ -1,66 +0,0 @@
-# nixos-config - My current NixOS configuration
-#
-# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of my nixos-config.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{...}: {
- soispha = {
- networking = {
- enable = true;
- hostName = "lahmu";
- mode = "systemd-networkd";
- };
-
- nixos-shell.enable = false;
-
- nixpkgs = {
- enable = true;
- systemName = "x86_64-linux";
- };
-
- services = {
- unison.enable = false;
- backup = {
- storagebox.enable = false;
- local.enable = false;
- };
- };
-
- programs = {
- river.enable = false;
-
- # We don't have access to the age secrets.
- atuin.enableAge = false;
- taskwarrior.enableAge = false;
- };
-
- locale = {
- enable = true;
- keyMap = "us";
- };
-
- users = {
- enable = true;
- enableDeprecatedPlugdev = true;
-
- # Make logging in impossible.
- # (This also removes root a obvious access-point from the virtual machine)
- hashedPassword = "";
- };
- };
-
- soispha = {
- hardware = {
- enable = false;
- };
-
- disks.enable = false;
- };
-
- # We run without state
- # system.stateVersion = null;
-}
diff --git a/modules/common/projects.json b/modules/common/projects.json
index e8e9bc39..ea14d6e4 100644
--- a/modules/common/projects.json
+++ b/modules/common/projects.json
@@ -22,6 +22,7 @@
"google": {},
"health": {},
"job": {},
+ "shop": {},
"sweden": {}
}
},
@@ -65,5 +66,6 @@
"netflix": {},
"youtube": {}
}
- }
+ },
+ "uni": {}
}
diff --git a/modules/home.legacy/conf/beets/default.nix b/modules/home.legacy/conf/beets/default.nix
index 2fec6881..43d6bff9 100644
--- a/modules/home.legacy/conf/beets/default.nix
+++ b/modules/home.legacy/conf/beets/default.nix
@@ -14,7 +14,6 @@
...
}: let
plugins = import ./plugins.nix {};
-
# TODO: Enable xtractor again <2025-03-29>
# package = pkgs.beets.override {
# pluginOverrides = {
@@ -23,7 +22,6 @@
# propagatedBuildInputs = [pkgs.beetsExtraPlugins.xtractor];
# };
# };
- package = pkgs.beets;
in {
imports = [
./plugins
@@ -31,7 +29,14 @@ in {
programs.beets = {
enable = true;
- inherit package;
+ package = pkgs.python3.pkgs.toPythonApplication (pkgs.python3.pkgs.beets.override
+ {
+ ffmpeg = pkgs.ffmpeg-headless;
+ keyfinder-cli = pkgs.keyfinder-cli.override {
+ ffmpeg =
+ pkgs.ffmpeg-headless;
+ };
+ });
settings = {
library = "${config.xdg.dataHome}/beets/library.db";
@@ -105,18 +110,17 @@ in {
};
mpdIntegration = {
- enableStats = true;
enableUpdate = true;
host = config.home.sessionVariables.MPD_HOST;
};
};
- # Use the json formatter instead of the YAML one, as the YAML formatter mangles the
- # longer python inline strings.
- # YAML is a superset of JSON.
- xdg.configFile."beets/config.yaml".source =
- lib.mkForce
- ((pkgs.formats.json {}).generate
- "beets-config"
- config.programs.beets.settings);
+ # # Use the json formatter instead of the YAML one, as the YAML formatter mangles the
+ # # longer python inline strings.
+ # # YAML is a superset of JSON.
+ # xdg.configFile."beets/config.yaml".source =
+ # lib.mkForce
+ # ((pkgs.formats.json {}).generate
+ # "beets-config"
+ # config.programs.beets.settings);
}
diff --git a/modules/home.legacy/conf/beets/plugins.nix b/modules/home.legacy/conf/beets/plugins.nix
index ed78b49e..aaeab843 100644
--- a/modules/home.legacy/conf/beets/plugins.nix
+++ b/modules/home.legacy/conf/beets/plugins.nix
@@ -20,6 +20,8 @@
# Extract things from the music file
# "xtractor"
+ "musicbrainz"
+
# Calculate replay gain
"replaygain"
@@ -29,12 +31,6 @@
# Show tags on files/queries
"info"
- # Create playlist from `play_count`/`skip_count` (gathered by the `mpdstats`
- # plugin)
- # Note that this should come _before_ the `mpdupdate` plugin, to ensure that
- # `mpdupgate` can propagate changed playlist to `mpd`.
- "smartplaylist"
-
"inline"
# Warn, when importing a matching item
@@ -58,6 +54,5 @@
# Allow beets to understand deezer id's
# "deezer"
- "mpdstats" # Transfer MPD stats to beets
"mpdupdate" # Update MPD database on import
]
diff --git a/modules/home.legacy/conf/beets/plugins/default.nix b/modules/home.legacy/conf/beets/plugins/default.nix
index d22369be..2b2bd607 100644
--- a/modules/home.legacy/conf/beets/plugins/default.nix
+++ b/modules/home.legacy/conf/beets/plugins/default.nix
@@ -15,7 +15,6 @@
./inline
./lyrics
./replaygain
- ./smartplaylist
# ./xtractor
];
}
diff --git a/modules/home.legacy/conf/beets/plugins/ihate/default.nix b/modules/home.legacy/conf/beets/plugins/ihate/default.nix
index 51cb6f11..2356ec3b 100644
--- a/modules/home.legacy/conf/beets/plugins/ihate/default.nix
+++ b/modules/home.legacy/conf/beets/plugins/ihate/default.nix
@@ -11,6 +11,7 @@
programs.beets.settings.ihate = {
warn = [
"title:commentary"
+ "title:live"
"albumtype:live"
];
};
diff --git a/modules/home.legacy/conf/beets/plugins/inline/default.nix b/modules/home.legacy/conf/beets/plugins/inline/default.nix
index 564d5b4c..8dccdc20 100644
--- a/modules/home.legacy/conf/beets/plugins/inline/default.nix
+++ b/modules/home.legacy/conf/beets/plugins/inline/default.nix
@@ -11,8 +11,6 @@
programs.beets.settings.item_fields = {
primary_artist =
# python
- ''
- albumartists[0]
- '';
+ ''dict(enumerate(albumartists)).get(0, artist)'';
};
}
diff --git a/modules/home.legacy/conf/beets/plugins/mbsubmit/default.nix b/modules/home.legacy/conf/beets/plugins/mbsubmit/default.nix
deleted file mode 100644
index e69de29b..00000000
--- a/modules/home.legacy/conf/beets/plugins/mbsubmit/default.nix
+++ /dev/null
diff --git a/modules/home.legacy/conf/beets/plugins/smartplaylist/default.nix b/modules/home.legacy/conf/beets/plugins/smartplaylist/default.nix
deleted file mode 100644
index 9b52c1ad..00000000
--- a/modules/home.legacy/conf/beets/plugins/smartplaylist/default.nix
+++ /dev/null
@@ -1,42 +0,0 @@
-# nixos-config - My current NixOS configuration
-#
-# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of my nixos-config.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{config, ...}: {
- programs.beets.settings.smartplaylist = {
- relative_to = config.services.mpd.musicDirectory;
- playlist_dir = config.services.mpd.playlistDirectory;
- forward_slash = false;
-
- # Show the real m3u file paths, when running `--pretend`
- pretend_paths = true;
-
- playlists = [
- {
- name = "artists-$first_artist.m3u";
- query = "";
- }
- {
- name = "ratings-good.m3u";
- query = "rating:0.7..1.0";
- }
- {
- name = "ratings-mediocre.m3u";
- query = "rating:0.4..0.7";
- }
- {
- name = "ratings-bad.m3u";
- query = "rating:0.0..0.4";
- }
- {
- name = "not_played.m3u";
- query = "-play_count: artist:";
- }
- ];
- };
-}
diff --git a/modules/home.legacy/conf/default.nix b/modules/home.legacy/conf/default.nix
index 89502a64..cedcf71a 100644
--- a/modules/home.legacy/conf/default.nix
+++ b/modules/home.legacy/conf/default.nix
@@ -13,7 +13,6 @@
./btop
./dconf
./gammastep
- ./gtk
./himalaya
./keepassxc
./mail
@@ -23,7 +22,5 @@
./npm
./prusa_slicer
./python
- ./starship
- ./swayidle
];
}
diff --git a/modules/home.legacy/conf/gtk/default.nix b/modules/home.legacy/conf/gtk/default.nix
deleted file mode 100644
index 68bef531..00000000
--- a/modules/home.legacy/conf/gtk/default.nix
+++ /dev/null
@@ -1,65 +0,0 @@
-# nixos-config - My current NixOS configuration
-#
-# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of my nixos-config.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{
- config,
- pkgs,
- ...
-}: {
- gtk = {
- enable = true;
- theme = {
- package = pkgs.nordic;
- name = "Nordic";
- };
- cursorTheme = {
- package = pkgs.nordzy-cursor-theme;
- name = "Nordzy-cursors";
- };
- iconTheme = {
- package = pkgs.nordzy-icon-theme;
- name = "Nordzy-icon";
- };
- font = {
- name = "Noto Sans";
- size = 10;
- };
- gtk2 = {
- configLocation = "${config.xdg.configHome}/gtk-2.0/gtkrc";
- # extraConfig = ''
- # gtk-enable-animations=1
- # gtk-primary-button-warps-slider=0
- # gtk-toolbar-style=3
- # gtk-menu-images=1
- # gtk-button-images=1
- #
- # '';
- };
- gtk3 = {
- # extraConfig = ''
- # gtk-button-images=true
- # gtk-decoration-layout=icon:minimize,maximize,close
- # gtk-enable-animations=true
- # gtk-menu-images=true
- # gtk-primary-button-warps-slider=false
- # gtk-toolbar-style=3
- # #gtk-modules=window-decorations-gtk-module:colorreload-gtk-module
- # '';
- extraCss = "";
- };
- gtk4 = {
- # extraConfig = ''
- # gtk-application-prefer-dark-theme=true
- # gtk-decoration-layout=icon:minimize,maximize,close
- # gtk-enable-animations=true
- # gtk-primary-button-warps-slider=false
- # '';
- };
- };
-}
diff --git a/modules/home.legacy/conf/starship/default.nix b/modules/home.legacy/conf/starship/default.nix
deleted file mode 100644
index 6a6938f7..00000000
--- a/modules/home.legacy/conf/starship/default.nix
+++ /dev/null
@@ -1,143 +0,0 @@
-# nixos-config - My current NixOS configuration
-#
-# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of my nixos-config.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{
- lib,
- nixosConfig,
- pkgs,
- ...
-}: {
- programs.starship = {
- enable = true;
- enableZshIntegration = true;
-
- settings = {
- add_newline = false;
- format = lib.concatStrings [
- "$directory"
- "$username"
- "\${custom.in_nixos_shell}"
- "$cmd_duration"
- "$status"
- "$character"
- ];
-
- right_format = lib.concatStrings (
- [
- "$git_metrics"
- "$git_branch"
- "$git_status"
- "$git_commit"
- "$git_state"
- "$time"
- ]
- ++ lib.optional
- nixosConfig.soispha.laptop.enable
- "$battery"
- );
-
- scan_timeout = 20;
-
- character = {
- # success_symbol = "[❯](bold blue)";
- # a = "⬢";
- success_symbol = "[](bold blue)";
- error_symbol = "[](bold red)";
- };
-
- status = {
- disabled = false;
- format = "([($common_meaning )($status)( $signal_name)]($style) )";
- };
-
- time = {
- disabled = false;
- format = "[\\[$time\\]]($style)";
- };
-
- username = {
- format = "as [$user]($style) ";
- };
-
- git_branch = {
- format = "[($symbol$branch(:$remote_branch) )]($style)";
- };
- git_status = {
- disabled = true;
- format = "([\\[$ahead_behind\\]]($style) )";
- ahead = "⇡$count";
- diverged = "⇕⇡$ahead_count⇣$behind_count";
- behind = "⇣$count";
- };
- git_metrics = {
- disabled = true;
- ignore_submodules = true;
- };
- git_state = {
- format = "[\($state( $progress_current of $progress_total)\)]($style) ";
- rebase = "[rebasing](bold magenta)";
- merge = "[merging](bold yellow)";
- revert = "[reverting](bold blue)";
- cherry_pick = "[picking](bold red)";
- bisect = "[bisecting](bold red)";
- am = "[applying](bold green)";
- am_or_rebase = "[applying/rebasing](bold yellow)";
- };
- git_commit = {
- disabled = true;
- tag_disabled = false;
- tag_symbol = "v";
- format = "[(\\[$tag\\] )]($style)";
- };
-
- directory = {
- truncate_to_repo = true;
- read_only = " 󰌾";
- before_repo_root_style = "black bold dimmed";
- };
-
- cmd_duration = {
- min_time = 2000; # Milliseconds
- style = "bold white";
- };
-
- custom = {
- in_nixos_shell = {
- symbol = "VM ";
- style = "bold white";
- when = ''test "$IN_NIXOS_SHELL" = true '';
- shell = ["${lib.getExe pkgs.dash}"];
- description = "Show if a shell is run in a vm";
- };
- };
-
- battery = {
- # '󰁹 '
- # '󰂄 '
- # '󰂃 '
- # '󰁽 '
- # '󰂎 '
- display = [
- {
- threshold = 10;
- style = "bold red";
- }
- {
- threshold = 30;
- style = "bold blue";
- }
- {
- threshold = 50;
- style = "bold green";
- }
- ];
- };
- };
- };
-}
diff --git a/modules/home.legacy/conf/swayidle/config b/modules/home.legacy/conf/swayidle/config
deleted file mode 100644
index a48f670b..00000000
--- a/modules/home.legacy/conf/swayidle/config
+++ /dev/null
@@ -1,5 +0,0 @@
-timeout 180 'swaylock -fF'
-
-timeout 360 'systemctl suspend-then-hibernate'
-
-before-sleep 'swaylock -f'
diff --git a/modules/home.legacy/conf/swayidle/config.license b/modules/home.legacy/conf/swayidle/config.license
deleted file mode 100644
index eae6a84c..00000000
--- a/modules/home.legacy/conf/swayidle/config.license
+++ /dev/null
@@ -1,9 +0,0 @@
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/modules/home.legacy/conf/swayidle/default.nix b/modules/home.legacy/conf/swayidle/default.nix
deleted file mode 100644
index 4483c8b9..00000000
--- a/modules/home.legacy/conf/swayidle/default.nix
+++ /dev/null
@@ -1,35 +0,0 @@
-# nixos-config - My current NixOS configuration
-#
-# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of my nixos-config.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{...}: {
- # TODO: This fails to hibernate when the hardware swap was not previously activated. <2025-04-04>
- xdg.configFile."swayidle/config".source = ./config;
-
- # services.swayidle = {
- # enable = true;
- # events = [
- # {
- # event = "before-sleep";
- # command = "${pkgs.swaylock}/bin/swaylock -f ";
- # }
- # ];
- # timeouts = [
- # {
- # timeout = 180;
- # command = "${pkgs.swaylock}/bin/swaylock -fFu ";
- # }
- # {
- # timeout = 360;
- # # TODO: systemctl is installed?
- # command = "systemctl suspend-then-hibernate";
- # }
- # ];
- # # systemdTarget = ""; # TODO: this might be usefull
- # };
-}
diff --git a/modules/home.legacy/default.nix b/modules/home.legacy/default.nix
index 8fb209f3..7cc9bb45 100644
--- a/modules/home.legacy/default.nix
+++ b/modules/home.legacy/default.nix
@@ -7,17 +7,7 @@
#
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{config, ...}: let
- inherit (config.home) homeDirectory;
-
- # xdg
- configHome = "${homeDirectory}/.config";
- dataHome = "${homeDirectory}/.local/share";
- stateHome = "${homeDirectory}/.local/state";
- cacheHome = "${homeDirectory}/.cache";
- binHome = "${homeDirectory}/.local/bin";
- # TODO: add XDG_RUNTIME_DIR
-in {
+{...}: {
imports = [
./conf
./files
@@ -26,27 +16,4 @@ in {
# I don't know what this does, but I've seen it a lot online, so it should be good, right?
programs.home-manager.enable = true;
-
- xdg = {
- enable = true;
- inherit configHome dataHome stateHome cacheHome; #binHome; # TODO: add binHome, when the standard is extended
-
- /*
- TODO: add this
- desktopEntries = {};
- */
-
- userDirs = {
- enable = true;
- createDirectories = true;
- desktop = null;
- documents = "${homeDirectory}/documents/general";
- download = "/tmp/download";
- music = "${homeDirectory}/media/music";
- pictures = "${homeDirectory}/media/pictures";
- videos = "${homeDirectory}/media/videos";
- templates = "${homeDirectory}/media/templates";
- publicShare = "${homeDirectory}/media/public";
- };
- };
}
diff --git a/modules/home.legacy/pkgs/default.nix b/modules/home.legacy/pkgs/default.nix
index 5ab1e73b..840ffb19 100644
--- a/modules/home.legacy/pkgs/default.nix
+++ b/modules/home.legacy/pkgs/default.nix
@@ -16,26 +16,13 @@
# TODO: Remove this whole file, and move each package to a separate module. <2024-11-16>
with pkgs; let
Gui = {
- Terminals = [
- alacritty # default terminal
- ];
-
Misc = [
keepassxc # password manager
];
};
TuiCli = {
- Social = [
- iamb # best tui matrix client (as of today)
- ];
-
- Pdfs = [
- con2pdf # Scanner implementation
- ];
-
Misc = [
- killall # kill a application by name
bc # Smart calculator
aumo # Automatic mount
jq # Json parser
@@ -60,16 +47,6 @@ with pkgs; let
];
};
- Media = {
- View = [
- zathura # PDF viewer
- ];
-
- YouTube = [
- yt # A command line YouTube client
- ];
- };
-
Hardware = {
Storage = [
# TODO: smartmontools # Control and monitor S.M.A.R.T. enabled ATA and SCSI Hard Drives
@@ -103,16 +80,6 @@ with pkgs; let
ripgrep # A search tool that combines the usability of ag with the raw speed of grep
file # Show information about a file
];
-
- Programming = {
- GeneralTools = [
- stamp # Add a license header to a file
- git # the fast distributed version control system
- git-edit-index # Allows you to edit the indexed version of a file
- git-cm # A wrapper that re-adds the last commit's subject
- glow # Command-line markdown renderer
- ];
- };
};
mapFun = x:
if builtins.isAttrs x
diff --git a/notes/beets_import.md b/notes/beets_import.md
deleted file mode 100644
index 6725192e..00000000
--- a/notes/beets_import.md
+++ /dev/null
@@ -1,20 +0,0 @@
-<!--
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
--->
-
-# How to import an artist into beets
-
-1. Download said artist (`deerix`)
-1. Remove the useless/bad albums of the downloaded artist
-1. Check for name duplicates (`./find_duplicates`)
-1. Import (`beet import <artist>`)
-1. Remove duplicated files (`beet duplicates artist:<artist>`)
-1. Search for canonical name duplicates (`beet list artist:<artist> <leftover name>`)
diff --git a/pkgs/by-name/at/atuin-dvorak/package.nix b/pkgs/by-name/at/atuin-dvorak/package.nix
deleted file mode 100644
index d3f92961..00000000
--- a/pkgs/by-name/at/atuin-dvorak/package.nix
+++ /dev/null
@@ -1,32 +0,0 @@
-# nixos-config - My current NixOS configuration
-#
-# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-# SPDX-License-Identifier: GPL-3.0-or-later
-#
-# This file is part of my nixos-config.
-#
-# You should have received a copy of the License along with this program.
-# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-{atuin}:
-atuin.overrideAttrs (finalAttrs: previousAttrs: {
- pname = previousAttrs.pname + "-dvorak";
- patches =
- previousAttrs.patches
- ++ [
- ./set-dvorak-keybindings.patch
- ];
-
- # atuin's default features include 'check-updates', which do not make sense
- # for distribution builds. List all other default features.
- buildNoDefaultFeatures = true;
- buildFeatures = [
- "client"
- # "sync"
- # "server"
- "clipboard"
- # "daemon"
- ];
-
- # The checks don't compile without the `server` feature
- doCheck = false;
-})
diff --git a/pkgs/by-name/at/atuin-dvorak/set-dvorak-keybindings.patch b/pkgs/by-name/at/atuin-dvorak/set-dvorak-keybindings.patch
deleted file mode 100644
index 9ab3700a..00000000
--- a/pkgs/by-name/at/atuin-dvorak/set-dvorak-keybindings.patch
+++ /dev/null
@@ -1,37 +0,0 @@
-From f763aef8094c12293570119d9ff0922356dd8c79 Mon Sep 17 00:00:00 2001
-From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
-Date: Fri, 18 Oct 2024 23:03:01 +0200
-Subject: [PATCH] fix(client): Change the keymaps to support dvorka vim keys
-
----
- crates/atuin/src/command/client/search/interactive.rs | 6 +++---
- 1 file changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs
-index c87fff1c..19e965fe 100644
---- a/crates/atuin/src/command/client/search/interactive.rs
-+++ b/crates/atuin/src/command/client/search/interactive.rs
-@@ -322,17 +322,17 @@ impl State {
- self.keymap_mode = KeymapMode::VimInsert;
- return InputAction::Continue;
- }
-- KeyCode::Char('j') if !ctrl => {
-+ KeyCode::Char('t') if !ctrl => {
- return self.handle_search_down(settings, true);
- }
-- KeyCode::Char('k') if !ctrl => {
-+ KeyCode::Char('n') if !ctrl => {
- return self.handle_search_up(settings, true);
- }
- KeyCode::Char('h') if !ctrl => {
- self.search.input.left();
- return InputAction::Continue;
- }
-- KeyCode::Char('l') if !ctrl => {
-+ KeyCode::Char('s') if !ctrl => {
- self.search.input.right();
- return InputAction::Continue;
- }
---
-2.46.0
-
diff --git a/pkgs/by-name/at/atuin-dvorak/set-dvorak-keybindings.patch.license b/pkgs/by-name/at/atuin-dvorak/set-dvorak-keybindings.patch.license
deleted file mode 100644
index eae6a84c..00000000
--- a/pkgs/by-name/at/atuin-dvorak/set-dvorak-keybindings.patch.license
+++ /dev/null
@@ -1,9 +0,0 @@
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/pkgs/by-name/au/aumo/aumo.sh b/pkgs/by-name/au/aumo/aumo.sh
index dba5ef63..824d817a 100755
--- a/pkgs/by-name/au/aumo/aumo.sh
+++ b/pkgs/by-name/au/aumo/aumo.sh
@@ -25,18 +25,24 @@ usage() {
echo "Usage: $NAME mount|unmount"
}
+is_label_mounted() {
+ label="$1"
+
+ findmnt --output label --json | jq --arg label "$label" '.filesystems | map(.label) | sort | unique | map(select(. != null)) | index($label) != null'
+}
+
get_mounted_labels() {
findmnt --output label --json | jq '.filesystems | map(.label) | sort | unique | map(select(. != null))'
}
get_unmounted_labels() {
first=true
- find /dev/disk/by-label -printf "%P\n" | while read -r label; do
- if ! get_mounted_labels | jq 'join("\n")' --raw-output | grep "$label" --quiet; then
+ fd . /dev/disk/by-label --format "{/.}" | while read -r label; do
+ if ! [ "$(is_label_mounted "$label")" = true ]; then
if [ "$first" = "true" ]; then
first=false
else
- printf "|"
+ printf "\0"
fi
printf "%s" "$label"
fi
@@ -44,13 +50,13 @@ get_unmounted_labels() {
}
unmounting() {
- disk_name="$(get_mounted_labels | jq 'join("|")' --join-output | rofi -sep "|" -dmenu -p "Select disk to unmount")"
+ disk_name="$(get_mounted_labels | jq 'join("\u0000")' --join-output | rofi -sep "\0" -dmenu -p "Select disk to unmount" | sed 's/ /\\x20/')"
udisksctl unmount --block-device "/dev/disk/by-label/$disk_name"
}
mounting() {
- disk_name="$(get_unmounted_labels | rofi -sep "|" -dmenu -p "Select disk to mount")"
+ disk_name="$(get_unmounted_labels | rofi -sep "\0" -dmenu -p "Select disk to mount")"
udisksctl mount --block-device "/dev/disk/by-label/$disk_name"
}
diff --git a/pkgs/by-name/au/aumo/package.nix b/pkgs/by-name/au/aumo/package.nix
index 8132a15a..c3c2f3ca 100644
--- a/pkgs/by-name/au/aumo/package.nix
+++ b/pkgs/by-name/au/aumo/package.nix
@@ -10,12 +10,13 @@
{
writeShellApplication,
# Dependencies
+ fd,
udisks,
- findutils,
rofi,
jq,
gnugrep,
util-linux,
+ gnused,
}:
writeShellApplication {
name = "aumo";
@@ -23,10 +24,11 @@ writeShellApplication {
inheritPath = false;
runtimeInputs = [
udisks
- findutils
+ fd
rofi
jq
gnugrep
util-linux # for findmnt
+ gnused
];
}
diff --git a/pkgs/by-name/co/con2pdf/con2pdf.sh b/pkgs/by-name/co/con2pdf/con2pdf.sh
index ebe35ad3..5737bc82 100755
--- a/pkgs/by-name/co/con2pdf/con2pdf.sh
+++ b/pkgs/by-name/co/con2pdf/con2pdf.sh
@@ -12,6 +12,12 @@
# TODO(@bpeetz): This should probably be rewritten in rust. <2025-04-14>
+
+# shellcheck disable=SC2317,SC2329
+
+echo "Rewrite this script in something else then shell (or remove the dependency on the sh-lib)"
+exit 2
+
NAME="con2pdf"
help() {
cat <<EOF
diff --git a/pkgs/by-name/di/direnv-patched/0001-rc-Also-consider-.nenvrc-files.patch b/pkgs/by-name/di/direnv-patched/0001-rc-Also-consider-.nenvrc-files.patch
new file mode 100644
index 00000000..fa2716f4
--- /dev/null
+++ b/pkgs/by-name/di/direnv-patched/0001-rc-Also-consider-.nenvrc-files.patch
@@ -0,0 +1,28 @@
+From c7fd4ebf40f2cec9ce23c4227fc48402c0469e69 Mon Sep 17 00:00:00 2001
+From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
+Date: Thu, 13 Aug 2026 17:47:06 +0200
+Subject: [PATCH] rc: Also consider `.nenvrc` files
+
+---
+ internal/cmd/rc.go | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/internal/cmd/rc.go b/internal/cmd/rc.go
+index 4167ecf..d7f5886 100644
+--- a/internal/cmd/rc.go
++++ b/internal/cmd/rc.go
+@@ -391,9 +391,9 @@ func allow(path string, allowPath string) (err error) {
+
+ func findEnvUp(searchDir string, loadDotenv bool) (path string) {
+ if loadDotenv {
+- return findUp(searchDir, ".envrc", ".env")
++ return findUp(searchDir, ".envrc", ".nenvrc", ".env")
+ }
+- return findUp(searchDir, ".envrc")
++ return findUp(searchDir, ".envrc", ".nenvrc")
+ }
+
+ func findUp(searchDir string, fileNames ...string) (path string) {
+--
+2.55.0
+
diff --git a/pkgs/by-name/di/direnv-patched/package.nix b/pkgs/by-name/di/direnv-patched/package.nix
new file mode 100644
index 00000000..073be614
--- /dev/null
+++ b/pkgs/by-name/di/direnv-patched/package.nix
@@ -0,0 +1,28 @@
+{direnv}:
+direnv.overrideAttrs (final: prev: {
+ pname = "${prev.pname}-patched";
+
+ buildPhase =
+ ''
+ runHook preBuild
+ ''
+ + prev.buildPhase
+ + ''
+ runHook postBuild
+ '';
+
+ installPhase =
+ ''
+ runHook preInstall
+ ''
+ + prev.installPhase
+ + ''
+ runHook postInstall
+ '';
+
+ patches =
+ (prev.patches or [])
+ ++ [
+ ./0001-rc-Also-consider-.nenvrc-files.patch
+ ];
+})
diff --git a/pkgs/by-name/fi/fish-patched/Cargo.lock b/pkgs/by-name/fi/fish-patched/Cargo.lock
new file mode 100644
index 00000000..1814ae12
--- /dev/null
+++ b/pkgs/by-name/fi/fish-patched/Cargo.lock
@@ -0,0 +1,3551 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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 = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "assert_matches"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
+
+[[package]]
+name = "async-trait"
+version = "0.1.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "axum"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
+dependencies = [
+ "axum-core",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde_core",
+ "sync_wrapper",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "http-body-util",
+ "mime",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "beef"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1"
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "bstr"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
+dependencies = [
+ "memchr",
+ "serde_core",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
+
+[[package]]
+name = "cc"
+version = "1.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "rand_core",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_complete"
+version = "4.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19"
+dependencies = [
+ "clap",
+ "clap_lex",
+ "is_executable",
+ "shlex",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "cmov"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "ctutils"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e"
+dependencies = [
+ "cmov",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer 0.10.4",
+ "crypto-common 0.1.6",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "const-oid",
+ "crypto-common 0.2.2",
+ "ctutils",
+]
+
+[[package]]
+name = "directories"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "dotenvy"
+version = "0.15.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
+
+[[package]]
+name = "either"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "etcetera"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
+dependencies = [
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "eyre"
+version = "0.6.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08309dbcc659c5549a24ddb9b27027640641b282ef5768267c7e675558986a3"
+dependencies = [
+ "autocfg",
+ "indenter",
+ "once_cell",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
+
+[[package]]
+name = "fish"
+version = "4.8.1"
+dependencies = [
+ "assert_matches",
+ "bitflags",
+ "cc",
+ "cfg-if",
+ "errno",
+ "fish-build-helper",
+ "fish-build-man-pages",
+ "fish-color",
+ "fish-common",
+ "fish-fallback",
+ "fish-feature-flags",
+ "fish-fluent",
+ "fish-fluent-extraction",
+ "fish-gettext",
+ "fish-gettext-extraction",
+ "fish-gettext-mo-file-parser",
+ "fish-localization",
+ "fish-printf",
+ "fish-tempfile",
+ "fish-util",
+ "fish-wcstringutil",
+ "fish-wgetopt",
+ "fish-widecharwidth",
+ "fish-widestring",
+ "fluent",
+ "itertools",
+ "libc",
+ "lru",
+ "macro_rules_attribute",
+ "nix 0.31.3",
+ "num-traits",
+ "once_cell",
+ "pcre2",
+ "phf_codegen",
+ "portable-atomic",
+ "rand",
+ "rsconf",
+ "rust-embed",
+ "rustc_version",
+ "serial_test",
+ "strum_macros",
+ "tokio",
+ "turtle-api",
+ "unix_path",
+ "uuid",
+ "xterm-color",
+]
+
+[[package]]
+name = "fish-build-helper"
+version = "0.0.0"
+dependencies = [
+ "rsconf",
+]
+
+[[package]]
+name = "fish-build-man-pages"
+version = "0.0.0"
+dependencies = [
+ "fish-build-helper",
+ "rsconf",
+]
+
+[[package]]
+name = "fish-color"
+version = "0.0.0"
+dependencies = [
+ "fish-common",
+ "fish-widestring",
+]
+
+[[package]]
+name = "fish-common"
+version = "0.0.0"
+dependencies = [
+ "bitflags",
+ "fish-build-helper",
+ "fish-feature-flags",
+ "fish-widestring",
+ "libc",
+ "nix 0.31.3",
+ "rsconf",
+]
+
+[[package]]
+name = "fish-fallback"
+version = "0.0.0"
+dependencies = [
+ "fish-build-helper",
+ "fish-common",
+ "fish-widecharwidth",
+ "fish-widestring",
+ "libc",
+ "rsconf",
+ "widestring",
+]
+
+[[package]]
+name = "fish-feature-flags"
+version = "0.0.0"
+dependencies = [
+ "fish-widestring",
+]
+
+[[package]]
+name = "fish-fluent"
+version = "0.0.0"
+dependencies = [
+ "cfg-if",
+ "fish-build-helper",
+ "fish-fluent-extraction",
+ "fish-localization",
+ "fluent",
+ "fluent-ftl-tools",
+ "rust-embed",
+ "unic-langid",
+ "widestring",
+]
+
+[[package]]
+name = "fish-fluent-extraction"
+version = "0.0.0"
+dependencies = [
+ "fish-tempfile",
+ "fluent-ftl-tools",
+ "fluent-syntax",
+ "proc-macro2",
+ "rsconf",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "fish-gettext"
+version = "0.0.0"
+dependencies = [
+ "fish-gettext-maps",
+ "fish-localization",
+ "phf",
+]
+
+[[package]]
+name = "fish-gettext-extraction"
+version = "0.0.0"
+dependencies = [
+ "fish-tempfile",
+ "proc-macro2",
+ "rsconf",
+]
+
+[[package]]
+name = "fish-gettext-maps"
+version = "0.0.0"
+dependencies = [
+ "fish-build-helper",
+ "fish-gettext-mo-file-parser",
+ "fish-localization",
+ "phf",
+ "phf_codegen",
+ "rsconf",
+]
+
+[[package]]
+name = "fish-gettext-mo-file-parser"
+version = "0.0.0"
+
+[[package]]
+name = "fish-localization"
+version = "0.0.0"
+dependencies = [
+ "fish-build-helper",
+ "phf_shared",
+]
+
+[[package]]
+name = "fish-printf"
+version = "0.2.1"
+dependencies = [
+ "assert_matches",
+ "libc",
+ "unicode-segmentation",
+ "unicode-width 0.2.2",
+ "widestring",
+]
+
+[[package]]
+name = "fish-tempfile"
+version = "0.0.0"
+dependencies = [
+ "nix 0.31.3",
+ "rand",
+]
+
+[[package]]
+name = "fish-util"
+version = "0.0.0"
+dependencies = [
+ "errno",
+ "fish-widestring",
+ "libc",
+ "nix 0.31.3",
+ "rand",
+]
+
+[[package]]
+name = "fish-wcstringutil"
+version = "0.0.0"
+dependencies = [
+ "fish-build-helper",
+ "fish-fallback",
+ "fish-widestring",
+ "rsconf",
+]
+
+[[package]]
+name = "fish-wgetopt"
+version = "0.0.0"
+dependencies = [
+ "assert_matches",
+ "fish-wcstringutil",
+ "fish-widestring",
+]
+
+[[package]]
+name = "fish-widecharwidth"
+version = "0.0.0"
+
+[[package]]
+name = "fish-widestring"
+version = "0.0.0"
+dependencies = [
+ "libc",
+ "unicode-width 0.2.2",
+ "widestring",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
+
+[[package]]
+name = "fluent"
+version = "0.17.0"
+source = "git+https://github.com/danielrainer/fluent-rs?rev=cf712bced280b217b6307edabc2089b3e57204ab#cf712bced280b217b6307edabc2089b3e57204ab"
+dependencies = [
+ "fluent-bundle",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-bundle"
+version = "0.16.0"
+source = "git+https://github.com/danielrainer/fluent-rs?rev=cf712bced280b217b6307edabc2089b3e57204ab#cf712bced280b217b6307edabc2089b3e57204ab"
+dependencies = [
+ "fluent-langneg",
+ "fluent-syntax",
+ "intl-memoizer",
+ "intl_pluralrules",
+ "rustc-hash",
+ "self_cell",
+ "smallvec",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-ftl-tools"
+version = "0.1.0"
+source = "git+https://codeberg.org/danielrainer/fluent-ftl-tools?rev=5917664c8f2e4928ef1e480ff5c13bbe1e226066#5917664c8f2e4928ef1e480ff5c13bbe1e226066"
+dependencies = [
+ "anyhow",
+ "fluent",
+ "fluent-syntax",
+ "gettext-po-file-parser",
+ "nix 0.30.1",
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-langneg"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "fluent-syntax"
+version = "0.12.0"
+source = "git+https://github.com/danielrainer/fluent-rs?rev=cf712bced280b217b6307edabc2089b3e57204ab#cf712bced280b217b6307edabc2089b3e57204ab"
+dependencies = [
+ "memchr",
+ "thiserror",
+]
+
+[[package]]
+name = "flume"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "spin",
+]
+
+[[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 = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-intrusive"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
+dependencies = [
+ "futures-core",
+ "lock_api",
+ "parking_lot",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+]
+
+[[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",
+ "rand_core",
+]
+
+[[package]]
+name = "gettext-po-file-parser"
+version = "0.0.0"
+source = "git+https://codeberg.org/danielrainer/fluent-ftl-tools?rev=5917664c8f2e4928ef1e480ff5c13bbe1e226066#5917664c8f2e4928ef1e480ff5c13bbe1e226066"
+dependencies = [
+ "anyhow",
+]
+
+[[package]]
+name = "globset"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "h2"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "839c0e8a181239723652be9062bb56ca5bf5f64011f73b623f6f4fc59086a228"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[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 = "hashlink"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
+dependencies = [
+ "hashbrown 0.16.1",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+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 = "hkdf"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018"
+dependencies = [
+ "hmac",
+]
+
+[[package]]
+name = "hmac"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f"
+dependencies = [
+ "digest 0.11.3",
+]
+
+[[package]]
+name = "http"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "httpdate"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
+
+[[package]]
+name = "hybrid-array"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "hyper"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-timeout"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
+dependencies = [
+ "hyper",
+ "hyper-util",
+ "pin-project-lite",
+ "tokio",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "libc",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+
+[[package]]
+name = "icu_properties"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+dependencies = [
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+
+[[package]]
+name = "icu_provider"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "ignore"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b009b6744c1445efd7244084e25e498636412effb6760b55067553baa925cc7"
+dependencies = [
+ "crossbeam-deque",
+ "globset",
+ "log",
+ "memchr",
+ "regex-automata",
+ "same-file",
+ "walkdir",
+ "winapi-util",
+]
+
+[[package]]
+name = "indenter"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.1",
+]
+
+[[package]]
+name = "intl-memoizer"
+version = "0.5.3"
+source = "git+https://github.com/danielrainer/fluent-rs?rev=cf712bced280b217b6307edabc2089b3e57204ab#cf712bced280b217b6307edabc2089b3e57204ab"
+dependencies = [
+ "type-map",
+ "unic-langid",
+]
+
+[[package]]
+name = "intl_pluralrules"
+version = "7.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
+dependencies = [
+ "unic-langid",
+]
+
+[[package]]
+name = "is_executable"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "jobserver"
+version = "0.1.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
+dependencies = [
+ "getrandom 0.4.3",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "libredox"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "libsqlite3-sys"
+version = "0.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
+dependencies = [
+ "cc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litemap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+
+[[package]]
+name = "logos"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154"
+dependencies = [
+ "logos-derive 0.15.1",
+]
+
+[[package]]
+name = "logos"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb2c55a318a87600ea870ff8c2012148b44bf18b74fad48d0f835c38c7d07c5f"
+dependencies = [
+ "logos-derive 0.16.1",
+]
+
+[[package]]
+name = "logos-codegen"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c"
+dependencies = [
+ "beef",
+ "fnv",
+ "lazy_static",
+ "proc-macro2",
+ "quote",
+ "regex-syntax",
+ "rustc_version",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "logos-codegen"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58b3ffaa284e1350d017a57d04ada118c4583cf260c8fb01e0fe28a2e9cf8970"
+dependencies = [
+ "fnv",
+ "proc-macro2",
+ "quote",
+ "regex-automata",
+ "regex-syntax",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "logos-derive"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470"
+dependencies = [
+ "logos-codegen 0.15.1",
+]
+
+[[package]]
+name = "logos-derive"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52d3a9855747c17eaf4383823f135220716ab49bea5fbea7dd42cc9a92f8aa31"
+dependencies = [
+ "logos-codegen 0.16.1",
+]
+
+[[package]]
+name = "lru"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a"
+dependencies = [
+ "hashbrown 0.17.1",
+]
+
+[[package]]
+name = "macro_rules_attribute"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3ae8f6d608c795738406608304d30a2dfbdc8e58e44f7ba43236da5208ded3c"
+dependencies = [
+ "macro_rules_attribute-proc_macro",
+ "pastey",
+]
+
+[[package]]
+name = "macro_rules_attribute-proc_macro"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc04a4c58212d57930a24bf47d3fa87485264a3a054e9c10e042eb373573ad3c"
+
+[[package]]
+name = "matchit"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
+
+[[package]]
+name = "md-5"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
+dependencies = [
+ "cfg-if",
+ "digest 0.11.3",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "miette"
+version = "7.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
+dependencies = [
+ "cfg-if",
+ "miette-derive",
+ "unicode-width 0.1.14",
+]
+
+[[package]]
+name = "miette-derive"
+version = "7.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
+[[package]]
+name = "mio"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
+dependencies = [
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "multimap"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
+
+[[package]]
+name = "nix"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "nix"
+version = "0.31.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
+name = "num-conv"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "objc2-system-configuration"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396"
+dependencies = [
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "pastey"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
+
+[[package]]
+name = "pcre2"
+version = "0.2.9"
+source = "git+https://github.com/fish-shell/rust-pcre2?tag=0.2.9-utf32#85b7afba1a9d9bd445779800e5bcafeb732e4421"
+dependencies = [
+ "libc",
+ "log",
+ "pcre2-sys",
+]
+
+[[package]]
+name = "pcre2-sys"
+version = "0.2.9"
+source = "git+https://github.com/fish-shell/rust-pcre2?tag=0.2.9-utf32#85b7afba1a9d9bd445779800e5bcafeb732e4421"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "petgraph"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
+dependencies = [
+ "fixedbitset",
+ "hashbrown 0.15.5",
+ "indexmap",
+]
+
+[[package]]
+name = "phf"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
+dependencies = [
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
+dependencies = [
+ "fastrand",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
+
+[[package]]
+name = "portable-atomic"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
+
+[[package]]
+name = "potential_utf"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "prettyplease"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prost"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
+dependencies = [
+ "heck",
+ "itertools",
+ "log",
+ "multimap",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "pulldown-cmark",
+ "pulldown-cmark-to-cmark",
+ "regex",
+ "syn 2.0.119",
+ "tempfile",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "prost-reflect"
+version = "0.16.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01b80ea363c31af2de2b92e3c07ed1156628f7838c4afb4df75ee78a37fedbd1"
+dependencies = [
+ "logos 0.16.1",
+ "miette",
+ "prost",
+ "prost-types",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "protox"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f25a07a73c6717f0b9bbbd685918f5df9815f7efba450b83d9c9dea41f0e3a1"
+dependencies = [
+ "bytes",
+ "miette",
+ "prost",
+ "prost-reflect",
+ "prost-types",
+ "protox-parse",
+ "thiserror",
+]
+
+[[package]]
+name = "protox-parse"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "072eee358134396a4643dff81cfff1c255c9fbd3fb296be14bdb6a26f9156366"
+dependencies = [
+ "logos 0.15.1",
+ "miette",
+ "prost-types",
+ "thiserror",
+]
+
+[[package]]
+name = "pulldown-cmark"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e"
+dependencies = [
+ "bitflags",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
+name = "pulldown-cmark-to-cmark"
+version = "22.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab1ad36992cead65f02aa399a373a42730922f1525d988172634fdefdecb8a60"
+dependencies = [
+ "pulldown-cmark",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[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.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
+dependencies = [
+ "getrandom 0.2.17",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+
+[[package]]
+name = "ring"
+version = "0.17.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom 0.2.17",
+ "libc",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rsconf"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06cbd984e96cc891aa018958ac3d09986c0ea7635eedfff670b99a90970f159f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "rust-embed"
+version = "8.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9"
+dependencies = [
+ "rust-embed-impl",
+ "rust-embed-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-impl"
+version = "8.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097"
+dependencies = [
+ "mime_guess",
+ "proc-macro2",
+ "quote",
+ "rust-embed-utils",
+ "shellexpand",
+ "syn 2.0.119",
+ "walkdir",
+]
+
+[[package]]
+name = "rust-embed-utils"
+version = "8.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0"
+dependencies = [
+ "globset",
+ "sha2 0.11.0",
+ "walkdir",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96"
+dependencies = [
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-webpki"
+version = "0.103.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "self_cell"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813"
+
+[[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.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serial_test"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d"
+dependencies = [
+ "once_cell",
+ "parking_lot",
+ "serial_test_derive",
+]
+
+[[package]]
+name = "serial_test_derive"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "sha1"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
+]
+
+[[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 0.10.7",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
+]
+
+[[package]]
+name = "shellexpand"
+version = "3.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8"
+dependencies = [
+ "dirs",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "siphasher"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "socket2"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "sqlx"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71"
+dependencies = [
+ "sqlx-core",
+ "sqlx-macros",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+]
+
+[[package]]
+name = "sqlx-core"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb"
+dependencies = [
+ "base64",
+ "bytes",
+ "cfg-if",
+ "crc",
+ "crossbeam-queue",
+ "either",
+ "event-listener",
+ "futures-core",
+ "futures-intrusive",
+ "futures-io",
+ "futures-util",
+ "hashbrown 0.16.1",
+ "hashlink",
+ "indexmap",
+ "log",
+ "memchr",
+ "percent-encoding",
+ "rustls",
+ "serde",
+ "serde_json",
+ "sha2 0.10.9",
+ "smallvec",
+ "thiserror",
+ "time",
+ "tokio",
+ "tokio-stream",
+ "tracing",
+ "url",
+ "uuid",
+ "webpki-roots",
+]
+
+[[package]]
+name = "sqlx-macros"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "sqlx-core",
+ "sqlx-macros-core",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "sqlx-macros-core"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3"
+dependencies = [
+ "cfg-if",
+ "dotenvy",
+ "either",
+ "heck",
+ "hex",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "sha2 0.10.9",
+ "sqlx-core",
+ "sqlx-mysql",
+ "sqlx-postgres",
+ "sqlx-sqlite",
+ "syn 2.0.119",
+ "thiserror",
+ "tokio",
+ "url",
+]
+
+[[package]]
+name = "sqlx-mysql"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27"
+dependencies = [
+ "bitflags",
+ "byteorder",
+ "bytes",
+ "crc",
+ "digest 0.11.3",
+ "dotenvy",
+ "either",
+ "futures-core",
+ "futures-util",
+ "generic-array",
+ "log",
+ "percent-encoding",
+ "serde",
+ "sha1",
+ "sha2 0.11.0",
+ "sqlx-core",
+ "thiserror",
+ "time",
+ "tracing",
+ "uuid",
+]
+
+[[package]]
+name = "sqlx-postgres"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e"
+dependencies = [
+ "atoi",
+ "base64",
+ "bitflags",
+ "byteorder",
+ "crc",
+ "dotenvy",
+ "etcetera",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "hex",
+ "hkdf",
+ "hmac",
+ "itoa",
+ "log",
+ "md-5",
+ "memchr",
+ "rand",
+ "serde",
+ "serde_json",
+ "sha2 0.11.0",
+ "smallvec",
+ "sqlx-core",
+ "stringprep",
+ "thiserror",
+ "time",
+ "tracing",
+ "uuid",
+ "whoami",
+]
+
+[[package]]
+name = "sqlx-sqlite"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c"
+dependencies = [
+ "atoi",
+ "flume",
+ "form_urlencoded",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-intrusive",
+ "futures-util",
+ "libsqlite3-sys",
+ "log",
+ "percent-encoding",
+ "regex",
+ "serde",
+ "sqlx-core",
+ "thiserror",
+ "time",
+ "tracing",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "stringprep"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+ "unicode-properties",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[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 = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+
+[[package]]
+name = "synstructure"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
+dependencies = [
+ "fastrand",
+ "getrandom 0.4.3",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[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.3",
+]
+
+[[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",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
+
+[[package]]
+name = "time-macros"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
+dependencies = [
+ "displaydoc",
+ "serde_core",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "libc",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tonic"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec"
+dependencies = [
+ "async-trait",
+ "axum",
+ "base64",
+ "bytes",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-timeout",
+ "hyper-util",
+ "percent-encoding",
+ "pin-project",
+ "socket2",
+ "sync_wrapper",
+ "tokio",
+ "tokio-stream",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tonic-build"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734"
+dependencies = [
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "tonic-prost"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309"
+dependencies = [
+ "bytes",
+ "prost",
+ "tonic",
+]
+
+[[package]]
+name = "tonic-prost-build"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a"
+dependencies = [
+ "prettyplease",
+ "proc-macro2",
+ "prost-build",
+ "prost-types",
+ "quote",
+ "syn 2.0.119",
+ "tempfile",
+ "tonic-build",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "indexmap",
+ "pin-project-lite",
+ "slab",
+ "sync_wrapper",
+ "tokio",
+ "tokio-util",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "turtle-api"
+version = "20.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95568c8b817de1e74b4661e760329a36c11c36da4e1bbb94f7fe16c19748b1d"
+dependencies = [
+ "eyre",
+ "hyper-util",
+ "prost",
+ "protox",
+ "regex",
+ "sqlx",
+ "time",
+ "tokio",
+ "tonic",
+ "tonic-prost",
+ "tonic-prost-build",
+ "tower",
+ "turtle-common",
+ "typed-builder",
+ "uuid",
+]
+
+[[package]]
+name = "turtle-common"
+version = "20.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38e3ea95176431c038196cd8ce808e99e85096d3fe5ec06da0562cf2e310e701"
+dependencies = [
+ "directories",
+ "eyre",
+ "semver",
+ "serde",
+ "sqlx",
+ "time",
+ "typed-builder",
+ "uuid",
+ "whoami",
+]
+
+[[package]]
+name = "type-map"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
+dependencies = [
+ "rustc-hash",
+]
+
+[[package]]
+name = "typed-builder"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31aa81521b70f94402501d848ccc0ecaa8f93c8eb6999eb9747e72287757ffda"
+dependencies = [
+ "typed-builder-macro",
+]
+
+[[package]]
+name = "typed-builder-macro"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "076a02dc54dd46795c2e9c8282ed40bcfb1e22747e955de9389a1de28190fb26"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "unic-langid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05"
+dependencies = [
+ "unic-langid-impl",
+]
+
+[[package]]
+name = "unic-langid-impl"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658"
+dependencies = [
+ "tinystr",
+]
+
+[[package]]
+name = "unicase"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-properties"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
+
+[[package]]
+name = "unix_path"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af8e291873ae77c4c8d9c9b34d0bee68a35b048fb39c263a5155e0e353783eaf"
+dependencies = [
+ "unix_str",
+]
+
+[[package]]
+name = "unix_str"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ace0b4755d0a2959962769239d56267f8a024fef2d9b32666b3dcd0946b0906"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc"
+dependencies = [
+ "getrandom 0.4.3",
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasi"
+version = "0.14.7+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
+dependencies = [
+ "wasip2",
+]
+
+[[package]]
+name = "wasip2"
+version = "1.0.1+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasite"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42"
+dependencies = [
+ "wasi 0.14.7+wasi-0.2.4",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "whoami"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c"
+dependencies = [
+ "libc",
+ "libredox",
+ "objc2-system-configuration",
+ "wasite",
+ "web-sys",
+]
+
+[[package]]
+name = "widestring"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
+
+[[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",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "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.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
+
+[[package]]
+name = "writeable"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
+
+[[package]]
+name = "xtask"
+version = "0.0.0"
+dependencies = [
+ "anstyle",
+ "anyhow",
+ "clap",
+ "clap_complete",
+ "fish-build-helper",
+ "fish-common",
+ "fish-tempfile",
+ "fish-widestring",
+ "fluent-ftl-tools",
+ "fluent-syntax",
+ "ignore",
+ "pcre2",
+ "walkdir",
+]
+
+[[package]]
+name = "xterm-color"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7008a9d8ba97a7e47d9b2df63fcdb8dade303010c5a7cd5bf2469d4da6eba673"
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "synstructure",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e"
+
+[[package]]
+name = "zerotrie"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
+dependencies = [
+ "serde",
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "zmij"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/pkgs/by-name/fi/fish-patched/package.nix b/pkgs/by-name/fi/fish-patched/package.nix
new file mode 100644
index 00000000..cb45af99
--- /dev/null
+++ b/pkgs/by-name/fi/fish-patched/package.nix
@@ -0,0 +1,38 @@
+{
+ fish,
+ rustPlatform,
+}: let
+ loadedRustPlatform = {
+ inherit (rustPlatform) cargoSetupHook;
+ fetchCargoVendor = loadedFetchCargoVendor;
+ };
+
+ loadedFetchCargoVendor = prev:
+ rustPlatform.importCargoLock
+ {
+ lockFile = ./Cargo.lock;
+ allowBuiltinFetchGit = true;
+ };
+in
+ (fish.override {
+ rustPlatform = loadedRustPlatform;
+ }).overrideAttrs (prev: {
+ pname = "${prev.pname}-patched";
+
+ postPatch =
+ (prev.postPatch or "")
+ + ''
+ rm Cargo.lock
+ cat ${./Cargo.lock} > Cargo.lock
+ '';
+
+ doCheck = false;
+
+ patches =
+ (prev.patches or [])
+ ++ (
+ builtins.map (name: ./patches + "/${name}")
+ (builtins.attrNames
+ (builtins.readDir ./patches))
+ );
+ })
diff --git a/pkgs/by-name/fi/fish-patched/patches/0003-feat-history-Add-turtle-as-history-backend.patch b/pkgs/by-name/fi/fish-patched/patches/0003-feat-history-Add-turtle-as-history-backend.patch
new file mode 100644
index 00000000..8eeb03b4
--- /dev/null
+++ b/pkgs/by-name/fi/fish-patched/patches/0003-feat-history-Add-turtle-as-history-backend.patch
@@ -0,0 +1,843 @@
+From 26e8fbb5a24dd8b9cac67adcb59e714b32f95aa6 Mon Sep 17 00:00:00 2001
+From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
+Date: Sat, 22 Aug 2026 22:42:44 +0200
+Subject: [PATCH 03/10] feat(history): Add turtle as history backend
+
+---
+ Cargo.toml | 4 +
+ src/history/history.rs | 285 ++---------------------------
+ src/history/mod.rs | 2 +
+ src/history/turtle.rs | 400 +++++++++++++++++++++++++++++++++++++++++
+ src/parser.rs | 5 +
+ src/reader/reader.rs | 4 +-
+ 6 files changed, 424 insertions(+), 276 deletions(-)
+ create mode 100644 src/history/turtle.rs
+
+diff --git a/Cargo.toml b/Cargo.toml
+index aa91b61b7..137fbed24 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -11,6 +11,7 @@ repository = "https://github.com/fish-shell/fish-shell"
+ license = "GPL-2.0-only AND LGPL-2.0-or-later AND MIT AND PSF-2.0"
+
+ [workspace.dependencies]
++turtle-api = { version = "20.0.0" }
+ anstyle = "1.0.13"
+ anyhow = "1.0.102"
+ assert_matches = "1.5.0"
+@@ -115,6 +116,9 @@ homepage = "https://fishshell.com"
+ readme = "README.rst"
+
+ [dependencies]
++turtle-api.workspace = true
++tokio = { version = "1.53.1", features = ["rt"] }
++uuid = { version = "1.24.0", features = ["v7"] }
+ assert_matches.workspace = true
+ bitflags.workspace = true
+ cfg-if.workspace = true
+diff --git a/src/history/history.rs b/src/history/history.rs
+index e1d04ad49..90cbfe6a9 100644
+--- a/src/history/history.rs
++++ b/src/history/history.rs
+@@ -14,6 +14,7 @@
+ //! `src/fs.rs`. By default, `flock()` is used for locking. If that is unavailable, an imperfect
+ //! fallback solution attempts to detect races and retries if a race is detected.
+
++use crate::history::turtle::HistoryDb;
+ use crate::{
+ ast::{self, Kind, Node as _},
+ common::valid_var_name,
+@@ -334,7 +335,7 @@ struct HistoryImpl {
+ /// Deleted item contents, and the scope of the deletion.
+ deleted_items: HashMap<WString, DeletionScope>,
+ /// The history file contents.
+- file_contents: Option<HistoryFile>,
++ file_contents: Option<HistoryDb>,
+ /// The file ID of the history file.
+ history_file_id: FileId, // INVALID_FILE_ID
+ /// The boundary timestamp distinguishes old items from new items. Items whose timestamps are <=
+@@ -358,6 +359,8 @@ fn history_file_path(&self) -> std::io::Result<Option<WString>> {
+ return Ok(None);
+ }
+
++ return Ok(Some(L!("/run/user/1000/turtle.sock").to_owned()));
++
+ let mut path = if let Some(custom_dir) = &self.custom_directory {
+ custom_dir.clone()
+ } else {
+@@ -422,10 +425,7 @@ fn add(&mut self, item: HistoryItem, pending: bool, do_save: bool) {
+ }
+
+ /// Internal function.
+- fn clear_file_state(&mut self) {
+- // Erase everything we know about our file.
+- self.file_contents = None;
+- }
++ fn clear_file_state(&mut self) {}
+
+ /// Returns a timestamp for new items - see the implementation for a subtlety.
+ fn timestamps_as_of_now(&self) -> Timestamps {
+@@ -451,32 +451,16 @@ fn timestamps_as_of_now(&self) -> Timestamps {
+
+ /// Loads old items if necessary.
+ /// Return a reference to the loaded history file.
+- fn load_old_if_needed(&mut self) -> &HistoryFile {
++ fn load_old_if_needed(&mut self) -> &HistoryDb {
+ if let Some(ref file_contents) = self.file_contents {
+ return file_contents;
+ }
+ let Ok(Some(history_path)) = self.history_file_path() else {
+- return self.file_contents.insert(HistoryFile::create_empty());
++ return self.file_contents.insert(HistoryDb::create_empty());
+ };
+
+ let _profiler = TimeProfiler::new("load_old");
+- let file_contents = match lock_and_load(&history_path, RawHistoryFile::create) {
+- Ok((file_id, history_file)) => {
+- self.history_file_id = file_id;
+- let _profiler = TimeProfiler::new("populate_from_file_contents");
+- let file_contents = history_file.decode(Some(self.boundary_timestamp));
+- flogf!(
+- history,
+- "Loaded %u old items",
+- file_contents.offsets().len()
+- );
+- file_contents
+- }
+- Err(e) => {
+- flog!(history_file, "Error reading from history file:", e);
+- HistoryFile::create_empty()
+- }
+- };
++ let file_contents = HistoryDb::load(&history_path, self.boundary_timestamp);
+ self.file_contents.insert(file_contents)
+ }
+
+@@ -524,260 +508,11 @@ fn remove_ephemeral_items(&mut self) {
+ usize::min(self.first_unwritten_new_item_index, self.new_items.len());
+ }
+
+- /// Given an existing history file, write a new history file to `dst`.
+- fn rewrite_to_temporary_file(
+- &self,
+- existing_file: &File,
+- dst: &mut File,
+- ) -> std::io::Result<()> {
+- // We are reading FROM existing_file and writing TO dst
+-
+- // Make an LRU cache to save only the last N elements.
+-
+- /// When we rewrite the history, the number of items we keep.
+- const HISTORY_SAVE_MAX: NonZeroUsize = NonZeroUsize::new(1024 * 256).unwrap();
+- let mut lru = LruCache::new(HISTORY_SAVE_MAX);
+-
+- // Read in existing items (which may have changed out from underneath us, so don't trust our
+- // old file contents).
+- let file_id = file_id_for_file(existing_file);
+- if let Ok(local_file) = RawHistoryFile::create(existing_file, file_id) {
+- for offset in local_file.offsets(None) {
+- // Try decoding an old item.
+- let Some(old_item) = local_file.decode_item(offset) else {
+- continue;
+- };
+- if old_item.is_empty() {
+- continue;
+- }
+-
+- // Check if this item should be deleted.
+- if let Some(&scope) = self.deleted_items.get(old_item.str()) {
+- // If old item is newer than session always erase if in deleted.
+- // If old item is older and in deleted items don't erase if added by clear_session.
+- let delete = old_item.first_added_timestamp() > self.boundary_timestamp
+- || scope == DeletionScope::AllSessions;
+- if delete {
+- continue;
+- }
+- }
+- lru.add_item(old_item);
+- }
+- }
+-
+- // Insert any unwritten new items
+- for item in self
+- .new_items
+- .iter()
+- .skip(self.first_unwritten_new_item_index)
+- {
+- if item.should_write_to_disk() {
+- lru.add_item(item.clone());
+- }
+- }
+-
+- // Stable-sort our items by timestamp
+- // This is because we may have read "old" items with a later timestamp than our "new" items
+- // This is the essential step that roughly orders items by history
+- let mut items: Vec<_> = lru.into_iter().map(|(_key, item)| item).collect();
+- items.sort_by_key(HistoryItem::last_added_timestamp);
+-
+- /// Default buffer size for flushing to the history file.
+- const HISTORY_OUTPUT_BUFFER_SIZE: usize = 64 * 1024;
+- // Write them out.
+- let mut buffer = BufWriter::with_capacity(HISTORY_OUTPUT_BUFFER_SIZE + 128, dst);
+- for item in items {
+- item.write_to(&mut buffer)?;
+- }
+- buffer.flush()?;
+- Ok(())
+- }
+-
+- /// Saves history by rewriting the file.
+- fn save_internal_via_rewrite(&mut self, history_path: &wstr) -> std::io::Result<()> {
+- flogf!(
+- history,
+- "Saving %u items via rewrite",
+- self.new_items.len() - self.first_unwritten_new_item_index
+- );
+-
+- let rewrite =
+- |old_file: &File, tmp_file: &mut File| -> std::io::Result<PotentialUpdate<()>> {
+- let result = self.rewrite_to_temporary_file(old_file, tmp_file);
+- if let Err(err) = result {
+- flog!(
+- history_file,
+- "Error writing to temporary history file:",
+- err
+- );
+- return Err(err);
+- }
+- Ok(PotentialUpdate {
+- do_save: true,
+- data: (),
+- })
+- };
+-
+- let (file_id, _) = rewrite_via_temporary_file(history_path, rewrite)?;
+- self.history_file_id = file_id;
+-
+- // We've saved everything, so we have no more unsaved items.
+- self.first_unwritten_new_item_index = self.new_items.len();
+-
+- // We deleted our deleted items.
+- self.deleted_items.clear();
+-
+- // Our history has been written to the file, so clear our state so we can re-reference the
+- // file.
+- self.clear_file_state();
+-
+- Ok(())
+- }
+-
+- /// Saves history by appending to the file.
+- fn save_internal_via_appending(&mut self, history_path: &wstr) -> std::io::Result<()> {
+- flogf!(
+- history,
+- "Saving %u items via appending",
+- self.new_items.len() - self.first_unwritten_new_item_index
+- );
+- // No deleting allowed.
+- assert!(self.deleted_items.is_empty());
+-
+- let mut locked_history_file =
+- LockedFile::new(LockingMode::Exclusive(WriteMethod::Append), history_path)?;
+-
+- // Check if the file was modified since it was last read.
+- // If someone has replaced the file, forget our file state.
+- if file_id_for_file(locked_history_file.get()) != self.history_file_id {
+- self.clear_file_state();
+- }
+-
+- // We took the exclusive lock. Append to the file.
+- // Note that this is sketchy for a few reasons:
+- // - Another shell may have appended its own items with a later timestamp, so our file may
+- // no longer be sorted by timestamp.
+- // - Another shell may have appended the same items, so our file may now contain
+- // duplicates.
+- //
+- // Originally we always rewrote the file on saving, which avoided both of these problems.
+- // However, appending allows us to save history after every command, which is nice!
+- //
+- // Periodically we "clean up" the file by rewriting it, so that most of the time it doesn't
+- // have duplicates, although we don't yet sort by timestamp (the timestamp isn't really used
+- // for much anyways).
+-
+- // So far so good. Write all items at or after first_unwritten_new_item_index. Note that we
+- // write even a pending item - pending items are ignored by history within the command
+- // itself, but should still be written to the file.
+- // Use a small buffer size for appending, as we usually only have 1 item.
+- // Buffer everything and then write it all at once to avoid tearing writes (O_APPEND).
+- let mut buffer = Vec::new();
+- let mut new_first_index = self.first_unwritten_new_item_index;
+- while new_first_index < self.new_items.len() {
+- let item = &self.new_items[new_first_index];
+- if item.should_write_to_disk() {
+- // Can't error writing to a buffer.
+- item.write_to(&mut buffer).unwrap();
+- }
+- // We wrote or skipped this item, hooray.
+- new_first_index += 1;
+- }
+- locked_history_file.get_mut().write_all(&buffer)?;
+- fsync(locked_history_file.get())?;
+- self.first_unwritten_new_item_index = new_first_index;
+-
+- // Since we just modified the file, update our history_file_id to match its current state
+- // Otherwise we'll think the file has been changed by someone else the next time we go to
+- // write.
+- // We don't update `self.file_contents` since we only appended to the file, and everything we
+- // appended remains in our new_items
+- self.history_file_id = file_id_for_file(locked_history_file.get());
+-
+- Ok(())
+- }
+-
+ /// Saves history.
+- fn save(&mut self, vacuum: bool) {
+- // Nothing to do if there's no new items.
+- if self.first_unwritten_new_item_index >= self.new_items.len()
+- && self.deleted_items.is_empty()
+- {
+- return;
+- }
+-
+- // Compact our new items so we don't have duplicates.
+- self.compact_new_items();
+-
+- if self.name.is_empty() {
+- // We're in the "incognito" mode. Pretend we've saved the history.
+- self.first_unwritten_new_item_index = self.new_items.len();
+- self.deleted_items.clear();
+- self.clear_file_state();
+- return;
+- }
+-
+- let history_path = match self.history_file_path() {
+- Ok(history_path) => history_path.unwrap(),
+- Err(e) => {
+- flog!(history, "Saving history failed:", e);
+- return;
+- }
+- };
+-
+- // Try saving. If we have items to delete, we have to rewrite the file. If we do not, we can
+- // append to it.
+- let mut ok = false;
+- if !vacuum && self.deleted_items.is_empty() {
+- // Try doing a fast append.
+- if let Err(e) = self.save_internal_via_appending(&history_path) {
+- flog!(history, "Appending to history failed:", e);
+- } else {
+- ok = true;
+- }
+- }
+- if !ok {
+- // We did not or could not append; rewrite the file ("vacuum" it).
+- if let Err(e) = self.save_internal_via_rewrite(&history_path) {
+- flog!(history, "Rewriting history failed:", e);
+- }
+- }
+- }
++ fn save(&mut self, vacuum: bool) {}
+
+ /// Saves history unless doing so is disabled.
+- fn save_unless_disabled(&mut self) {
+- // Respect disable_automatic_save_counter.
+- if self.disable_automatic_save_counter > 0 {
+- return;
+- }
+-
+- // We may or may not vacuum. We try to vacuum every `VACUUM_FREQUENCY` items, but start the
+- // countdown at a random number so that even if the user never runs more than 25 commands, we'll
+- // eventually vacuum. If countdown_to_vacuum is None, it means we haven't yet picked a value for
+- // the counter.
+- let countdown_to_vacuum = self
+- .countdown_to_vacuum
+- .get_or_insert_with(|| rand::rng().random_range(0..VACUUM_FREQUENCY));
+-
+- // Determine if we're going to vacuum.
+- let mut vacuum = false;
+- if *countdown_to_vacuum == 0 {
+- *countdown_to_vacuum = VACUUM_FREQUENCY;
+- vacuum = true;
+- }
+-
+- // Update our countdown.
+- assert!(*countdown_to_vacuum > 0);
+- *countdown_to_vacuum -= 1;
+-
+- // This might be a good candidate for moving to a background thread.
+- let _profiler = TimeProfiler::new(if vacuum {
+- "save vacuum"
+- } else {
+- "save no vacuum"
+- });
+- self.save(vacuum);
+- }
++ fn save_unless_disabled(&mut self) {}
+
+ fn new(name: WString, custom_directory: Option<WString>) -> Self {
+ Self {
+diff --git a/src/history/mod.rs b/src/history/mod.rs
+index 7ef4d4417..0de867740 100644
+--- a/src/history/mod.rs
++++ b/src/history/mod.rs
+@@ -3,4 +3,6 @@
+ mod history;
+ mod yaml_backend;
+
++pub(crate) mod turtle;
++
+ pub use history::*;
+diff --git a/src/history/turtle.rs b/src/history/turtle.rs
+new file mode 100644
+index 000000000..ffcdea108
+--- /dev/null
++++ b/src/history/turtle.rs
+@@ -0,0 +1,400 @@
++use std::{
++ collections::HashMap,
++ sync::{
++ Arc, OnceLock, RwLock,
++ atomic::{AtomicBool, Ordering},
++ mpsc,
++ },
++ thread::{self, JoinHandle, Thread},
++ time::{Duration, SystemTime, UNIX_EPOCH},
++};
++
++use fish_widestring::{L, WString};
++use turtle_api::{
++ client::{HistoryClient, OffsetDateTime, Probe, Range},
++ history::{History, HistoryId},
++};
++
++use crate::{env::Environment as _, flog, flogf, history::HistoryItem};
++
++pub(crate) struct HistoryDb;
++
++#[derive(Debug)]
++struct HistoryDbInner {
++ range: Option<Range>,
++ session: uuid::Uuid,
++
++ loaded_history: Vec<History>,
++
++ handler: Handler,
++}
++
++impl HistoryDbInner {
++ fn loaded_history(&mut self) -> &[History] {
++ let should_wait = if self.loaded_history.is_empty() {
++ // We are probably running this the first time.
++ // Make sure, that we actually load something.
++ true
++ } else {
++ false
++ };
++
++ if NEW_LOADED_HISTORY_AVAILABLE.load(Ordering::Relaxed) || should_wait {
++ if let Some(pre_loaded_history) = self.handler.load_history_resp(should_wait) {
++ self.loaded_history = pre_loaded_history;
++ }
++
++ flogf!(
++ history,
++ "Loaded history was requested, returning %d entries.",
++ self.loaded_history.len()
++ );
++
++ NEW_LOADED_HISTORY_AVAILABLE.store(false, Ordering::Relaxed);
++ }
++
++ &self.loaded_history
++ }
++}
++
++impl HistoryDb {
++ fn with_inner_mut<T>(mut fun: impl FnMut(&mut HistoryDbInner) -> T) -> T {
++ let mut inner = INNER.write().expect("Should not be poisioned");
++ let inner = (*inner).get_mut().expect("Should be initialized");
++
++ let output = fun(inner);
++
++ output
++ }
++}
++
++impl HistoryDb {
++ /// Create an empty history file.
++ pub(super) fn create_empty() -> Self {
++ flog!(history, "turtle: Creating new empty hist");
++
++ Self
++ }
++
++ /// Return the offsets of items in this file.
++ pub(super) fn offsets(&self) -> Vec<HistoryId> {
++ let out: Vec<_> = Self::with_inner_mut(|inner| {
++ inner
++ .loaded_history()
++ .iter()
++ .map(|h| h.id.clone())
++ .collect()
++ });
++ out
++ }
++
++ /// Return whether this file is empty.
++ pub(super) fn is_empty(&self) -> bool {
++ self.offsets().is_empty()
++ }
++
++ /// Load from on-disk file.
++ pub(super) fn load(history_path: &WString, _boundary_timestamp: SystemTime) -> Self {
++ flogf!(history, "turtle: Loading hist from %s", history_path);
++
++ let inner = {
++ // let range = Some(Range {
++ // start: OffsetDateTime::from_unix_timestamp(
++ // boundary_timestamp
++ // .duration_since(UNIX_EPOCH)
++ // .expect("Also valid")
++ // .as_secs() as i64,
++ // )
++ // .expect("Valid"),
++ // end: OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()),
++ // });
++ let range = None;
++
++ let handler = Handler::start(history_path.to_string());
++ handler.emit(HandleHistoryCmd::Load { range });
++
++ let inner = HistoryDbInner {
++ session: uuid::Uuid::now_v7(),
++ range,
++ loaded_history: vec![],
++ handler,
++ };
++
++ inner
++ };
++
++ let static_inner = INNER.write().expect("should not be poisened");
++ (*static_inner)
++ .set(inner)
++ .expect("Should not have been initialized before");
++
++ Self
++ }
++
++ /// Decode an item at a given offset.
++ pub(super) fn decode_item(&self, id: HistoryId) -> Option<HistoryItem> {
++ Self::with_inner_mut(|inner| {
++ inner.loaded_history().iter().find(|h| h.id == id).map(|h| {
++ HistoryItem::new(
++ WString::from_str(&h.command),
++ super::Timestamps {
++ last_added: UNIX_EPOCH
++ + Duration::from_nanos_u128(h.timestamp.unix_timestamp_nanos() as u128),
++ first_added: UNIX_EPOCH
++ + Duration::from_nanos_u128(h.timestamp.unix_timestamp_nanos() as u128),
++ },
++ super::PersistenceMode::Disk,
++ )
++ })
++ })
++ }
++}
++
++static INNER: RwLock<OnceLock<HistoryDbInner>> = RwLock::new(OnceLock::new());
++
++pub(crate) fn handle_preexec(parser: &mut crate::parser::Parser, command: WString) {
++ HistoryDb::with_inner_mut(|inner| {
++ let now = OffsetDateTime::now_local().expect("Should have local time zone");
++ let command = command.to_string();
++ let history: History = History::daemon()
++ .timestamp(now)
++ .command(command)
++ .cwd(parser.vars().get_pwd_slash().to_string())
++ .session(inner.session)
++ .hostname(
++ parser
++ .vars()
++ .get(L!("hostname"))
++ .map_or(String::from("<unknown>"), |s| s.as_string().to_string()),
++ )
++ .author(
++ parser
++ .vars()
++ .get(L!("USER"))
++ .map_or(String::from("<unknown>"), |s| s.as_string().to_string()),
++ )
++ .build()
++ .into();
++
++ inner.handler.emit(HandleHistoryCmd::Start { history });
++ });
++}
++
++pub(crate) fn handle_postexec(parser: &mut crate::parser::Parser, command: WString) {
++ HistoryDb::with_inner_mut(|inner| {
++ inner.handler.emit(HandleHistoryCmd::End {
++ command: command.to_string(),
++ when: OffsetDateTime::now_local().expect("to work"),
++ exit_code: i64::from(parser.last_statuses().status),
++ });
++ inner
++ .handler
++ .emit(HandleHistoryCmd::Load { range: inner.range });
++ });
++}
++
++pub(crate) fn handle_shutdow() {
++ flog!(history, "Shutting down history db");
++
++ // If the `load` function was never called, inner will be None.
++ // Therefore we only need to run shutdown, when inner was loaded.
++ let inner_is_initialized = {
++ let read = INNER.read().expect("should be not-poisioned");
++ read.get().is_some()
++ };
++
++ if inner_is_initialized {
++ HistoryDb::with_inner_mut(|inner| inner.handler.stop());
++ }
++}
++
++#[derive(Debug)]
++struct Handler {
++ thread: Option<JoinHandle<()>>,
++
++ cmd_tx: Option<mpsc::Sender<HandleHistoryCmd>>,
++
++ returned_loaded_history: Arc<RwLock<Option<Vec<History>>>>,
++}
++
++#[derive(Debug)]
++enum HandleHistoryCmd {
++ Start {
++ history: History,
++ },
++ End {
++ command: String,
++ when: OffsetDateTime,
++ exit_code: i64,
++ },
++ Load {
++ range: Option<Range>,
++ },
++}
++
++static HANDLER_IS_SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
++static LOAD_HISTORY_THREAD: RwLock<Option<Thread>> = RwLock::new(None);
++static LOAD_HISTORY_WAITING: AtomicBool = AtomicBool::new(false);
++static NEW_LOADED_HISTORY_AVAILABLE: AtomicBool = AtomicBool::new(false);
++
++impl Handler {
++ fn start(daemon_socket: String) -> Self {
++ let (cmd_tx, cmd_rx) = mpsc::channel();
++ let returned_loaded_history = Arc::new(RwLock::new(None));
++
++ let loaded_history_return = Arc::clone(&returned_loaded_history);
++ let thread = std::thread::spawn(move || {
++ let mut running_history: HashMap<String, (HistoryId, OffsetDateTime)> = HashMap::new();
++
++ let rt = {
++ let mut b = tokio::runtime::Builder::new_current_thread();
++ b.enable_all();
++ b.build()
++ .expect("to work, as all the fish code is otherwise sync.")
++ };
++ let mut client = match rt.block_on(turtle_api::client::probe(daemon_socket.clone())) {
++ Probe::Ready(_) => rt
++ .block_on(HistoryClient::new(daemon_socket))
++ .expect("We probed, this client should be accessible"),
++ Probe::NeedsRestart(err) => {
++ flogf!(history, "Turtle daemon needs restart: %s", err);
++ todo!();
++ }
++ Probe::Unreachable(report) => {
++ flogf!(history, "Turtle daemon unreachable: %s", report.to_string());
++ todo!();
++ }
++ };
++
++ while let Ok(cmd) = cmd_rx.recv() {
++ match cmd {
++ HandleHistoryCmd::Start { history } => {
++ let command = history.command.clone();
++
++ flogf!(history, " > Starting cmd '%s'", command);
++
++ let history_id = history.id;
++ let start_time = history.timestamp;
++
++ rt.block_on(client.start_history(history))
++ .expect("client to still work");
++
++ running_history.insert(command, (history_id, start_time));
++ }
++ HandleHistoryCmd::End {
++ command,
++ when: now,
++ exit_code,
++ } => {
++ let (id, start_time) = running_history
++ .remove(command.as_str())
++ .expect("to contain it");
++
++ let duration = Duration::from_nanos_u128(
++ (now - start_time).whole_nanoseconds() as u128,
++ );
++
++ flogf!(
++ history,
++ " < Ending cmd '%s' with exit %d and duration %d ms",
++ command,
++ exit_code,
++ duration.as_millis() as u64,
++ );
++
++ rt.block_on(client.end_history(id.to_string(), duration, exit_code))
++ .expect("client to still work");
++ }
++ HandleHistoryCmd::Load { range } => {
++ if !HANDLER_IS_SHUTTING_DOWN.load(Ordering::Relaxed) {
++ let loaded_history = {
++ let base = rt.block_on(client.history(range));
++ base.expect("the client to still work")
++ };
++
++ let mut output = loaded_history_return.write().expect("not poisioned");
++ (*output) = Some(loaded_history);
++ NEW_LOADED_HISTORY_AVAILABLE.store(true, Ordering::Relaxed);
++
++ if LOAD_HISTORY_WAITING.load(Ordering::Relaxed) {
++ let read = LOAD_HISTORY_THREAD.read().expect("not poisioned");
++ let t = read
++ .as_ref()
++ .expect("is some, as a thread is marked as waiting");
++
++ LOAD_HISTORY_WAITING.store(false, Ordering::Relaxed);
++ t.unpark();
++ }
++ }
++ }
++ }
++ }
++ });
++
++ Self {
++ thread: Some(thread),
++ cmd_tx: Some(cmd_tx),
++ returned_loaded_history,
++ }
++ }
++
++ /// This is effectively a [`Drop`] impl, but we can't use the trait directly as statics don't
++ /// get dropped.
++ fn stop(&mut self) {
++ HANDLER_IS_SHUTTING_DOWN.store(true, Ordering::Relaxed);
++
++ let thread = self.thread.take().expect("is some");
++ let cmd_tx = self.cmd_tx.take().expect("is some");
++
++ // Tell the handler, that we won't send more cmds
++ drop(cmd_tx);
++
++ thread.join().expect("should not panic");
++
++ flog!(history, "History db shutdown completed.");
++ }
++
++ fn load_history_resp(&self, should_wait: bool) -> Option<Vec<History>> {
++ let mut output = None;
++ let rx = &self.returned_loaded_history;
++
++ if let Ok(read) = rx.try_read() {
++ read.clone_into(&mut output);
++ }
++
++ if should_wait && output.is_none() {
++ {
++ {
++ let me = thread::current();
++ let mut write = LOAD_HISTORY_THREAD.write().expect("not poisioned");
++ (*write) = Some(me);
++ }
++
++ LOAD_HISTORY_WAITING.store(true, Ordering::Relaxed);
++ while LOAD_HISTORY_WAITING.load(Ordering::Relaxed) {
++ thread::park();
++ }
++
++ {
++ let mut write = LOAD_HISTORY_THREAD.write().expect("not poisioned");
++ (*write) = None;
++ }
++ }
++
++ let read = rx.read().expect("not poisioned");
++ read.clone_into(&mut output);
++ }
++
++ output
++ }
++
++ fn emit(&self, cmd: HandleHistoryCmd) {
++ flogf!(history, "Emitting handler cmd: %s ", format!("{:?}", cmd));
++
++ self.cmd_tx
++ .as_ref()
++ .expect("was initialized")
++ .send(cmd)
++ .expect("receiver should not have hung up");
++ }
++}
+diff --git a/src/parser.rs b/src/parser.rs
+index ffbc28633..6440b7476 100644
+--- a/src/parser.rs
++++ b/src/parser.rs
+@@ -437,6 +437,11 @@ pub struct Parser {
+ #[cfg(test)]
+ pub test_only_suppress_stderr: bool,
+ }
++impl Drop for Parser {
++ fn drop(&mut self) {
++ crate::history::turtle::handle_shutdow();
++ }
++}
+
+ #[derive(Copy, Clone, Default)]
+ pub struct ParserEnvSetMode {
+diff --git a/src/reader/reader.rs b/src/reader/reader.rs
+index 14fbc89c0..6848a7f1c 100644
+--- a/src/reader/reader.rs
++++ b/src/reader/reader.rs
+@@ -871,6 +871,7 @@ fn read_i(parser: &mut Parser) {
+ L!("fish_preexec").to_owned(),
+ vec![command.clone()],
+ );
++ crate::history::turtle::handle_preexec(reader.parser, command.clone());
+ let eval_res = reader_run_command(reader.parser, &command);
+ signal_clear_cancel();
+ if !eval_res.no_status {
+@@ -884,6 +885,7 @@ fn read_i(parser: &mut Parser) {
+ BufferedOutputter::new(Outputter::stdoutput()).write_command(Osc133CommandFinished {
+ exit_status: reader.parser.last_status(),
+ });
++ crate::history::turtle::handle_postexec(reader.parser, command.clone());
+ event::fire_generic(reader.parser, L!("fish_postexec").to_owned(), vec![command]);
+ // Allow any pending history items to be returned in the history array.
+ reader.history.resolve_pending();
+@@ -2663,7 +2665,7 @@ fn readline(
+ self.clear_pager();
+ }
+
+- if EXIT_STATE.load(Ordering::Relaxed) != ExitState::FinishedHandlers as _ {
++ if EXIT_STATE.load(Ordering::Relaxed) != ExitState::FinishedHandlers as u8 {
+ // The order of the two conditions below is important. Try to restore the mode
+ // in all cases, but only complain if interactive.
+ if let Some(old_modes) = old_modes {
+--
+2.55.0
+
diff --git a/pkgs/by-name/fi/fish-patched/patches/0004-history-turtle-Fix-id-misuse-and-history-return-orde.patch b/pkgs/by-name/fi/fish-patched/patches/0004-history-turtle-Fix-id-misuse-and-history-return-orde.patch
new file mode 100644
index 00000000..4eb3220b
--- /dev/null
+++ b/pkgs/by-name/fi/fish-patched/patches/0004-history-turtle-Fix-id-misuse-and-history-return-orde.patch
@@ -0,0 +1,50 @@
+From c4f1ff5812024e50f8c6cf825a81b654afada2a4 Mon Sep 17 00:00:00 2001
+From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
+Date: Sun, 23 Aug 2026 23:34:58 +0200
+Subject: [PATCH 04/10] history/turtle: Fix id misuse and history return order
+
+---
+ src/history/turtle.rs | 16 +++++++++++++---
+ 1 file changed, 13 insertions(+), 3 deletions(-)
+
+diff --git a/src/history/turtle.rs b/src/history/turtle.rs
+index ffcdea108..862b7346b 100644
+--- a/src/history/turtle.rs
++++ b/src/history/turtle.rs
+@@ -42,6 +42,10 @@ fn loaded_history(&mut self) -> &[History] {
+ if NEW_LOADED_HISTORY_AVAILABLE.load(Ordering::Relaxed) || should_wait {
+ if let Some(pre_loaded_history) = self.handler.load_history_resp(should_wait) {
+ self.loaded_history = pre_loaded_history;
++
++ // HACK(@bpeetz): For some reason, the returned history is reversed.
++ // So we un-reverse it here. <2026-08-23>
++ self.loaded_history.reverse();
+ }
+
+ flogf!(
+@@ -273,13 +277,19 @@ fn start(daemon_socket: String) -> Self {
+
+ flogf!(history, " > Starting cmd '%s'", command);
+
+- let history_id = history.id;
+ let start_time = history.timestamp;
+
+- rt.block_on(client.start_history(history))
++ let reply = rt
++ .block_on(client.start_history(history))
+ .expect("client to still work");
+
+- running_history.insert(command, (history_id, start_time));
++ // NOTE(@bpeetz): We already _have_ an HistoryId on our history,
++ // but the turtle daemon will assign a new one.
++ // Therefore, we need to make sure, that we only use the id it has assigned.
++ // <2026-08-23>
++ let real_id = HistoryId::from(reply.id);
++
++ running_history.insert(command, (real_id, start_time));
+ }
+ HandleHistoryCmd::End {
+ command,
+--
+2.55.0
+
diff --git a/pkgs/by-name/fi/fish-patched/patches/0005-history-history-Always-save-history-even-in-private-.patch b/pkgs/by-name/fi/fish-patched/patches/0005-history-history-Always-save-history-even-in-private-.patch
new file mode 100644
index 00000000..f55a6096
--- /dev/null
+++ b/pkgs/by-name/fi/fish-patched/patches/0005-history-history-Always-save-history-even-in-private-.patch
@@ -0,0 +1,32 @@
+From b113638a4a3283b95029fe67fe575ae37e1b768a Mon Sep 17 00:00:00 2001
+From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
+Date: Mon, 24 Aug 2026 21:12:01 +0200
+Subject: [PATCH 05/10] history/history: Always save history, even in private
+ mode
+
+---
+ src/history/history.rs | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/history/history.rs b/src/history/history.rs
+index 90cbfe6a9..b721e04df 100644
+--- a/src/history/history.rs
++++ b/src/history/history.rs
+@@ -355,12 +355,12 @@ impl HistoryImpl {
+ /// Because the `path_get_data` function does not return error information,
+ /// we cannot provide more detail about the reason for the failure here.
+ fn history_file_path(&self) -> std::io::Result<Option<WString>> {
++ return Ok(Some(L!("/run/user/1000/turtle.sock").to_owned()));
++
+ if self.name.is_empty() {
+ return Ok(None);
+ }
+
+- return Ok(Some(L!("/run/user/1000/turtle.sock").to_owned()));
+-
+ let mut path = if let Some(custom_dir) = &self.custom_directory {
+ custom_dir.clone()
+ } else {
+--
+2.55.0
+
diff --git a/pkgs/by-name/fi/fish-patched/patches/0006-history-turtle-Don-t-block-at-startup-and-wait-until.patch b/pkgs/by-name/fi/fish-patched/patches/0006-history-turtle-Don-t-block-at-startup-and-wait-until.patch
new file mode 100644
index 00000000..00fbc192
--- /dev/null
+++ b/pkgs/by-name/fi/fish-patched/patches/0006-history-turtle-Don-t-block-at-startup-and-wait-until.patch
@@ -0,0 +1,101 @@
+From c159bc0d6be03ce566a5a0e144a503d174aee91f Mon Sep 17 00:00:00 2001
+From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
+Date: Mon, 24 Aug 2026 21:18:39 +0200
+Subject: [PATCH 06/10] history/turtle: Don't block at startup, and wait until
+ history is loaded
+
+---
+ src/history/turtle.rs | 49 +++----------------------------------------
+ 1 file changed, 3 insertions(+), 46 deletions(-)
+
+diff --git a/src/history/turtle.rs b/src/history/turtle.rs
+index 862b7346b..a1009a162 100644
+--- a/src/history/turtle.rs
++++ b/src/history/turtle.rs
+@@ -31,16 +31,8 @@ struct HistoryDbInner {
+
+ impl HistoryDbInner {
+ fn loaded_history(&mut self) -> &[History] {
+- let should_wait = if self.loaded_history.is_empty() {
+- // We are probably running this the first time.
+- // Make sure, that we actually load something.
+- true
+- } else {
+- false
+- };
+-
+- if NEW_LOADED_HISTORY_AVAILABLE.load(Ordering::Relaxed) || should_wait {
+- if let Some(pre_loaded_history) = self.handler.load_history_resp(should_wait) {
++ if NEW_LOADED_HISTORY_AVAILABLE.load(Ordering::Relaxed) {
++ if let Some(pre_loaded_history) = self.handler.load_history_resp() {
+ self.loaded_history = pre_loaded_history;
+
+ // HACK(@bpeetz): For some reason, the returned history is reversed.
+@@ -237,8 +229,6 @@ enum HandleHistoryCmd {
+ }
+
+ static HANDLER_IS_SHUTTING_DOWN: AtomicBool = AtomicBool::new(false);
+-static LOAD_HISTORY_THREAD: RwLock<Option<Thread>> = RwLock::new(None);
+-static LOAD_HISTORY_WAITING: AtomicBool = AtomicBool::new(false);
+ static NEW_LOADED_HISTORY_AVAILABLE: AtomicBool = AtomicBool::new(false);
+
+ impl Handler {
+@@ -325,16 +315,6 @@ fn start(daemon_socket: String) -> Self {
+ let mut output = loaded_history_return.write().expect("not poisioned");
+ (*output) = Some(loaded_history);
+ NEW_LOADED_HISTORY_AVAILABLE.store(true, Ordering::Relaxed);
+-
+- if LOAD_HISTORY_WAITING.load(Ordering::Relaxed) {
+- let read = LOAD_HISTORY_THREAD.read().expect("not poisioned");
+- let t = read
+- .as_ref()
+- .expect("is some, as a thread is marked as waiting");
+-
+- LOAD_HISTORY_WAITING.store(false, Ordering::Relaxed);
+- t.unpark();
+- }
+ }
+ }
+ }
+@@ -364,7 +344,7 @@ fn stop(&mut self) {
+ flog!(history, "History db shutdown completed.");
+ }
+
+- fn load_history_resp(&self, should_wait: bool) -> Option<Vec<History>> {
++ fn load_history_resp(&self) -> Option<Vec<History>> {
+ let mut output = None;
+ let rx = &self.returned_loaded_history;
+
+@@ -372,29 +352,6 @@ fn load_history_resp(&self, should_wait: bool) -> Option<Vec<History>> {
+ read.clone_into(&mut output);
+ }
+
+- if should_wait && output.is_none() {
+- {
+- {
+- let me = thread::current();
+- let mut write = LOAD_HISTORY_THREAD.write().expect("not poisioned");
+- (*write) = Some(me);
+- }
+-
+- LOAD_HISTORY_WAITING.store(true, Ordering::Relaxed);
+- while LOAD_HISTORY_WAITING.load(Ordering::Relaxed) {
+- thread::park();
+- }
+-
+- {
+- let mut write = LOAD_HISTORY_THREAD.write().expect("not poisioned");
+- (*write) = None;
+- }
+- }
+-
+- let read = rx.read().expect("not poisioned");
+- read.clone_into(&mut output);
+- }
+-
+ output
+ }
+
+--
+2.55.0
+
diff --git a/pkgs/by-name/fi/fish-patched/patches/0007-history-turtle-Don-t-reverse-newly-loaded-history.patch b/pkgs/by-name/fi/fish-patched/patches/0007-history-turtle-Don-t-reverse-newly-loaded-history.patch
new file mode 100644
index 00000000..1e75acaf
--- /dev/null
+++ b/pkgs/by-name/fi/fish-patched/patches/0007-history-turtle-Don-t-reverse-newly-loaded-history.patch
@@ -0,0 +1,27 @@
+From dd41eb10eb76289504ff4c5457434c81112cb008 Mon Sep 17 00:00:00 2001
+From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
+Date: Mon, 24 Aug 2026 21:28:04 +0200
+Subject: [PATCH 07/10] history/turtle: Don't reverse newly loaded history
+
+---
+ src/history/turtle.rs | 4 ----
+ 1 file changed, 4 deletions(-)
+
+diff --git a/src/history/turtle.rs b/src/history/turtle.rs
+index a1009a162..2143f38a9 100644
+--- a/src/history/turtle.rs
++++ b/src/history/turtle.rs
+@@ -34,10 +34,6 @@ fn loaded_history(&mut self) -> &[History] {
+ if NEW_LOADED_HISTORY_AVAILABLE.load(Ordering::Relaxed) {
+ if let Some(pre_loaded_history) = self.handler.load_history_resp() {
+ self.loaded_history = pre_loaded_history;
+-
+- // HACK(@bpeetz): For some reason, the returned history is reversed.
+- // So we un-reverse it here. <2026-08-23>
+- self.loaded_history.reverse();
+ }
+
+ flogf!(
+--
+2.55.0
+
diff --git a/pkgs/by-name/fi/fish-patched/patches/0008-history-turtle-Reverse-history-upon-load.patch b/pkgs/by-name/fi/fish-patched/patches/0008-history-turtle-Reverse-history-upon-load.patch
new file mode 100644
index 00000000..b7331637
--- /dev/null
+++ b/pkgs/by-name/fi/fish-patched/patches/0008-history-turtle-Reverse-history-upon-load.patch
@@ -0,0 +1,28 @@
+From 5afaf63f339cfc183a8728363fbb34a710f0e2dc Mon Sep 17 00:00:00 2001
+From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
+Date: Mon, 24 Aug 2026 21:32:51 +0200
+Subject: [PATCH 08/10] history/turtle: Reverse history upon load
+
+For _some_ reason, that seems to improve performance by 5x?!
+(I presume I'm just measuring wrong.)
+---
+ src/history/turtle.rs | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/history/turtle.rs b/src/history/turtle.rs
+index 2143f38a9..4ac931a19 100644
+--- a/src/history/turtle.rs
++++ b/src/history/turtle.rs
+@@ -34,6 +34,9 @@ fn loaded_history(&mut self) -> &[History] {
+ if NEW_LOADED_HISTORY_AVAILABLE.load(Ordering::Relaxed) {
+ if let Some(pre_loaded_history) = self.handler.load_history_resp() {
+ self.loaded_history = pre_loaded_history;
++
++ // PERFORMANCE: That seems to improve performance? <2026-08-24>
++ self.loaded_history.reverse();
+ }
+
+ flogf!(
+--
+2.55.0
+
diff --git a/pkgs/by-name/fi/fish-patched/patches/0009-history-turtle-Do-more-work-in-the-command-handler-w.patch b/pkgs/by-name/fi/fish-patched/patches/0009-history-turtle-Do-more-work-in-the-command-handler-w.patch
new file mode 100644
index 00000000..9e5fbecc
--- /dev/null
+++ b/pkgs/by-name/fi/fish-patched/patches/0009-history-turtle-Do-more-work-in-the-command-handler-w.patch
@@ -0,0 +1,188 @@
+From b316b4952d46ad964aa8397f9a3e57de8ba98b3b Mon Sep 17 00:00:00 2001
+From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
+Date: Mon, 24 Aug 2026 21:43:45 +0200
+Subject: [PATCH 09/10] history/turtle: Do more work in the command handler,
+ when loading history
+
+---
+ src/history/turtle.rs | 94 +++++++++++++++++++++++++++++--------------
+ 1 file changed, 63 insertions(+), 31 deletions(-)
+
+diff --git a/src/history/turtle.rs b/src/history/turtle.rs
+index 4ac931a19..15515fe15 100644
+--- a/src/history/turtle.rs
++++ b/src/history/turtle.rs
+@@ -5,7 +5,7 @@
+ atomic::{AtomicBool, Ordering},
+ mpsc,
+ },
+- thread::{self, JoinHandle, Thread},
++ thread::JoinHandle,
+ time::{Duration, SystemTime, UNIX_EPOCH},
+ };
+
+@@ -19,30 +19,40 @@
+
+ pub(crate) struct HistoryDb;
+
++#[derive(Debug, Clone)]
++struct LoadedHistory {
++ map: HashMap<HistoryId, HistoryItem>,
++
++ /// This field is effectively the same as `map.keys().collect()`.
++ ///
++ /// It just a cache.
++ keys: Vec<HistoryId>,
++}
++
+ #[derive(Debug)]
+ struct HistoryDbInner {
+ range: Option<Range>,
+ session: uuid::Uuid,
+
+- loaded_history: Vec<History>,
++ loaded_history: LoadedHistory,
+
+ handler: Handler,
+ }
+
+ impl HistoryDbInner {
+- fn loaded_history(&mut self) -> &[History] {
++ fn loaded_history(&mut self) -> &LoadedHistory {
+ if NEW_LOADED_HISTORY_AVAILABLE.load(Ordering::Relaxed) {
+ if let Some(pre_loaded_history) = self.handler.load_history_resp() {
+ self.loaded_history = pre_loaded_history;
+
+ // PERFORMANCE: That seems to improve performance? <2026-08-24>
+- self.loaded_history.reverse();
++ self.loaded_history.keys.reverse();
+ }
+
+ flogf!(
+ history,
+ "Loaded history was requested, returning %d entries.",
+- self.loaded_history.len()
++ self.loaded_history.map.len()
+ );
+
+ NEW_LOADED_HISTORY_AVAILABLE.store(false, Ordering::Relaxed);
+@@ -73,14 +83,7 @@ pub(super) fn create_empty() -> Self {
+
+ /// Return the offsets of items in this file.
+ pub(super) fn offsets(&self) -> Vec<HistoryId> {
+- let out: Vec<_> = Self::with_inner_mut(|inner| {
+- inner
+- .loaded_history()
+- .iter()
+- .map(|h| h.id.clone())
+- .collect()
+- });
+- out
++ Self::with_inner_mut(|inner| inner.loaded_history().keys.clone())
+ }
+
+ /// Return whether this file is empty.
+@@ -111,7 +114,10 @@ pub(super) fn load(history_path: &WString, _boundary_timestamp: SystemTime) -> S
+ let inner = HistoryDbInner {
+ session: uuid::Uuid::now_v7(),
+ range,
+- loaded_history: vec![],
++ loaded_history: LoadedHistory {
++ map: HashMap::new(),
++ keys: vec![],
++ },
+ handler,
+ };
+
+@@ -128,20 +134,7 @@ pub(super) fn load(history_path: &WString, _boundary_timestamp: SystemTime) -> S
+
+ /// Decode an item at a given offset.
+ pub(super) fn decode_item(&self, id: HistoryId) -> Option<HistoryItem> {
+- Self::with_inner_mut(|inner| {
+- inner.loaded_history().iter().find(|h| h.id == id).map(|h| {
+- HistoryItem::new(
+- WString::from_str(&h.command),
+- super::Timestamps {
+- last_added: UNIX_EPOCH
+- + Duration::from_nanos_u128(h.timestamp.unix_timestamp_nanos() as u128),
+- first_added: UNIX_EPOCH
+- + Duration::from_nanos_u128(h.timestamp.unix_timestamp_nanos() as u128),
+- },
+- super::PersistenceMode::Disk,
+- )
+- })
+- })
++ Self::with_inner_mut(|inner| inner.loaded_history().map.get(&id).map(ToOwned::to_owned))
+ }
+ }
+
+@@ -209,7 +202,7 @@ struct Handler {
+
+ cmd_tx: Option<mpsc::Sender<HandleHistoryCmd>>,
+
+- returned_loaded_history: Arc<RwLock<Option<Vec<History>>>>,
++ returned_loaded_history: Arc<RwLock<Option<LoadedHistory>>>,
+ }
+
+ #[derive(Debug)]
+@@ -311,8 +304,47 @@ fn start(daemon_socket: String) -> Self {
+ base.expect("the client to still work")
+ };
+
++ let mut loaded_history_map = HashMap::new();
++
++ for item in loaded_history {
++ loaded_history_map.insert(item.id, {
++ HistoryItem::new(
++ WString::from_str(&item.command),
++ super::Timestamps {
++ last_added: UNIX_EPOCH
++ + Duration::from_nanos_u128(
++ item.timestamp.unix_timestamp_nanos() as u128,
++ ),
++ first_added: UNIX_EPOCH
++ + Duration::from_nanos_u128(
++ item.timestamp.unix_timestamp_nanos() as u128,
++ ),
++ },
++ super::PersistenceMode::Disk,
++ )
++ });
++ }
++
++ let keys = {
++ let mut base: Vec<_> = loaded_history_map
++ .iter()
++ .map(|(key, h)| (key, h.first_added_timestamp()))
++ .collect();
++
++ base.sort_by_key(|(_, time)| *time);
++
++ // reverse this first here, so we can re-reverse it in the
++ // `loaded_history` function.
++ base.reverse();
++
++ base.into_iter().map(|(key, _)| key).copied().collect()
++ };
++
+ let mut output = loaded_history_return.write().expect("not poisioned");
+- (*output) = Some(loaded_history);
++ (*output) = Some(LoadedHistory {
++ keys,
++ map: loaded_history_map,
++ });
+ NEW_LOADED_HISTORY_AVAILABLE.store(true, Ordering::Relaxed);
+ }
+ }
+@@ -343,7 +375,7 @@ fn stop(&mut self) {
+ flog!(history, "History db shutdown completed.");
+ }
+
+- fn load_history_resp(&self) -> Option<Vec<History>> {
++ fn load_history_resp(&self) -> Option<LoadedHistory> {
+ let mut output = None;
+ let rx = &self.returned_loaded_history;
+
+--
+2.55.0
+
diff --git a/pkgs/by-name/fu/fupdate-sys/package.nix b/pkgs/by-name/fu/fupdate-sys/package.nix
index 7fd4674b..6672c2f2 100644
--- a/pkgs/by-name/fu/fupdate-sys/package.nix
+++ b/pkgs/by-name/fu/fupdate-sys/package.nix
@@ -11,7 +11,7 @@
writeShellApplication,
# Dependencies
git,
- nixos-rebuild,
+ nixos-rebuild-ng,
sudo,
openssh,
coreutils,
@@ -19,20 +19,25 @@
gnugrep,
gnused,
systemd,
-}:
-writeShellApplication {
- name = "fupdate-sys";
- text = builtins.readFile ./fupdate-sys.sh;
- inheritPath = false;
- runtimeInputs = [
- git
- nixos-rebuild
- sudo
- openssh
- coreutils
- mktemp
- gnugrep
- gnused
- systemd
- ];
-}
+ lixPackageSets,
+}: let
+ nixos-rebuild-without-nix = nixos-rebuild-ng.override {
+ nix = lixPackageSets.latest.lix;
+ };
+in
+ writeShellApplication {
+ name = "fupdate-sys";
+ text = builtins.readFile ./fupdate-sys.sh;
+ inheritPath = false;
+ runtimeInputs = [
+ git
+ nixos-rebuild-without-nix
+ sudo
+ openssh
+ coreutils
+ mktemp
+ gnugrep
+ gnused
+ systemd
+ ];
+ }
diff --git a/pkgs/by-name/fu/fupdate/Cargo.lock b/pkgs/by-name/fu/fupdate/Cargo.lock
index 9e72636a..644795c1 100644
--- a/pkgs/by-name/fu/fupdate/Cargo.lock
+++ b/pkgs/by-name/fu/fupdate/Cargo.lock
@@ -13,9 +13,9 @@ version = 4
[[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",
@@ -28,15 +28,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",
]
@@ -47,7 +47,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]]
@@ -58,20 +58,20 @@ 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 = "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",
@@ -79,9 +79,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",
@@ -91,9 +91,9 @@ dependencies = [
[[package]]
name = "clap_complete"
-version = "4.5.65"
+version = "4.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d"
+checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19"
dependencies = [
"clap",
"clap_lex",
@@ -103,9 +103,9 @@ 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",
@@ -115,15 +115,15 @@ dependencies = [
[[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 = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "fupdate"
@@ -142,11 +142,11 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_executable"
-version = "1.0.5"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4"
+checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd"
dependencies = [
- "windows-sys 0.60.2",
+ "windows-sys",
]
[[package]]
@@ -163,27 +163,27 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "proc-macro2"
-version = "1.0.105"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.43"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[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 = "strsim"
@@ -193,9 +193,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
-version = "2.0.114"
+version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
@@ -204,9 +204,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
-version = "1.0.22"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "utf8parse"
@@ -222,83 +222,9 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[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"
dependencies = [
"windows-link",
]
-
-[[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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
diff --git a/pkgs/by-name/fu/fupdate/Cargo.toml b/pkgs/by-name/fu/fupdate/Cargo.toml
index b62cee51..35afd716 100644
--- a/pkgs/by-name/fu/fupdate/Cargo.toml
+++ b/pkgs/by-name/fu/fupdate/Cargo.toml
@@ -16,9 +16,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-anyhow = "1.0.100"
-clap = { version = "4.5.54", features = ["derive"] }
-clap_complete = { version = "4.5.65", features = ["unstable-dynamic"] }
+anyhow = "1.0.104"
+clap = { version = "4.6.6", features = ["derive"] }
+clap_complete = { version = "4.6.9", features = ["unstable-dynamic"] }
[profile.release]
lto = true
diff --git a/pkgs/by-name/fu/fupdate/flake.lock b/pkgs/by-name/fu/fupdate/flake.lock
deleted file mode 100644
index 1e997998..00000000
--- a/pkgs/by-name/fu/fupdate/flake.lock
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "nodes": {
- "nixpkgs": {
- "locked": {
- "lastModified": 1768661221,
- "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "3327b113f2ef698d380df83fbccefad7e83d7769",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "nixpkgs": "nixpkgs"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/pkgs/by-name/fu/fupdate/flake.lock.license b/pkgs/by-name/fu/fupdate/flake.lock.license
deleted file mode 100644
index eae6a84c..00000000
--- a/pkgs/by-name/fu/fupdate/flake.lock.license
+++ /dev/null
@@ -1,9 +0,0 @@
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/pkgs/by-name/fu/fupdate/flake.nix b/pkgs/by-name/fu/fupdate/flake.nix
index f06e27ec..ca4d41c8 100644
--- a/pkgs/by-name/fu/fupdate/flake.nix
+++ b/pkgs/by-name/fu/fupdate/flake.nix
@@ -10,16 +10,16 @@
{
description = "This is a Nix flake update manager.";
- inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
- };
+ inputs = {};
- outputs = {nixpkgs, ...}: let
+ outputs = {...}: let
system = "x86_64-linux";
- pkgs = nixpkgs.legacyPackages."${system}";
+ sources = import ../../../../unflake.nix;
+
+ pkgs = sources.nixpkgs.legacyPackages."${system}";
in {
devShells."${system}".default = pkgs.mkShell {
- packages = [
+ packages = [
pkgs.cargo
pkgs.clippy
pkgs.rustc
diff --git a/pkgs/by-name/fu/fupdate/src/cli.rs b/pkgs/by-name/fu/fupdate/src/cli.rs
index 6ebd1bc4..a7168413 100644
--- a/pkgs/by-name/fu/fupdate/src/cli.rs
+++ b/pkgs/by-name/fu/fupdate/src/cli.rs
@@ -11,7 +11,7 @@
use std::{env, ffi::OsStr, fs::read_dir};
use clap::Parser;
-use clap_complete::{engine::ArgValueCompleter, CompletionCandidate};
+use clap_complete::{CompletionCandidate, engine::ArgValueCompleter};
/// This is a Nix flake update manager.
#[derive(Parser, Debug)]
diff --git a/pkgs/by-name/fu/fupdate/src/main.rs b/pkgs/by-name/fu/fupdate/src/main.rs
index b4af6cd6..2200ce83 100644
--- a/pkgs/by-name/fu/fupdate/src/main.rs
+++ b/pkgs/by-name/fu/fupdate/src/main.rs
@@ -10,7 +10,7 @@
use std::process::Command;
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use clap::{CommandFactory, Parser};
pub mod cli;
diff --git a/pkgs/by-name/fu/fupdate/update.sh b/pkgs/by-name/fu/fupdate/update.sh
index 8e36e13e..5ad524e8 100755
--- a/pkgs/by-name/fu/fupdate/update.sh
+++ b/pkgs/by-name/fu/fupdate/update.sh
@@ -10,5 +10,5 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-[ "$1" = "upgrade" ] && cargo upgrade
-cargo update
+[ "$1" = "upgrade" ] && cargo upgrade --incompatible allow --pinned allow --recursive true
+cargo update --recursive
diff --git a/pkgs/by-name/gi/git-cgit/git-cgit.sh b/pkgs/by-name/gi/git-cgit/git-cgit.sh
new file mode 100755
index 00000000..d24fda15
--- /dev/null
+++ b/pkgs/by-name/gi/git-cgit/git-cgit.sh
@@ -0,0 +1,131 @@
+#!/usr/bin/env sh
+
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+NAME="git-cgit"
+
+not_empty() {
+ arg="$1"
+ name="$2"
+
+ if [ "$arg" = "" ]; then
+ echo 1>&2 "flag '$name' is empty"
+ exit 2
+ fi
+}
+
+help() {
+ cat <<EOF
+Create a cgit repo to push the current repo to.
+
+USAGE:
+ $NAME [OPTIONS] --git URL..
+
+OPTIONS:
+ --help | -h
+ Display this help and exit.
+ --git URL
+ git upstream URL.
+ Should be in the form:
+ 'ssh://git@<url>/path/to/repo'
+ --desc DESC
+ Optional description (will be read from './.git/description' otherwise).
+ --defbranch
+ Optional default branch
+ --cgit-owner
+ Optional cgit owner
+EOF
+}
+
+git_url=""
+ssh_url=""
+url_path=""
+desc="$(cat "$(git rev-parse --show-toplevel)/.git/description")"
+
+cgit_owner="$(id --name -u)"
+defbranch="$(git branch --show-current)"
+
+while [ "$#" -ne 0 ]; do
+ case "$1" in
+ "--help" | "-h")
+ help
+ exit 0
+ ;;
+ "--git")
+ shift 1
+ git_url="$1"
+ ;;
+ "--desc")
+ shift 1
+ desc="$1"
+ ;;
+ "--defbranch")
+ shift 1
+ defbranch="$1"
+ ;;
+ "--cgit-owner")
+ shift 1
+ cgit_owner="$1"
+ ;;
+ esac
+ shift 1
+done
+
+# In the form git@<url>
+ssh_url="${git_url#ssh://}"
+while [ "$(dirname "$ssh_url")" != "." ]; do
+ ssh_url="$(dirname "$ssh_url")"
+done
+url_path="${git_url#ssh://"$ssh_url"}"
+url_path="${url_path#/}"
+
+not_empty "$git_url" "--git"
+not_empty "$ssh_url" "--git <indirectly>"
+not_empty "$url_path" "--git <indirectly>"
+
+not_empty "$desc" "--desc"
+not_empty "$defbranch" "--defbranch"
+not_empty "$cgit_owner" "--cgit-owner"
+
+cat <<EOF
+Initializing repo with following values:
+ --git -> '$git_url'
+ <ssh_url> -> '$ssh_url'
+ <url_path> -> '$url_path'
+
+ --desc -> '$desc'
+ --defbranch -> '$defbranch'
+ --cgit-owner -> '$cgit_owner'
+
+EOF
+
+printf "Continue [y/N]? "
+read -r continue
+if [ "$continue" != "y" ]; then
+ echo 1>&2 "Not continuing.."
+ exit 1
+fi
+
+set -x
+
+git remote remove origin
+git remote add origin "$git_url"
+
+git push --set-upstream origin "$defbranch"
+
+ssh "$ssh_url" -- perms "$url_path" + READERS @all
+ssh "$ssh_url" -- desc "$url_path" "$desc"
+
+ssh "$ssh_url" -- config "$url_path" --add cgit.defbranch "$defbranch"
+ssh "$ssh_url" -- config "$url_path" --add cgit.owner "$cgit_owner"
+
+ssh "$ssh_url" -- info
+# vim: ft=sh
diff --git a/pkgs/by-name/i3/i3status-rust-patched/package.nix b/pkgs/by-name/gi/git-cgit/package.nix
index a103e275..b64c5e84 100644
--- a/pkgs/by-name/i3/i3status-rust-patched/package.nix
+++ b/pkgs/by-name/gi/git-cgit/package.nix
@@ -8,15 +8,21 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
{
- i3status-rust,
+ writeShellApplication,
+ # Dependencies
+ coreutils,
+ git,
+ openssh,
}:
-i3status-rust.overrideAttrs (final: prev: {
- pname = "${prev.pname}-patched";
+writeShellApplication {
+ name = "git-cgit";
+ text = builtins.readFile ./git-cgit.sh;
- patches =
- (prev.patches or [])
- ++ [
- # Btrfs support for disk_space block.
- ./patches/0001-disk_space-Support-btrfs-backend.patch
- ];
-})
+ inheritPath = false;
+
+ runtimeInputs = [
+ coreutils
+ git
+ openssh
+ ];
+}
diff --git a/pkgs/by-name/hi/hibernate/hibernate.sh b/pkgs/by-name/hi/hibernate/hibernate.sh
index 4a68e0d7..5f2a524a 100755
--- a/pkgs/by-name/hi/hibernate/hibernate.sh
+++ b/pkgs/by-name/hi/hibernate/hibernate.sh
@@ -10,7 +10,8 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-# TODO(@bpeetz): This functionality could be moved to `tskm`. <2025-04-14>
+# TODO(@bpeetz): The task context reset functionality could be moved to `tskm`. <2025-04-14>
+# TODO: This whole script should be a systemd service with Before=hibernate.target <2026-06-26>
context="$(task _get rc.context)"
[ "$context" ] && task context none
@@ -19,6 +20,10 @@ context="$(task _get rc.context)"
active="$(task +ACTIVE _ids)"
[ "$active" ] && task stop "$active"
+# Make sure that the swayidle service is actually running. Otherwise we don't get a
+# screenlock upon hibernate-resume.
+systemctl --user start swayidle.service
+
systemctl hibernate "$@"
# vim: ft=sh
diff --git a/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch b/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch
deleted file mode 100644
index 8ef0af2e..00000000
--- a/pkgs/by-name/i3/i3status-rust-patched/patches/0001-disk_space-Support-btrfs-backend.patch
+++ /dev/null
@@ -1,190 +0,0 @@
-From 78d2936b67064e3b5e700a2859d00ea3dd6eda4c Mon Sep 17 00:00:00 2001
-From: Benedikt Peetz <benedikt.peetz@b-peetz.de>
-Date: Sun, 18 May 2025 20:22:04 +0200
-Subject: [PATCH 1/3] disk_space: Support btrfs backend
-
-Btrfs is too smart for the statvfs based backend (i.e., only counting
-blocks leads to wrong numbers).
-
-For example, a btrfs disk with a lot of de-duplicated blocks (via the copy
-on write mechanism) might have a drastically over-reported disk usage.
-
-The btrfs backend is currently implemented by parsing the output of the
-`btrfs filesystem usage --raw` command. This is suboptimal, as this now
-relies on the command output not changing.
-
-Vendoring the algorithm used internally by the `btrfs` command does not
-seem to be a reasonable alternative, considering that the code[1] is
-rather complex, low level and would require semi-constant maintenance.
-Additionally, the c code would need bindings to be usable from rust.
-
-I assume, that the `btrfs` command output will stay rather similar in
-the future, as a lot of tools rely on directly parsing it (see the
-various scripts in the issue, this commit fixes).
-
-[1]: https://github.com/kdave/btrfs-progs/blob/eeab081e9d9fbdf4583122ed1caedf541383cf2d/cmds/filesystem-usage.c#L442
-
-Fixes: #1654
----
- src/blocks/disk_space.rs | 112 +++++++++++++++++++++++++++++++++++----
- 1 file changed, 101 insertions(+), 11 deletions(-)
-
-diff --git a/src/blocks/disk_space.rs b/src/blocks/disk_space.rs
-index 79bfebd27..da0d3f518 100644
---- a/src/blocks/disk_space.rs
-+++ b/src/blocks/disk_space.rs
-@@ -12,6 +12,7 @@
- //! `alert` | A value which will trigger critical block state | `10.0`
- //! `info_type` | Determines which information will affect the block state. Possible values are `"available"`, `"free"` and `"used"` | `"available"`
- //! `alert_unit` | The unit of `alert` and `warning` options. If not set, percents are used. Possible values are `"B"`, `"KB"`, `"KiB"`, `"MB"`, `"MiB"`, `"GB"`, `"Gib"`, `"TB"` and `"TiB"` | `None`
-+//! `backend` | The backend to use when querying disk usage. Possible values are `"vfs"` (like `du(1)`) and `"btrfs"` | `"vfs"`
- //!
- //! Placeholder | Value | Type | Unit
- //! -------------|--------------------------------------------------------------------|--------|-------
-@@ -63,9 +64,12 @@
-
- // make_log_macro!(debug, "disk_space");
-
-+use std::cell::OnceCell;
-+
- use super::prelude::*;
- use crate::formatting::prefix::Prefix;
- use nix::sys::statvfs::statvfs;
-+use tokio::process::Command;
-
- #[derive(Copy, Clone, Debug, Deserialize, SmartDefault)]
- #[serde(rename_all = "lowercase")]
-@@ -76,11 +80,20 @@ pub enum InfoType {
- Used,
- }
-
-+#[derive(Copy, Clone, Debug, Deserialize, SmartDefault)]
-+#[serde(rename_all = "lowercase")]
-+pub enum Backend {
-+ #[default]
-+ Vfs,
-+ Btrfs,
-+}
-+
- #[derive(Deserialize, Debug, SmartDefault)]
- #[serde(deny_unknown_fields, default)]
- pub struct Config {
- #[default("/".into())]
- pub path: ShellString,
-+ pub backend: Backend,
- pub info_type: InfoType,
- pub format: FormatConfig,
- pub format_alt: Option<FormatConfig>,
-@@ -128,17 +141,9 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
- loop {
- let mut widget = Widget::new().with_format(format.clone());
-
-- let statvfs = statvfs(&*path).error("failed to retrieve statvfs")?;
--
-- // Casting to be compatible with 32-bit systems
-- #[allow(clippy::unnecessary_cast)]
-- let (total, used, available, free) = {
-- let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64);
-- let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64))
-- * (statvfs.fragment_size() as u64);
-- let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64);
-- let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64);
-- (total, used, available, free)
-+ let (total, used, available, free) = match config.backend {
-+ Backend::Vfs => get_vfs(&*path)?,
-+ Backend::Btrfs => get_btrfs(&path).await?,
- };
-
- let result = match config.info_type {
-@@ -205,3 +210,88 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
- }
- }
- }
-+
-+fn get_vfs<P>(path: &P) -> Result<(u64, u64, u64, u64)>
-+where
-+ P: ?Sized + nix::NixPath,
-+{
-+ let statvfs = statvfs(path).error("failed to retrieve statvfs")?;
-+
-+ // Casting to be compatible with 32-bit systems
-+ #[allow(clippy::unnecessary_cast)]
-+ {
-+ let total = (statvfs.blocks() as u64) * (statvfs.fragment_size() as u64);
-+ let used = ((statvfs.blocks() as u64) - (statvfs.blocks_free() as u64))
-+ * (statvfs.fragment_size() as u64);
-+ let available = (statvfs.blocks_available() as u64) * (statvfs.block_size() as u64);
-+ let free = (statvfs.blocks_free() as u64) * (statvfs.block_size() as u64);
-+
-+ Ok((total, used, available, free))
-+ }
-+}
-+
-+async fn get_btrfs(path: &str) -> Result<(u64, u64, u64, u64)> {
-+ const OUTPUT_CHANGED: &str = "Btrfs filesystem usage output format changed";
-+
-+ fn remove_estimate_min(estimate_str: &str) -> Result<&str> {
-+ estimate_str.trim_matches('\t')
-+ .split_once("\t")
-+ .ok_or(Error::new(OUTPUT_CHANGED))
-+ .map(|v| v.0)
-+ }
-+
-+ macro_rules! get {
-+ ($source:expr, $name:expr, $variable:ident) => {
-+ get!(@pre_op (|a| {Ok::<_, Error>(a)}), $source, $name, $variable)
-+ };
-+ (@pre_op $function:expr, $source:expr, $name:expr, $variable:ident) => {
-+ if $source.starts_with(concat!($name, ":")) {
-+ let (found_name, variable_str) =
-+ $source.split_once(":").ok_or(Error::new(OUTPUT_CHANGED))?;
-+
-+ let variable_str = $function(variable_str)?;
-+
-+ debug_assert_eq!(found_name, $name);
-+ $variable
-+ .set(variable_str.trim().parse().error(OUTPUT_CHANGED)?)
-+ .map_err(|_| Error::new(OUTPUT_CHANGED))?;
-+ }
-+ };
-+ }
-+
-+ let filesystem_usage = Command::new("btrfs")
-+ .args(["filesystem", "usage", "--raw", path])
-+ .output()
-+ .await
-+ .error("Failed to collect btrfs filesystem usage info")?
-+ .stdout;
-+
-+ {
-+ let final_total = OnceCell::new();
-+ let final_used = OnceCell::new();
-+ let final_free = OnceCell::new();
-+
-+ let mut lines = filesystem_usage.lines();
-+ while let Some(line) = lines
-+ .next_line()
-+ .await
-+ .error("Failed to read output of btrfs filesystem usage")?
-+ {
-+ let line = line.trim();
-+
-+ // See btrfs-filesystem(8) for an explanation for the rows.
-+ get!(line, "Device size", final_total);
-+ get!(line, "Used", final_used);
-+ get!(@pre_op remove_estimate_min, line, "Free (estimated)", final_free);
-+ }
-+
-+ Ok((
-+ *final_total.get().ok_or(Error::new(OUTPUT_CHANGED))?,
-+ *final_used.get().ok_or(Error::new(OUTPUT_CHANGED))?,
-+ // HACK(@bpeetz): We also return the free disk space as the available one, because btrfs
-+ // does not tell us which disk space is reserved for the fs. <2025-05-18>
-+ *final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?,
-+ *final_free.get().ok_or(Error::new(OUTPUT_CHANGED))?,
-+ ))
-+ }
-+}
---
-2.49.0
-
diff --git a/pkgs/by-name/lf/lf-make-map/Cargo.lock b/pkgs/by-name/lf/lf-make-map/Cargo.lock
index 1e293fde..97b82460 100644
--- a/pkgs/by-name/lf/lf-make-map/Cargo.lock
+++ b/pkgs/by-name/lf/lf-make-map/Cargo.lock
@@ -13,18 +13,18 @@ version = 4
[[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 +37,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",
]
@@ -72,27 +72,33 @@ dependencies = [
[[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 = "autocfg"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[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 = "cc"
-version = "1.2.53"
+version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
+checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -106,9 +112,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[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",
@@ -119,9 +125,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",
@@ -129,9 +135,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",
@@ -141,27 +147,27 @@ 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.3",
]
[[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 = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "core-foundation-sys"
@@ -170,10 +176,68 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
+name = "crossterm"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
+dependencies = [
+ "bitflags",
+ "document-features",
+ "mio",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "signal-hook-mio",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
name = "find-msvc-tools"
-version = "0.1.8"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
+checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
+
+[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
[[package]]
name = "heck"
@@ -189,9 +253,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[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",
@@ -230,11 +294,12 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "js-sys"
-version = "0.3.85"
+version = "0.3.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
dependencies = [
- "once_cell",
+ "cfg-if",
+ "futures-util",
"wasm-bindgen",
]
@@ -253,6 +318,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
+ "crossterm",
"keymaps",
"log",
"stderrlog",
@@ -261,15 +327,48 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.180"
+version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[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"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
[[package]]
name = "log"
-version = "0.4.29"
+version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+
+[[package]]
+name = "mio"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
[[package]]
name = "num-traits"
@@ -282,9 +381,9 @@ dependencies = [
[[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"
@@ -293,28 +392,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
name = "proc-macro2"
-version = "1.0.105"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.43"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "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 = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "same-file"
@@ -326,10 +476,59 @@ dependencies = [
]
[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
name = "shlex"
-version = "1.3.0"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+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"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "stderrlog"
@@ -352,9 +551,20 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[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.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
@@ -372,38 +582,38 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.17"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.17"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.3",
]
[[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 = "unicode-ident"
-version = "1.0.22"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "utf8parse"
@@ -422,10 +632,16 @@ dependencies = [
]
[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
name = "wasm-bindgen"
-version = "0.2.108"
+version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
dependencies = [
"cfg-if",
"once_cell",
@@ -436,9 +652,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.108"
+version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -446,22 +662,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.108"
+version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.108"
+version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
dependencies = [
"unicode-ident",
]
@@ -496,7 +712,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
@@ -507,7 +723,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
diff --git a/pkgs/by-name/lf/lf-make-map/Cargo.toml b/pkgs/by-name/lf/lf-make-map/Cargo.toml
index 15d48115..1925b273 100644
--- a/pkgs/by-name/lf/lf-make-map/Cargo.toml
+++ b/pkgs/by-name/lf/lf-make-map/Cargo.toml
@@ -17,9 +17,10 @@ edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-anyhow = "1.0.100"
-clap = { version = "4.5.54", features = ["derive", "env"] }
+anyhow = "1.0.104"
+clap = { version = "4.6.6", features = ["derive", "env"] }
+crossterm = {version = "0.29.0", default-features = false, features = ["events"]}
keymaps = "1.2.0"
-log = "0.4.29"
+log = "0.4.34"
stderrlog = "0.6.0"
walkdir = "2.5.0"
diff --git a/pkgs/by-name/lf/lf-make-map/flake.lock b/pkgs/by-name/lf/lf-make-map/flake.lock
deleted file mode 100644
index 1e997998..00000000
--- a/pkgs/by-name/lf/lf-make-map/flake.lock
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "nodes": {
- "nixpkgs": {
- "locked": {
- "lastModified": 1768661221,
- "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "3327b113f2ef698d380df83fbccefad7e83d7769",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "nixpkgs": "nixpkgs"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/pkgs/by-name/lf/lf-make-map/flake.lock.license b/pkgs/by-name/lf/lf-make-map/flake.lock.license
deleted file mode 100644
index eae6a84c..00000000
--- a/pkgs/by-name/lf/lf-make-map/flake.lock.license
+++ /dev/null
@@ -1,9 +0,0 @@
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/pkgs/by-name/lf/lf-make-map/flake.nix b/pkgs/by-name/lf/lf-make-map/flake.nix
index 8ce8ff0f..743103b6 100644
--- a/pkgs/by-name/lf/lf-make-map/flake.nix
+++ b/pkgs/by-name/lf/lf-make-map/flake.nix
@@ -10,13 +10,13 @@
{
description = "An automatic lf cd mapping generator";
- inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
- };
+ inputs = {};
- outputs = {nixpkgs, ...}: let
+ outputs = {...}: let
system = "x86_64-linux";
- pkgs = nixpkgs.legacyPackages."${system}";
+ sources = import ../../../../unflake.nix;
+
+ pkgs = sources.nixpkgs.legacyPackages."${system}";
in {
devShells."${system}".default = pkgs.mkShell {
packages = [
diff --git a/pkgs/by-name/lf/lf-make-map/src/cli.rs b/pkgs/by-name/lf/lf-make-map/src/cli.rs
index 70746984..25a5a626 100644
--- a/pkgs/by-name/lf/lf-make-map/src/cli.rs
+++ b/pkgs/by-name/lf/lf-make-map/src/cli.rs
@@ -50,6 +50,12 @@ pub enum Command {
#[command(flatten)]
options: CommandOptions,
},
+
+ /// Perform interactive selection, and then cd-there.
+ Interactive {
+ #[command(flatten)]
+ options: CommandOptions,
+ },
}
#[derive(Debug, Parser)]
diff --git a/pkgs/by-name/lf/lf-make-map/src/main.rs b/pkgs/by-name/lf/lf-make-map/src/main.rs
index d5d934e1..1576c1dd 100644
--- a/pkgs/by-name/lf/lf-make-map/src/main.rs
+++ b/pkgs/by-name/lf/lf-make-map/src/main.rs
@@ -39,6 +39,7 @@ fn main() -> anyhow::Result<()> {
let relevant_directories = match &args.command {
Command::Visualize { options } => &options.relevant_directories,
Command::Generate { options } => &options.relevant_directories,
+ Command::Interactive { options } => &options.relevant_directories,
};
for dir in relevant_directories {
@@ -99,6 +100,7 @@ fn main() -> anyhow::Result<()> {
match args.command {
Command::Visualize { .. } => println!("{}", mappings.0),
Command::Generate { .. } => println!("{}", mappings.to_lf_mappings(args.home_name)),
+ Command::Interactive { .. } => mappings.interactive_start(args.home_name)?,
}
Ok(())
@@ -144,90 +146,3 @@ because it can't be turned to a string
)
})
}
-
-// fn gen_lf_mappings(home_name: PathBuf, char_num: usize, rel_dirs: Vec<PathBuf>) {
-// let mut mappings_vec = vec![];
-// let mut index_counter = 0;
-// rel_dirs.iter().for_each(|rel_dir| {
-// mappings_vec.push(vec![Mapping::new(
-// &gen_hot_key(rel_dir, rel_dir, char_num),
-// rel_dir,
-// rel_dir,
-// None,
-// )]);
-// get_dir(rel_dir.to_owned()).iter().for_each(|path| {
-// mappings_vec[index_counter].push(Mapping::new(
-// &gen_hot_key(
-// path,
-// path.parent().expect("All paths here should have parents"),
-// char_num,
-// ),
-// path,
-// &path
-// .parent()
-// .expect("All paths here should have parents")
-// .to_owned(),
-// None,
-// ));
-// });
-// index_counter += 1;
-// });
-// print_mappings(&mappings_vec, home_name);
-// mappings_vec
-// .into_iter()
-// .for_each(|rel_dir_mapping: Vec<Mapping>| {
-// let mut hash_map = sort_mapping_by_hot_key(rel_dir_mapping.clone());
-// //dbg!(hash_map);
-// hash_map.insert("gsi".to_owned(), vec![rel_dir_mapping[0].clone()]);
-// });
-// }
-//
-// fn sort_mapping_by_hot_key(mut mappings: Vec<Mapping>) -> HashMap<String, Vec<Mapping>> {
-// mappings.sort_by_key(|mapping| mapping.hot_key.clone());
-//
-// let mut filtered_mappings: HashMap<String, Vec<Mapping>> = HashMap::new();
-// mappings.iter().for_each(|mapping| {
-// filtered_mappings.insert(mapping.hot_key.clone(), vec![]);
-// });
-// //dbg!(&mappings);
-//
-// let mut index_counter = 1;
-// mappings.iter().for_each(|mapping| {
-// if mappings.len() > index_counter {
-// let next_mapping = &mappings[index_counter];
-// let vec = filtered_mappings
-// .get_mut(&mapping.hot_key)
-// .expect("This existst as it has been initialized");
-//
-// if &next_mapping.hot_key == &mapping.hot_key {
-// vec.push(mapping.clone());
-// vec.push(next_mapping.clone());
-// } else {
-// vec.push(mapping.clone());
-// }
-//
-// let new_vec = vec.to_owned();
-// filtered_mappings.insert(mapping.hot_key.to_owned(), new_vec);
-// }
-//
-// index_counter += 1;
-// });
-// filtered_mappings
-// }
-//
-// fn print_mappings(mappings: &Vec<Vec<Mapping>>, home_name: PathBuf) {
-// for mapping in mappings {
-// mapping.iter().for_each(|map| {
-// println!(
-// "{} = \"cd {}\";",
-// map.hot_key,
-// map.path
-// .display()
-// .to_string()
-// .replace(home_name.to_str().expect("This should be UTF-8"), "~")
-// );
-// });
-//
-// println!("# -------------");
-// }
-// }
diff --git a/pkgs/by-name/lf/lf-make-map/src/mapping/interactive.rs b/pkgs/by-name/lf/lf-make-map/src/mapping/interactive.rs
new file mode 100644
index 00000000..b8ac27d2
--- /dev/null
+++ b/pkgs/by-name/lf/lf-make-map/src/mapping/interactive.rs
@@ -0,0 +1,172 @@
+// nixos-config - My current NixOS configuration
+//
+// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+// This file is part of my nixos-config.
+//
+// You should have received a copy of the License along with this program.
+// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+use std::{io::stderr, path::PathBuf};
+
+use anyhow::Result;
+use crossterm::{
+ cursor::{MoveToRow, MoveUp},
+ event::{
+ Event, KeyEventKind, KeyModifiers, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags,
+ PushKeyboardEnhancementFlags, read,
+ },
+ execute,
+ style::Print,
+ terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
+};
+
+use crate::mapping::map_key::MapKey;
+
+use super::MappingsTrie;
+
+enum Status {
+ Done(PathBuf),
+ Stop,
+}
+
+impl MappingsTrie {
+ pub fn interactive_start(&self, home_path: PathBuf) -> Result<()> {
+ terminal::enable_raw_mode()?;
+ execute!(
+ stderr(),
+ EnterAlternateScreen,
+ PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES),
+ MoveToRow(0)
+ )?;
+
+ let output = self.interactive_start_inner(home_path);
+
+ execute!(stderr(), LeaveAlternateScreen, PopKeyboardEnhancementFlags)?;
+ terminal::disable_raw_mode()?;
+
+ match output? {
+ Status::Done(path_buf) => {
+ println!("{}", path_buf.display())
+ }
+ Status::Stop => (),
+ }
+
+ Ok(())
+ }
+
+ fn interactive_start_inner(&self, home_path: PathBuf) -> Result<Status> {
+ macro_rules! done {
+ ($state:ident, $last_length:ident) => {{
+ let value = match self.0.get(&$state).expect("Is some").value() {
+ Some(value) => value,
+ None => return Ok(Status::Stop),
+ };
+
+ let path = home_path.join(&value.path);
+
+ terminal::disable_raw_mode()?;
+ execute!(
+ stderr(),
+ MoveUp($last_length as u16),
+ Clear(ClearType::FromCursorDown),
+ Print(format!("{}\n", path.display()))
+ )?;
+ terminal::enable_raw_mode()?;
+
+ return Ok(Status::Done(path));
+ }};
+ }
+
+ let mut state: Vec<MapKey> = vec![];
+ let mut last_length: usize = 1;
+ while let (trie, matched) = self.0.try_get(&state)
+ && matched == state
+ {
+ if trie.value().is_some() {
+ done!(state, last_length);
+ } else {
+ if let Some(last) = state.last_mut()
+ && let Some(mlast) = matched.last()
+ {
+ last.resolution = mlast.resolution;
+ mlast.part_path.clone_into(&mut last.part_path);
+ }
+ }
+
+ {
+ terminal::disable_raw_mode()?;
+ let string = trie.to_string();
+ execute!(
+ stderr(),
+ MoveUp(last_length as u16),
+ Clear(ClearType::FromCursorDown),
+ Print(format!(
+ "Current state: {}\n",
+ self.current_progress(home_path.display().to_string(), &state)
+ )),
+ Print(&string)
+ )?;
+ last_length = string.lines().count() + 1;
+ terminal::enable_raw_mode()?;
+ }
+
+ if let Event::Key(event) = read()?
+ && event.kind == KeyEventKind::Press
+ {
+ match event.code {
+ crossterm::event::KeyCode::Backspace => {
+ state.pop();
+ }
+ crossterm::event::KeyCode::Enter => done!(state, last_length),
+ crossterm::event::KeyCode::Esc => break,
+ crossterm::event::KeyCode::Char(char) => {
+ if event.modifiers == KeyModifiers::CONTROL && char == 'c' {
+ break;
+ } else {
+ state.push(MapKey {
+ key: char,
+ resolution: 0,
+ part_path: String::new(),
+ });
+ }
+ }
+
+ crossterm::event::KeyCode::Left
+ | crossterm::event::KeyCode::Right
+ | crossterm::event::KeyCode::Up
+ | crossterm::event::KeyCode::Down
+ | crossterm::event::KeyCode::Home
+ | crossterm::event::KeyCode::End
+ | crossterm::event::KeyCode::PageUp
+ | crossterm::event::KeyCode::PageDown
+ | crossterm::event::KeyCode::Tab
+ | crossterm::event::KeyCode::BackTab
+ | crossterm::event::KeyCode::Delete
+ | crossterm::event::KeyCode::Insert
+ | crossterm::event::KeyCode::F(_)
+ | crossterm::event::KeyCode::Null
+ | crossterm::event::KeyCode::CapsLock
+ | crossterm::event::KeyCode::ScrollLock
+ | crossterm::event::KeyCode::NumLock
+ | crossterm::event::KeyCode::PrintScreen
+ | crossterm::event::KeyCode::Pause
+ | crossterm::event::KeyCode::Menu
+ | crossterm::event::KeyCode::KeypadBegin
+ | crossterm::event::KeyCode::Media(_)
+ | crossterm::event::KeyCode::Modifier(_) => (),
+ }
+ }
+ }
+
+ Ok(Status::Stop)
+ }
+
+ fn current_progress(&self, home_path: String, state: &[MapKey]) -> String {
+ state
+ .iter()
+ .map(|mk| &mk.part_path)
+ .fold(home_path, |acc, part| format!("{acc}/{part}"))
+ }
+}
diff --git a/pkgs/by-name/lf/lf-make-map/src/mapping/map_key.rs b/pkgs/by-name/lf/lf-make-map/src/mapping/map_key.rs
index 10b612fd..6d88d5af 100644
--- a/pkgs/by-name/lf/lf-make-map/src/mapping/map_key.rs
+++ b/pkgs/by-name/lf/lf-make-map/src/mapping/map_key.rs
@@ -48,8 +48,8 @@ impl PartialOrd for MapKey {
}
impl MapKey {
- pub fn new_from_part_path(part_path: &str, resolution: usize) -> Vec<Self> {
- let key = Self::part_path_to_key(part_path, resolution);
+ pub fn new_from_part_path(part_path: &str, resolution: usize, full_path: &str) -> Vec<Self> {
+ let key = Self::path_part_to_key(part_path, resolution, Some(full_path));
key.chars()
.map(|ch| Self {
@@ -63,7 +63,7 @@ impl MapKey {
pub fn new_ones_from_path(path: &str, number_of_chars: usize) -> Vec<Self> {
let key: Vec<MapKey> = path
.split('/')
- .flat_map(|part| Self::new_from_part_path(part, number_of_chars))
+ .flat_map(|part| Self::new_from_part_path(part, number_of_chars, path))
.collect();
debug!(
@@ -80,14 +80,13 @@ impl MapKey {
// debug!("Incrementing: '{}' ('{}')", &self, &self.part_path);
let added_chars = if new_resolution < self.part_path.len() {
- MapKey::part_path_to_key(&self.part_path, new_resolution)
+ MapKey::path_part_to_key(&self.part_path, new_resolution, None)
} else {
let mut generated_chars =
- MapKey::part_path_to_key(&self.part_path, self.part_path.len());
+ MapKey::path_part_to_key(&self.part_path, self.part_path.len(), None);
generated_chars.extend(
(0..(new_resolution - self.part_path.len()))
- .into_iter()
.map(|_| self.part_path.chars().last().expect("This will exists")),
);
@@ -110,19 +109,24 @@ impl MapKey {
}
pub fn display(values: &[Self]) -> String {
- values.iter().map(|value| value.key.clone()).collect()
+ values.iter().map(|value| value.key).collect()
}
- fn part_path_to_key(part: &str, number_of_chars: usize) -> String {
- fn make(pat: char, part: &str, number_of_chars: usize) -> String {
+ fn path_part_to_key(part: &str, number_of_chars: usize, full_path: Option<&str>) -> String {
+ fn make(pat: char, part: &str, number_of_chars: usize, full_path: Option<&str>) -> String {
let mut acc = String::new();
- if !part.split(pat).all(|part| part.len() > 0) {
+ if part.split(pat).all(|split| split.is_empty()) {
panic!(
"\
-Can't turn this path '{}' to a mapping.
-This should not happen, please report the bug!",
- part
+Failed to split path part `{part}`, with pattern `{pat}`, because all resulting split parts are empty. {}
+This should not happen, please report this bug!", {
+ if let Some(full_path) = full_path {
+ format!("(Full path was: `{full_path}`)")
+ } else {
+ String::new()
+ }
+ }
)
}
@@ -149,15 +153,22 @@ This should not happen, please report the bug!",
}
let value = if part.contains('_') && !part.starts_with('_') && !part.ends_with('_') {
- make('_', part, number_of_chars)
+ make('_', part, number_of_chars, full_path)
} else if part.contains('-') && !part.starts_with('-') && !part.ends_with('-') {
- make('-', part, number_of_chars)
+ make('-', part, number_of_chars, full_path)
+ } else if part.starts_with('.') {
+ // HACK: Special case for directories like ~/.config ~/.local and so on.
+ // We just drop the starting '.' and it's easier to type. <2026-06-02>
+ part.chars()
+ .skip(1)
+ .take(number_of_chars)
+ .collect::<String>()
} else {
part.chars().take(number_of_chars).collect::<String>()
};
assert_eq!(
- value.len(),
+ value.chars().count(),
number_of_chars,
"'{}' does not have expected length of: {}",
value,
diff --git a/pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs b/pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs
index 21392388..b733990e 100644
--- a/pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs
+++ b/pkgs/by-name/lf/lf-make-map/src/mapping/mod.rs
@@ -14,6 +14,7 @@ use log::{Level, debug, log_enabled, trace};
use map_key::MapKey;
pub mod lf_mapping;
+pub mod interactive;
pub mod map_key;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
diff --git a/pkgs/by-name/lf/lf-make-map/tests/base.sh b/pkgs/by-name/lf/lf-make-map/tests/base.sh
index c7694985..d860cf84 100755
--- a/pkgs/by-name/lf/lf-make-map/tests/base.sh
+++ b/pkgs/by-name/lf/lf-make-map/tests/base.sh
@@ -18,7 +18,7 @@ execute_make_maps() {
}
fd . cases --max-depth 1 --type directory | while read -r case; do
- echo "Executing '$case/test.sh'"
+ echo "Executing '${case}test.sh'"
# shellcheck source=/dev/null
LOCATION="$case/test.sh" . "$case/test.sh"
diff --git a/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs/output.old b/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs/output.old
new file mode 100644
index 00000000..49307bc6
--- /dev/null
+++ b/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs/output.old
@@ -0,0 +1,13 @@
+map gc. cd "/tmp/tmp.DfcgjemfCG/.config"
+map gca. cd "/tmp/tmp.DfcgjemfCG/.config/apzu"
+map gcl. cd "/tmp/tmp.DfcgjemfCG/.config/lahmu"
+map gct. cd "/tmp/tmp.DfcgjemfCG/.config/tiamat"
+map gd. cd "/tmp/tmp.DfcgjemfCG/documents"
+map gda. cd "/tmp/tmp.DfcgjemfCG/documents/apzu"
+map gdl. cd "/tmp/tmp.DfcgjemfCG/documents/lahmu"
+map gdt. cd "/tmp/tmp.DfcgjemfCG/documents/tiamat"
+map gl. cd "/tmp/tmp.DfcgjemfCG/.local"
+map gln. cd "/tmp/tmp.DfcgjemfCG/.local/nvim"
+map glsh. cd "/tmp/tmp.DfcgjemfCG/.local/share"
+map glst. cd "/tmp/tmp.DfcgjemfCG/.local/state"
+
diff --git a/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs/test.sh b/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs/test.sh
new file mode 100755
index 00000000..d3df848c
--- /dev/null
+++ b/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs/test.sh
@@ -0,0 +1,49 @@
+#! /usr/bin/env sh
+
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+mkFile() {
+ t="$1"
+
+ mkdir --parents "$(dirname "$t")"
+ echo "TEST_FILE" >"$t"
+}
+
+# We need to hard code this, so that our output matches the golden sample.
+base="/tmp/tmp.DfcgjemfCG"
+test="$(mktemp --directory -t lf_make_temp_test_XXXXX)"
+
+if [ -d "$base" ]; then
+ rm --recursive "$base"
+fi
+mkdir "$base"
+
+cleanup() {
+ rm --recursive "$base" "$test"
+}
+trap cleanup EXIT
+
+mkFile "$base/.local/share/file1.txt"
+mkFile "$base/.local/state/file1.txt"
+mkFile "$base/.local/nvim/log.LOG"
+mkFile "$base/.local/.tog/TOG.LOG"
+
+mkFile "$base/.config/apzu/file2.txt"
+mkFile "$base/.config/tiamat/file2.txt"
+mkFile "$base/.config/lahmu/file2.txt"
+
+mkFile "$base/documents/apzu/file2.txt"
+mkFile "$base/documents/tiamat/file2.txt"
+mkFile "$base/documents/lahmu/file2.txt"
+
+execute_make_maps --home-name "$base" --depth 100 generate "$base/.local" "$base/.config" "$base/documents" >"$test/output.new"
+
+diff "$test/output.new" "$(dirname "$LOCATION")/output.old"
diff --git a/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs_duplicates/output.old b/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs_duplicates/output.old
new file mode 100644
index 00000000..f7523ba3
--- /dev/null
+++ b/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs_duplicates/output.old
@@ -0,0 +1,17 @@
+map gca. cd "/tmp/tmp.DfcgjemfCG/cats"
+map gcaa. cd "/tmp/tmp.DfcgjemfCG/cats/apzu"
+map gcal. cd "/tmp/tmp.DfcgjemfCG/cats/lahmu"
+map gcat. cd "/tmp/tmp.DfcgjemfCG/cats/tiamat"
+map gco. cd "/tmp/tmp.DfcgjemfCG/.config"
+map gcoa. cd "/tmp/tmp.DfcgjemfCG/.config/apzu"
+map gcol. cd "/tmp/tmp.DfcgjemfCG/.config/lahmu"
+map gcot. cd "/tmp/tmp.DfcgjemfCG/.config/tiamat"
+map gd. cd "/tmp/tmp.DfcgjemfCG/documents"
+map gda. cd "/tmp/tmp.DfcgjemfCG/documents/apzu"
+map gdl. cd "/tmp/tmp.DfcgjemfCG/documents/lahmu"
+map gdt. cd "/tmp/tmp.DfcgjemfCG/documents/tiamat"
+map gl. cd "/tmp/tmp.DfcgjemfCG/.local"
+map gln. cd "/tmp/tmp.DfcgjemfCG/.local/nvim"
+map glsh. cd "/tmp/tmp.DfcgjemfCG/.local/share"
+map glst. cd "/tmp/tmp.DfcgjemfCG/.local/state"
+
diff --git a/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs_duplicates/test.sh b/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs_duplicates/test.sh
new file mode 100755
index 00000000..33447e54
--- /dev/null
+++ b/pkgs/by-name/lf/lf-make-map/tests/cases/dot_dirs_duplicates/test.sh
@@ -0,0 +1,53 @@
+#! /usr/bin/env sh
+
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+mkFile() {
+ t="$1"
+
+ mkdir --parents "$(dirname "$t")"
+ echo "TEST_FILE" >"$t"
+}
+
+# We need to hard code this, so that our output matches the golden sample.
+base="/tmp/tmp.DfcgjemfCG"
+test="$(mktemp --directory -t lf_make_temp_test_XXXXX)"
+
+if [ -d "$base" ]; then
+ rm --recursive "$base"
+fi
+mkdir "$base"
+
+cleanup() {
+ rm --recursive "$base" "$test"
+}
+trap cleanup EXIT
+
+mkFile "$base/.local/share/file1.txt"
+mkFile "$base/.local/state/file1.txt"
+mkFile "$base/.local/nvim/log.LOG"
+mkFile "$base/.local/.tog/TOG.LOG"
+
+mkFile "$base/.config/apzu/file2.txt"
+mkFile "$base/.config/tiamat/file2.txt"
+mkFile "$base/.config/lahmu/file2.txt"
+
+mkFile "$base/documents/apzu/file2.txt"
+mkFile "$base/documents/tiamat/file2.txt"
+mkFile "$base/documents/lahmu/file2.txt"
+
+mkFile "$base/cats/apzu/file2.txt"
+mkFile "$base/cats/tiamat/file2.txt"
+mkFile "$base/cats/lahmu/file2.txt"
+
+execute_make_maps --home-name "$base" --depth 100 generate "$base/.local" "$base/.config" "$base/documents" "$base/cats" >"$test/output.new"
+
+diff "$test/output.new" "$(dirname "$LOCATION")/output.old"
diff --git a/pkgs/by-name/lf/lf-make-map/tests/cases/simple/test.sh b/pkgs/by-name/lf/lf-make-map/tests/cases/simple/test.sh
index 22f97009..97ee0cb9 100755
--- a/pkgs/by-name/lf/lf-make-map/tests/cases/simple/test.sh
+++ b/pkgs/by-name/lf/lf-make-map/tests/cases/simple/test.sh
@@ -14,11 +14,9 @@
base="/tmp/tmp.DfcgjemfCG"
test="$(mktemp --directory -t lf_make_temp_test_XXXXX)"
-[ -d "$base" ] && {
- echo "$base already exists!"
- exit 1
-}
-
+if [ -d "$base" ]; then
+ rm --recursive "$base"
+fi
mkdir "$base"
cleanup() {
diff --git a/pkgs/by-name/lf/lf-make-map/update.sh b/pkgs/by-name/lf/lf-make-map/update.sh
index 23d90a86..188771c4 100755
--- a/pkgs/by-name/lf/lf-make-map/update.sh
+++ b/pkgs/by-name/lf/lf-make-map/update.sh
@@ -10,5 +10,5 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-[ "$1" = "upgrade" ] && cargo upgrade
-cargo update
+[ "$1" = "upgrade" ] && cargo upgrade --incompatible allow --pinned allow --recursive true
+cargo update --recursive
diff --git a/pkgs/by-name/mp/mpdpopm/.envrc b/pkgs/by-name/mp/mpdpopm/.envrc
new file mode 100644
index 00000000..9f477e71
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/.envrc
@@ -0,0 +1,22 @@
+#!/usr/bin/env sh
+
+# 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>.
+
+use flake || use nix
+watch_file flake.nix
+
+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/.gitignore b/pkgs/by-name/mp/mpdpopm/.gitignore
new file mode 100644
index 00000000..c80d7eef
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/.gitignore
@@ -0,0 +1,16 @@
+# 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>.
+
+# build
+/target
+/result
+
+# dev env
+.direnv
diff --git a/pkgs/by-name/mp/mpdpopm/Cargo.lock b/pkgs/by-name/mp/mpdpopm/Cargo.lock
new file mode 100644
index 00000000..f022bb9f
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/Cargo.lock
@@ -0,0 +1,2501 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891"
+dependencies = [
+ "term",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[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.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 = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bit-set"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
+dependencies = [
+ "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.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 = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "boolinator"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9"
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+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.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "colorchoice"
+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 = "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"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+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"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[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.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[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"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[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.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
+
+[[package]]
+name = "ena"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[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.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
+
+[[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"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "futures"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "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"
+version = "0.5.0"
+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.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "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.3",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "js-sys"
+version = "0.3.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
+dependencies = [
+ "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.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
+dependencies = [
+ "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.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98a80a963123205c7157323c99611bc4abb65dcbd62ef46dc4bac74a3941bc75"
+dependencies = [
+ "ascii-canvas",
+ "bit-set 0.9.1",
+ "ena",
+ "itertools",
+ "lalrpop-util",
+ "petgraph",
+ "pico-args",
+ "regex",
+ "regex-syntax",
+ "sha3",
+ "string_cache",
+ "term",
+ "unicode-xid",
+ "walkdir",
+]
+
+[[package]]
+name = "lalrpop-util"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "884f3e747ed2dcee867cda1b0c31a048f9e20de2d916a248949319921a2e666e"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "libc"
+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 = "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"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+
+[[package]]
+name = "lru"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d2f2f9b4ba7e6b24d95e7e899329d35be83bcded72c8540cdd5368932d1d90a"
+dependencies = [
+ "hashbrown 0.17.1",
+]
+
+[[package]]
+name = "mac_address"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0aeb26bf5e836cc1c341c8106051b573f1766dfa05aa87f0b98be5e51b02303"
+dependencies = [
+ "nix",
+ "winapi",
+]
+
+[[package]]
+name = "matchers"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
+dependencies = [
+ "regex-automata",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "mpdpopm"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "boolinator",
+ "chrono",
+ "clap",
+ "errno",
+ "futures",
+ "lalrpop",
+ "lalrpop-util",
+ "lazy_static",
+ "os_str_bytes",
+ "pin-project",
+ "rand 0.10.2",
+ "ratatui",
+ "regex",
+ "serde",
+ "serde_json",
+ "shlex",
+ "tokio",
+ "toml",
+ "tracing",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.6"
+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",
+]
+
+[[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]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[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.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "once_cell_polyfill"
+version = "1.70.2"
+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.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "pest"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf"
+dependencies = [
+ "memchr",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496"
+dependencies = [
+ "pest",
+]
+
+[[package]]
+name = "petgraph"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
+dependencies = [
+ "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.7",
+]
+
+[[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"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
+dependencies = [
+ "siphasher",
+]
+
+[[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"
+checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
+
+[[package]]
+name = "pin-project"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+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 = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
+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 2.13.1",
+]
+
+[[package]]
+name = "regex"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+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 = "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.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 = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+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.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
+dependencies = [
+ "digest",
+ "keccak",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "siphasher"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+
+[[package]]
+name = "socket2"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
+dependencies = [
+ "libc",
+ "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.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
+dependencies = [
+ "new_debug_unreachable",
+ "parking_lot",
+ "phf_shared 0.13.1",
+ "precomputed-hash",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+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.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "term"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1"
+dependencies = [
+ "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.3",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
+name = "toml"
+version = "1.1.4+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.3+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex-automata",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "typenum"
+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 = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "unicode-ident"
+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 = "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"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
+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"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+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"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.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.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.127"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
+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",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "winnow"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "zmij"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/pkgs/by-name/mp/mpdpopm/Cargo.toml b/pkgs/by-name/mp/mpdpopm/Cargo.toml
new file mode 100644
index 00000000..8152fb40
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/Cargo.toml
@@ -0,0 +1,56 @@
+# 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>.
+
+[package]
+name = "mpdpopm"
+description = "Maintain ratings & playcounts for your mpd server"
+version = "0.1.0"
+edition = "2024"
+license = "AGPL-3.0-or-later"
+homepage = ""
+repository = "https://git.vhack.eu/bpeetz/nixos-config"
+authors = ["Benedikt Peetz", "Mechael Herstine"]
+keywords = ["mpd", "music", "daemon"]
+categories = ["multimedia", "network-programming", "database"]
+
+[build-dependencies]
+lalrpop = { version = "0.23", features = ["lexer"] }
+
+[dependencies]
+async-trait = "0.1"
+boolinator = "2.4"
+chrono = "0.4"
+clap = { version = "4.6", features = ["derive"] }
+errno = "0.3"
+futures = "0.3"
+lalrpop-util = { version = "0.23", features = ["lexer"] }
+lazy_static = "1.5"
+os_str_bytes = "7.2"
+pin-project = "1.1"
+regex = "1.13"
+serde = { version = "1.0", features = ["derive"] }
+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.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
new file mode 100644
index 00000000..b4d0db7d
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/README.md
@@ -0,0 +1,259 @@
+# Table of Contents
+
+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>
+
+# Introduction
+
+[mpdpopm](https://github.com/sp1ff/mpdpopm) provides a companion daemon to [MPD](https://www.musicpd.org/) for maintaining play counts, ratings and last-played timestamps, along with an associated CLI for talking to the companion daemon. Similar to [mpdfav](https://github.com/vincent-petithory/mpdfav), but written in Rust (which I prefer to Go), it will maintain this information in your sticker database. Along the lines of [mpdcron](https://alip.github.io/mpdcron), it will also allow you to keep that information up-to-date in your tags by invoking external (user-provided & -configured) commands.
+
+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?
+
+Once you've [installed](#installing) & [started](#getting_started) [mpdpopm](https://github.com/sp1ff/mpdpopm), its daemon (`mppopmd`) will sit in the background noting the songs you play and updating play counts & last played timestamps in your [MPD](https://www.musicpd.org/) sticker database. If you'd like to rate a song, you can send `mppopmd` a message using your favorte MPD client, or with the `mppopm` CLI that comes along with this package; `mppopmd` will note the rating, as well.
+
+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)"
+```
+
+to add all songs with a rating greater than 128 to the play queue, or
+
+```
+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– 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
+```
+
+[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:
+
+```
+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
+```
+
+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>
+
+## Crates.io
+
+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
+```
+
+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
+
+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
+```
+
+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
+```
+
+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
+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– 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"
+```
+
+Check also that the you have write access to the named file & its parent directory.
+
+<a id="org38f4b69"></a>
+
+### mppopmd
+
+The daemon depends on a configuration file that you'll need to provide. Most `mppopmd` configuration items have sensible defaults, but there are a few that will need to be customized to your MPD setup. A sample configuration file is provided with all distributions; see also the user [manual](https://www.unwoundstack.com/doc/mpdpopm/curr#mppopmd-Configuration) for detailed documentation.
+
+You'll likely want to run the program in the foreground initially for ease of trouble-shooting, but after that you'll probably want to run it as a daemon. Again see the [manual](https://www.unwoundstack.com/doc/mpdopmd/curr#mppopmd-as-a-Daemon) for detailed instructions.
+
+Once you've got the daemon running to your satisfaction, if you're on a systemd-based Linux distribution, have a look at the sample systemd unit file thanks to [tanshoku](https://github.com/tanshoku).
+
+[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 '*****'
+```
+
+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– so you can now, for instance, say:
+
+```
+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`… 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– 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/README.org b/pkgs/by-name/mp/mpdpopm/README.org
new file mode 100644
index 00000000..ebc91262
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/README.org
@@ -0,0 +1,214 @@
+#+TITLE: README
+#+AUTHOR: Michael Herstine
+#+DESCRIPTION: mpdpopm
+#+EMAIL: sp1ff@pobox.com
+#+DATE: <2025-10-19 Sun 19:17>
+#+AUTODATE: t
+
+* Introduction
+
+[[https://github.com/sp1ff/mpdpopm][mpdpopm]] provides a companion daemon to [[https://www.musicpd.org/][MPD]] for maintaining play counts, ratings and last-played timestamps, along with an associated CLI for talking to the companion daemon. Similar to [[https://github.com/vincent-petithory/mpdfav][mpdfav]], but written in Rust (which I prefer to Go), it will maintain this information in your sticker database. Along the lines of [[https://alip.github.io/mpdcron][mpdcron]], it will also allow you to keep that information up-to-date in your tags by invoking external (user-provided & -configured) commands.
+
+This README focuses on obtaining & installing [[https://github.com/sp1ff/mpdpopm][mpdpopm]]; the user manual is distributed with the package in [[https://www.gnu.org/software/texinfo/][Texinfo]] format. The HTML version of the user manual is hosted on my personal [[https://www.unwoundstack.com/doc/mpdpopm/curr][site]].
+
+* What Can You Do With It?
+
+Once you've [[#installing][installed]] & [[#getting_started][started]] [[https://github.com/sp1ff/mpdpopm][mpdpopm]], its daemon (=mppopmd=) will sit in the background noting the songs you play and updating play counts & last played timestamps in your [[https://www.musicpd.org/][MPD]] sticker database. If you'd like to rate a song, you can send =mppopmd= a message using your favorte MPD client, or with the =mppopm= CLI that comes along with this package; =mppopmd= will note the rating, as well.
+
+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:
+
+#+BEGIN_SRC bash
+mppopm findadd "(rating > 128)"
+#+END_SRC
+
+to add all songs with a rating greater than 128 to the play queue, or
+
+#+BEGIN_SRC bash
+mppopm findadd "(lastplayed <= \"2022-12-28\")"
+#+END_SRC
+
+to add all songs that haven't been played in the last year.
+
+* Licsense
+
+[[https://github.com/sp1ff/mpdpopm][mpdpopm]] is GPL v3 software.
+
+* Prerequisites
+
+[[https://www.musicpd.org/][Music Player Daemon]]: "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 [[https://github.com/sp1ff/mpdpopm/releases][releases]]), that's all you'll need-- you can jump ahead to the section entitled [[#getting_started][Installing]], below.
+
+If you would prefer to download [[https://github.com/sp1ff/mpdpopm][mpdpopm]] from [[https://crates.io/crates/mpdpopm][crates.io]], you'll need need the [[https://www.rust-lang.org/tools/install][Rust]] toolchain ("Rust is a memory- & thread-safe language with no runtime or garbage collector"). Installing the toolchain is easy:
+
+#+BEGIN_SRC bash
+curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
+#+END_SRC
+
+[[https://github.com/sp1ff/mpdpopm][mpdpopm]] is also available as an Autotools source distribution (also under [[https://github.com/sp1ff/mpdpopm/releases][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 [[https://www.gnu.org/software/automake/manual/html_node/Autotools-Introduction.html][Autotools]] 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.
+
+* Installing
+ :PROPERTIES:
+ :CUSTOM_ID: installing
+ :END:
+
+As mentioned above, you can install [[https://github.com/sp1ff/mpdpopm][mpdpopm]] in a few different ways. In increasing order of complexity:
+
+** Use the pre-built binaries
+
+Thanks to a suggestion by [[https://github.com/m040601][m040601]], you can download pre-built binaries for each [[https://github.com/sp1ff/mpdpopm/releases][release]]. At the time of this writing, only Linux & MacOS are supported, and only on x86_64 at that. If that works for you, you can do something like:
+
+#+BEGIN_SRC bash
+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
+#+END_SRC
+
+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.
+
+** Crates.io
+
+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.
+
+** Use the Debian package
+
+If you're running on a Debian-based Linux distribution, and you're on an x86_64 processor, I've begun providing a Debian binary package, courtesy of the very cool [[https://github.com/mmstick/cargo-deb][cargo-deb]] Cargo helper command. Just do:
+
+#+BEGIN_SRC bash
+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
+#+END_SRC
+
+The binaries will be placed in =/usr/local/bin=, and you can proceed to [[#getting_started][Getting Started]], below.
+
+** Use the Arch package
+
+If you're running on an Arch-based Linux distribution, I maintain a few packages in the [[https://aur.archlinux.org/][AUR]]:
+
+ - [[https://aur.archlinux.org/packages/mpdpopm][mpdpopm]]: which will grab the latest release & build it locally
+ - [[https://aur.archlinux.org/packages/mpdpopm-git][mpdpopm-git]]: grab =HEAD= from =master= & build it locally
+ - [[https://aur.archlinux.org/packages/mpdpopm-bin][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.)
+** Autotools source distributions
+
+If you've got the Rust toolchain as well as Autotools installed, you can build from source via Autotools:
+
+#+BEGIN_SRC bash
+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
+#+END_SRC
+
+All the usual =configure= options apply (=--prefix=, e.g.) In particular, you can say =--enable-debug= to produce debug builds.
+
+** Building from source
+
+Finally, and again if you have the build toolchain (Rust & Autotools) installed, you can build from source:
+
+#+BEGIN_SRC bash
+git clone git@github.com:sp1ff/mpdpopm.git
+cd mpdpopm
+./bootstrap
+./configure
+make
+make check
+sudo make install
+#+END_SRC
+
+Notice the call to =./bootstrap=, in this case.
+
+* Getting Started
+ :PROPERTIES:
+ :CUSTOM_ID: getting_started
+ :END:
+
+This README provides a "quick-start" guide to getting mpdpopm up & running. For detailed user docs, refer to the [[https://www.unwoundstack.com/doc/mpdpopm/curr][manual]].
+
+** Program Structure
+
+[[https://github.com/sp1ff/mpdpopm][mpdpopm]] provides two programs:
+
+ 1. =mppopmd= is the companion daemon process
+ 2. =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= [[https://www.musicpd.org/doc/html/protocol.html#stickers][stickers]]. A sticker is a little bit of textual information which clients can attach to songs in the form of a name-value pair. [[https://github.com/sp1ff/mpdpopm][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-- 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.
+
+** Getting Set-up
+
+*** 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 [[https://github.com/m040601][m040601]], however: as mentioned above, [[https://github.com/sp1ff/mpdpopm][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 [[https://github.com/sp1ff/mpdpopm][mpdpopm]], find your =mpd= configuration file and check to be sure you have a =sticker_file= entry; something like this:
+
+#+BEGIN_EXAMPLE
+ sticker_file "/home/sp1ff/lib/mpd/sticker.sql"
+#+END_EXAMPLE
+
+Check also that the you have write access to the named file & its parent directory.
+
+*** mppopmd
+
+The daemon depends on a configuration file that you'll need to provide. Most =mppopmd= configuration items have sensible defaults, but there are a few that will need to be customized to your MPD setup. A sample configuration file is provided with all distributions; see also the user [[https://www.unwoundstack.com/doc/mpdpopm/curr#mppopmd-Configuration][manual]] for detailed documentation.
+
+You'll likely want to run the program in the foreground initially for ease of trouble-shooting, but after that you'll probably want to run it as a daemon. Again see the [[https://www.unwoundstack.com/doc/mpdopmd/curr#mppopmd-as-a-Daemon][manual]] for detailed instructions.
+
+Once you've got the daemon running to your satisfaction, if you're on a systemd-based Linux distribution, have a look at the sample systemd unit file thanks to [[https://github.com/tanshoku][tanshoku]].
+
+[[https://github.com/tanshoku][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.
+
+*** mppopm
+
+At this point, [[https://github.com/sp1ff/mpdpopm][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 [[https://github.com/sp1ff/mpdpopm][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
+
+#+BEGIN_SRC bash
+mppopm set-rating '*****'
+#+END_SRC
+
+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.
+
+* Status & Roadmap
+
+I am currently using [[https://github.com/sp1ff/mpdpopm][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 _do_ 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 [[https://github.com/sp1ff/mpdpopm][mpdpopm]] manages-- so you can now, for instance, say:
+
+#+BEGIN_EXAMPLE
+mppopm findadd "(artist =~ \"foo\") and (rating > 175)"
+#+END_EXAMPLE
+
+MPD will handle the "artist =~" clause & [[https://github.com/sp1ff/mpdpopm][mpdpopm]] the "rating >" clause, as well as combining the results.
+
+This will hopefully be a start to making [[https://github.com/sp1ff/mpdpopm][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=... 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 [[https://github.com/sp1ff/mpdpopm][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
new file mode 100644
index 00000000..04586f29
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/build.rs
@@ -0,0 +1,15 @@
+extern crate lalrpop;
+fn main() {
+ let out_dir = std::env::var("OUT_DIR").unwrap();
+
+ lalrpop::Configuration::new()
+ .emit_comments(true)
+ .emit_whitespace(true)
+ .log_verbose()
+ .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
new file mode 100644
index 00000000..d3471a05
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/config.lsp
@@ -0,0 +1,11 @@
+{
+ "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.nix b/pkgs/by-name/mp/mpdpopm/flake.nix
new file mode 100644
index 00000000..d2e37260
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/flake.nix
@@ -0,0 +1,56 @@
+# 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 = {
+ };
+
+ outputs = {self, ...}: let
+ system = "x86_64-linux";
+ sources = import ../../../../unflake.nix;
+
+ pkgs = sources.nixpkgs.legacyPackages."${system}";
+ inherit (sources) treefmt-nix;
+
+ 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
new file mode 100644
index 00000000..7048f41e
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/package.nix
@@ -0,0 +1,29 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ rustPlatform,
+ lib,
+}:
+rustPlatform.buildRustPackage (finalAttrs: {
+ pname = "mpdpopm";
+ version = "0.1.0";
+
+ buildInputs = [];
+ nativeBuildInputs = [];
+
+ src = ./.;
+ cargoLock = {
+ lockFile = ./Cargo.lock;
+ };
+
+ meta = {
+ mainProgram = "mpdpopm";
+ };
+})
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/main.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs
new file mode 100644
index 00000000..42f01873
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopm/main.rs
@@ -0,0 +1,591 @@
+// 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/>.
+
+//! # mppopm
+//!
+//! mppopmd client
+//!
+//! # Introduction
+//!
+//! `mppopmd` is a companion daemon for [mpd](https://www.musicpd.org/) that maintains play counts &
+//! ratings. Similar to [mpdfav](https://github.com/vincent-petithory/mpdfav), but written in Rust
+//! (which I prefer to Go), it will allow you to maintain that information in your tags, as well as
+//! the sticker database, by invoking external commands to keep your tags up-to-date (something
+//! 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},
+ config::{self, Config},
+ 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 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 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
+///
+/// Several sub-commands take zero or more positional arguments meant to name tracks, with the
+/// convention that zero indicates that the sub-command should use the currently playing track.
+/// This is a convenience function for mapping the value returned by [`get_many`] to a
+/// convenient representation of the user's intentions.
+///
+/// [`get_many`]: [`clap::ArgMatches::get_many`]
+async fn map_tracks(client: &mut Client, args: Option<Vec<String>>) -> Result<Vec<String>> {
+ let files = match args {
+ Some(iter) => iter,
+ None => {
+ let file = provide_file(client, None).await?;
+ vec![file]
+ }
+ };
+ Ok(files)
+}
+
+async fn provide_file(client: &mut Client, maybe_file: Option<String>) -> Result<String> {
+ let file = match maybe_file {
+ Some(file) => file,
+ None => {
+ match client
+ .status()
+ .await
+ .context("Failed to get status of client")?
+ {
+ PlayerStatus::Play(curr) | PlayerStatus::Pause(curr) => curr
+ .file
+ .to_str()
+ .ok_or_else(|| anyhow!("Path is not utf8: `{}`", curr.file.display()))?
+ .to_string(),
+ PlayerStatus::Stopped => {
+ bail!("Player is stopped");
+ }
+ }
+ }
+ };
+
+ Ok(file)
+}
+
+/// Retrieve ratings for one or more tracks
+async fn get_ratings(
+ client: &mut Client,
+ tracks: Option<Vec<String>>,
+ with_uri: bool,
+) -> Result<()> {
+ let mut ratings: Vec<(String, i8)> = Vec::new();
+
+ for file in map_tracks(client, tracks).await? {
+ let rating = rating::get(client, &file).await?;
+
+ ratings.push((file, rating.unwrap_or_default()));
+ }
+
+ if ratings.len() == 1 && !with_uri {
+ println!("{}", ratings[0].1);
+ } else {
+ for pair in ratings {
+ println!("{}: {}", pair.0, pair.1);
+ }
+ }
+
+ Ok(())
+}
+
+/// Rate a track
+async fn set_rating(client: &mut Client, rating: i8, arg: Option<String>) -> Result<()> {
+ let is_current = arg.is_none();
+ let file = provide_file(client, arg).await?;
+
+ rating::set(client, &file, rating).await?;
+
+ match is_current {
+ false => info!("Set the rating for \"{}\" to \"{}\".", file, rating),
+ true => info!("Set the rating for the current song to \"{}\".", rating),
+ }
+
+ Ok(())
+}
+
+/// Rate a track by incrementing the current rating
+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::get(client, &file).await?;
+
+ rating::set(client, &file, now.unwrap_or_default().saturating_add(1)).await?;
+
+ match is_current {
+ false => info!("Incremented the rating for \"{}\".", file),
+ true => info!("Incremented the rating for the current song."),
+ }
+
+ Ok(())
+}
+
+/// Rate a track by decrementing the current rating
+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::get(client, &file).await?;
+
+ rating::set(client, &file, now.unwrap_or_default().saturating_sub(1)).await?;
+
+ match is_current {
+ false => info!("Decremented the rating for \"{}\".", file),
+ true => info!("Decremented the rating for the current song."),
+ }
+
+ Ok(())
+}
+
+/// Retrieve the playcount for one or more tracks
+async fn get_play_counts(
+ client: &mut Client,
+ tracks: Option<Vec<String>>,
+ with_uri: bool,
+) -> Result<()> {
+ let mut playcounts: Vec<(String, usize)> = Vec::new();
+ for file in map_tracks(client, tracks).await? {
+ let playcount = play_count::get(client, &file).await?.unwrap_or_default();
+ playcounts.push((file, playcount));
+ }
+
+ if playcounts.len() == 1 && !with_uri {
+ println!("{}", playcounts[0].1);
+ } else {
+ for pair in playcounts {
+ println!("{}: {}", pair.0, pair.1);
+ }
+ }
+
+ Ok(())
+}
+
+/// Set the playcount for a track
+async fn set_play_counts(client: &mut Client, playcount: usize, arg: Option<String>) -> Result<()> {
+ let is_current = arg.is_none();
+ let file = provide_file(client, arg).await?;
+
+ play_count::set(client, &file, playcount).await?;
+
+ match is_current {
+ false => info!("Set the playcount for \"{}\" to \"{}\".", file, playcount),
+ true => info!(
+ "Set the playcount for the current song to \"{}\".",
+ playcount
+ ),
+ }
+
+ Ok(())
+}
+
+/// Retrieve the last played time for one or more tracks
+async fn get_last_playeds(
+ client: &mut Client,
+ tracks: Option<Vec<String>>,
+ with_uri: bool,
+) -> Result<()> {
+ let mut lastplayeds: Vec<(String, Option<u64>)> = Vec::new();
+ for file in map_tracks(client, tracks).await? {
+ let lastplayed = last_played::get(client, &file).await?;
+ lastplayeds.push((file, lastplayed));
+ }
+
+ if lastplayeds.len() == 1 && !with_uri {
+ println!(
+ "{}",
+ match lastplayeds[0].1 {
+ Some(t) => format!("{}", t),
+ None => String::from("N/A"),
+ }
+ );
+ } else {
+ for pair in lastplayeds {
+ println!(
+ "{}: {}",
+ pair.0,
+ match pair.1 {
+ Some(t) => format!("{}", t),
+ None => String::from("N/A"),
+ }
+ );
+ }
+ }
+
+ Ok(())
+}
+
+/// Set the playcount for a track
+async fn set_last_playeds(client: &mut Client, lastplayed: u64, arg: Option<String>) -> Result<()> {
+ let is_current = arg.is_none();
+ let file = provide_file(client, arg).await?;
+
+ last_played::set(client, &file, lastplayed).await?;
+
+ match is_current {
+ false => info!("Set last played for \"{}\" to \"{}\".", file, lastplayed),
+ true => info!(
+ "Set last played for the current song to \"{}\".",
+ lastplayed
+ ),
+ }
+
+ Ok(())
+}
+
+/// Retrieve the list of stored playlists
+async fn get_playlists(client: &mut Client) -> Result<()> {
+ let mut pls = client.get_stored_playlists().await?;
+ pls.sort();
+ println!("Stored playlists:");
+ for pl in pls {
+ println!("{}", pl);
+ }
+ Ok(())
+}
+
+/// Add songs selected by filter to the queue
+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)
+ }
+ };
+
+ debug!("ast: {:#?}", ast);
+
+ 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;
+
+ if out.is_ok() {
+ eprintln!("Added: `{}`", song)
+ }
+
+ results.push(out);
+ }
+
+ match results.into_iter().collect::<Result<Vec<()>>>() {
+ Ok(_) => Ok(()),
+ Err(err) => Err(err),
+ }
+}
+
+#[tokio::main]
+async fn main() -> Result<()> {
+ let args = Args::parse();
+
+ let config = if let Some(configpath) = &args.config {
+ match std::fs::read_to_string(configpath) {
+ Ok(text) => config::from_str(&text).with_context(|| {
+ format!("Failed to parse config file at: `{}`", configpath.display())
+ })?,
+ Err(err) => {
+ // Either they did _not_, in which case they probably want to know that the config
+ // file they explicitly asked for does not exist, or there was some other problem,
+ // in which case we're out of options, anyway. Either way:
+ bail!(
+ "Failed to read config file at: `{}`, because: {err}",
+ configpath.display()
+ )
+ }
+ }
+ } else {
+ Config::default()
+ };
+
+ // Handle log verbosity: debug => verbose
+ let lf = match (args.verbose, args.debug) {
+ (_, true) => LevelFilter::TRACE,
+ (true, false) => LevelFilter::DEBUG,
+ _ => LevelFilter::WARN,
+ };
+
+ tracing::subscriber::set_global_default(
+ Registry::default()
+ .with(
+ tracing_subscriber::fmt::Layer::default()
+ .compact()
+ .with_writer(std::io::stdout),
+ )
+ .with(
+ EnvFilter::builder()
+ .with_default_directive(lf.into())
+ .from_env()
+ .unwrap(),
+ ),
+ )
+ .unwrap();
+
+ trace!("logging configured.");
+
+ let mut client = match config.conn {
+ config::Connection::Local { path } => Client::open(path).await?,
+ config::Connection::TCP { host, port } => {
+ Client::connect(format!("{}:{}", host, port)).await?
+ }
+ };
+
+ match args.command {
+ SubCommand::Rating { command } => match command {
+ RatingCommand::Get { with_uri, tracks } => {
+ get_ratings(&mut client, tracks, with_uri).await
+ }
+ RatingCommand::Set { rating, track } => set_rating(&mut client, rating, track).await,
+ RatingCommand::Inc { track } => inc_rating(&mut client, track).await,
+ RatingCommand::Decr { track } => decr_rating(&mut client, track).await,
+ },
+ SubCommand::PlayCount { command } => match command {
+ PlayCountCommand::Get { with_uri, tracks } => {
+ get_play_counts(&mut client, tracks, with_uri).await
+ }
+ PlayCountCommand::Set { play_count, track } => {
+ set_play_counts(&mut client, play_count, track).await
+ }
+ },
+ SubCommand::LastPlayed { command } => match command {
+ LastPlayedCommand::Get { with_uri, tracks } => {
+ get_last_playeds(&mut client, tracks, with_uri).await
+ }
+ LastPlayedCommand::Set { last_played, track } => {
+ set_last_playeds(&mut client, last_played, track).await
+ }
+ },
+ SubCommand::Playlists { command } => match command {
+ PlaylistsCommand::Get {} => get_playlists(&mut client).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/bin/mpdpopmd.rs b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs
new file mode 100644
index 00000000..643611d6
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/bin/mpdpopmd.rs
@@ -0,0 +1,150 @@
+// 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/>.
+
+//! # mppopmd
+//!
+//! Maintain ratings & playcounts for your mpd server.
+//!
+//! # Introduction
+//!
+//! This is a companion daemon for [mpd](https://www.musicpd.org/) that maintains play counts &
+//! ratings. Similar to [mpdfav](https://github.com/vincent-petithory/mpdfav), but written in Rust
+//! (which I prefer to Go), it will allow you to maintain that information in your tags, as well as
+//! the sticker database, by invoking external commands to keep your tags up-to-date (something
+//! along the lines of [mpdcron](https://alip.github.io/mpdcron)).
+
+use mpdpopm::{
+ config::{self, Config},
+ mpdpopm,
+};
+
+use anyhow::{Context, Result, bail};
+use clap::Parser;
+use tracing::{info, level_filters::LevelFilter};
+use tracing_subscriber::{EnvFilter, Layer, Registry, layer::SubscriberExt};
+
+use std::{io, path::PathBuf, sync::MutexGuard};
+
+pub struct MyMutexGuardWriter<'a>(MutexGuard<'a, std::fs::File>);
+
+impl io::Write for MyMutexGuardWriter<'_> {
+ #[inline]
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.0.write(buf)
+ }
+
+ #[inline]
+ fn flush(&mut self) -> io::Result<()> {
+ self.0.flush()
+ }
+
+ #[inline]
+ fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> io::Result<usize> {
+ self.0.write_vectored(bufs)
+ }
+
+ #[inline]
+ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+ self.0.write_all(buf)
+ }
+
+ #[inline]
+ fn write_fmt(&mut self, fmt: std::fmt::Arguments<'_>) -> io::Result<()> {
+ self.0.write_fmt(fmt)
+ }
+}
+
+/// mpd + POPM
+///
+/// `mppopmd' is a companion daemon for `mpd' that maintains playcounts & ratings,
+/// as well as implementing some handy functions. It maintains ratings & playcounts in the sticker
+/// database, but it allows you to keep that information in your tags, as well, by invoking external
+/// commands to keep your tags up-to-date.
+#[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,
+}
+
+/// Entry point for `mpdopmd'.
+///
+/// Do *not* use the #[tokio::main] attribute here! If this program is asked to daemonize (the usual
+/// case), we will fork after tokio has started its thread pool, with disastrous consequences.
+/// Instead, stay synchronous until we've daemonized (or figured out that we don't need to), and
+/// only then fire-up the tokio runtime.
+fn main() -> Result<()> {
+ use mpdpopm::vars::VERSION;
+
+ let args = Args::parse();
+
+ let config = if let Some(cfgpath) = &args.config {
+ match std::fs::read_to_string(cfgpath) {
+ Ok(text) => config::from_str(&text).with_context(|| {
+ format!("Failed to parse config file at: `{}`", cfgpath.display())
+ })?,
+ // The config file (defaulted or not) either didn't exist, or we were unable to read its
+ // contents...
+ Err(err) => {
+ // Either they did _not_, in which case they probably want to know that the config
+ // file they explicitly asked for does not exist, or there was some other problem,
+ // in which case we're out of options, anyway. Either way:
+ bail!(
+ "No config file could be read at: `{}`, because: {err}",
+ cfgpath.display()
+ )
+ }
+ }
+ } else {
+ Config::default()
+ };
+
+ // `--verbose' & `--debug' work as follows: if `--debug' is present, log at level Trace, no
+ // matter what. Else, if `--verbose' is present, log at level Debug. Else, log at level Info.
+ let lf = match (args.verbose, args.debug) {
+ (_, true) => LevelFilter::TRACE,
+ (true, false) => LevelFilter::DEBUG,
+ _ => LevelFilter::INFO,
+ };
+
+ let filter = EnvFilter::builder()
+ .with_default_directive(lf.into())
+ .from_env()
+ .context("Failed to construct env filter")?;
+
+ let formatter: Box<dyn Layer<Registry> + Send + Sync> = {
+ Box::new(
+ tracing_subscriber::fmt::Layer::default()
+ .compact()
+ .with_writer(io::stdout),
+ )
+ };
+
+ tracing::subscriber::set_global_default(Registry::default().with(formatter).with(filter))
+ .unwrap();
+
+ info!("mppopmd {VERSION} logging at level {lf:#?}.");
+ let rt = tokio::runtime::Runtime::new().unwrap();
+
+ rt.block_on(mpdpopm(config)).context("Main mpdpopm failed")
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/clients.rs b/pkgs/by-name/mp/mpdpopm/src/clients.rs
new file mode 100644
index 00000000..b934714a
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/clients.rs
@@ -0,0 +1,1200 @@
+// 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/>.
+
+//! mpd clients and associated utilities.
+//!
+//! # Introduction
+//!
+//! This module contains basic types implementing various MPD client operations (cf. the [mpd
+//! protocol](http://www.musicpd.org/doc/protocol/)). Since issuing the "idle" command will tie up
+//! the connection, MPD clients often use multiple connections to the server (one to listen for
+//! updates, one or more on which to issue commands). This modules provides two different client
+//! types: [Client] for general-purpose use and [IdleClient] for long-lived connections listening
+//! for server notifiations.
+//!
+//! Note that there *is* another idiom (used in [libmpdel](https://github.com/mpdel/libmpdel),
+//! e.g.): open a single connection & issue an "idle" command. When you want to issue a command,
+//! send a "noidle", then the command, then "idle" again. This isn't a race condition, as the
+//! server will buffer any changes that took place when you were not idle & send them when you
+//! re-issue the "idle" command. This crate however takes the approach of two channels (like
+//! [mpdfav](https://github.com/vincent-petithory/mpdfav)).
+
+use anyhow::{Context, Error, Result, anyhow, bail, ensure};
+use async_trait::async_trait;
+use regex::Regex;
+use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
+use tokio::net::{TcpStream, ToSocketAddrs, UnixStream};
+use tracing::{debug, info};
+
+use lazy_static::lazy_static;
+
+use std::{
+ collections::HashMap,
+ convert::TryFrom,
+ fmt,
+ marker::{Send, Unpin},
+ path::{Path, PathBuf},
+ str::FromStr,
+};
+
+// Some default error context messages
+const ENCODING_SNAFU: &str = "Failed to interpete text as utf8";
+const IO_SNAFU: &str = "Failed read from mpd socket";
+
+/// A description of the current track, suitable for our purposes (as in, it only tracks the
+/// attributes needed for this module's functionality).
+#[derive(Clone, Debug)]
+pub struct CurrentSong {
+ /// Identifier, unique within the play queue, identifying this particular track; if the same
+ /// file is listed twice in the `mpd' play queue each instance will get a distinct songid
+ pub songid: u64,
+
+ /// Path, relative to `mpd' music directory root of this track
+ pub file: std::path::PathBuf,
+
+ /// Elapsed time, in seconds, in this track
+ pub elapsed: f64,
+
+ /// Total track duration, in seconds
+ pub duration: f64,
+}
+
+impl CurrentSong {
+ fn new(songid: u64, file: std::path::PathBuf, elapsed: f64, duration: f64) -> CurrentSong {
+ CurrentSong {
+ songid,
+ file,
+ elapsed,
+ duration,
+ }
+ }
+ /// Compute the ratio of the track that has elapsed, expressed as a floating point between 0 & 1
+ pub fn played_pct(&self) -> f64 {
+ self.elapsed / self.duration
+ }
+}
+
+/// The MPD player itself can be in one of three states: playing, paused or stopped. In the first
+/// two there is a "current" song.
+#[derive(Clone, Debug)]
+pub enum PlayerStatus {
+ Play(CurrentSong),
+ Pause(CurrentSong),
+ Stopped,
+}
+
+impl PlayerStatus {
+ pub fn current_song(&self) -> Option<&CurrentSong> {
+ match self {
+ PlayerStatus::Play(curr) | PlayerStatus::Pause(curr) => Some(curr),
+ PlayerStatus::Stopped => None,
+ }
+ }
+}
+
+/// A trait representing a simple, textual request/response protocol like that
+/// [employed](https://www.musicpd.org/doc/html/protocol.html) by [MPD](https://www.musicpd.org/):
+/// the caller sends a textual command & the server responds with a (perhaps multi-line) textual
+/// response.
+///
+/// This trait also enables unit testing client implementations. Note that it is async-- cf.
+/// [async_trait](https://docs.rs/async-trait/latest/async_trait/).
+#[async_trait]
+pub trait RequestResponse {
+ async fn req(&mut self, msg: &str) -> Result<String>;
+ /// The hint is used to size the buffer prior to reading the response
+ async fn req_w_hint(&mut self, msg: &str, hint: usize) -> Result<String>;
+}
+
+#[cfg(test)]
+pub mod test_mock {
+ use super::*;
+
+ /// Mock is an implementation of [`RequestRespone`] that checks expected requests & responses,
+ /// and will panic if it sees anything unexpected
+ pub struct Mock {
+ inmsgs: Vec<String>,
+ outmsgs: Vec<String>,
+ }
+
+ impl Mock {
+ pub fn new(convo: &[(&str, &str)]) -> Mock {
+ let (left, right): (Vec<&str>, Vec<&str>) = convo.iter().copied().rev().unzip();
+ Mock {
+ inmsgs: left.iter().map(|x| x.to_string()).collect(),
+ outmsgs: right.iter().map(|x| x.to_string()).collect(),
+ }
+ }
+ }
+
+ #[async_trait]
+ impl RequestResponse for Mock {
+ async fn req(&mut self, msg: &str) -> Result<String> {
+ self.req_w_hint(msg, 512).await
+ }
+ async fn req_w_hint(&mut self, msg: &str, _hint: usize) -> Result<String> {
+ assert_eq!(msg, self.inmsgs.pop().unwrap());
+ Ok(self.outmsgs.pop().unwrap())
+ }
+ }
+
+ #[tokio::test]
+ async fn mock_smoke_test() {
+ let mut mock = Mock::new(&[("ping", "pong"), ("from", "to")]);
+ assert_eq!(mock.req("ping").await.unwrap(), "pong");
+ assert_eq!(mock.req("from").await.unwrap(), "to");
+ }
+
+ #[tokio::test]
+ #[should_panic]
+ async fn mock_negative_test() {
+ let mut mock = Mock::new(&[("ping", "pong")]);
+ assert_eq!(mock.req("ping").await.unwrap(), "pong");
+ let _should_panic = mock.req("not there!").await.unwrap();
+ }
+}
+
+/// [MPD](https://www.musicpd.org/) connections talk the same
+/// [protocol](https://www.musicpd.org/doc/html/protocol.html) over either a TCP or a Unix socket.
+///
+/// # Examples
+///
+/// Implementations are provided for tokio [UnixStream] and [TcpStream], but [MpdConnection] is a
+/// trait that can work in terms of any asynchronous communications channel (so long as it is also
+/// [Send] and [Unpin] so async executors can pass them between threads.
+///
+/// To create a connection to an `MPD` server over a Unix domain socket:
+///
+/// ```no_run
+/// use std::path::Path;
+/// use tokio::net::UnixStream;
+/// use mpdpopm::clients::MpdConnection;
+/// let local_conn = MpdConnection::<UnixStream>::connect(Path::new("/var/run/mpd/mpd.sock"));
+/// ```
+///
+/// In this example, `local_conn` is a Future that will resolve to a Result containing the
+/// [MpdConnection] Unix domain socket implementation once the socket has been established, the MPD
+/// server greets us & the protocol version has been parsed.
+///
+/// or over a TCP socket:
+///
+/// ```no_run
+/// use std::net::SocketAddrV4;
+/// use tokio::net::{TcpStream, ToSocketAddrs};
+/// use mpdpopm::clients::MpdConnection;
+/// let tcp_conn = MpdConnection::<TcpStream>::connect("localhost:6600".parse::<SocketAddrV4>().unwrap());
+/// ```
+///
+/// Here, `tcp_conn` is a Future that will resolve to a Result containing the [MpdConnection] TCP
+/// implementation on successful connection to the MPD server (i.e. the connection is established,
+/// the server greets us & we parse the protocol version).
+///
+///
+pub struct MpdConnection<T: AsyncRead + AsyncWrite + Send + Unpin> {
+ sock: T,
+ _protocol_ver: String,
+}
+
+/// MpdConnection implements RequestResponse using the usual (async) socket I/O
+///
+/// The callers need not include the trailing newline in their requests; the implementation will
+/// append it.
+#[async_trait]
+impl<T> RequestResponse for MpdConnection<T>
+where
+ T: AsyncRead + AsyncWrite + Send + Unpin,
+{
+ async fn req(&mut self, msg: &str) -> Result<String> {
+ self.req_w_hint(msg, 512).await
+ }
+ async fn req_w_hint(&mut self, msg: &str, hint: usize) -> Result<String> {
+ self.sock
+ .write_all(format!("{}\n", msg).as_bytes())
+ .await
+ .context(IO_SNAFU)?;
+ let mut buf = Vec::with_capacity(hint);
+
+ // Given the request/response nature of the MPD protocol, our callers expect a complete
+ // response. Therefore we need to loop here until we see either "...^OK\n" or
+ // "...^ACK...\n".
+ let mut cb = 0; // # bytes read so far
+ let mut more = true; // true as long as there is more to read
+ while more {
+ cb += self.sock.read_buf(&mut buf).await.context(IO_SNAFU)?;
+
+ // The shortest complete response has three bytes. If the final byte in `buf' is not a
+ // newline, then don't bother looking further.
+ if cb > 2 && char::from(buf[cb - 1]) == '\n' {
+ // If we're here, `buf' *may* contain a complete response. Search backward for the
+ // previous newline. It may not exist: many responses are of the form "OK\n".
+ let mut idx = cb - 2;
+ while idx > 0 {
+ if char::from(buf[idx]) == '\n' {
+ idx += 1;
+ break;
+ }
+ idx -= 1;
+ }
+
+ if (idx + 2 < cb && char::from(buf[idx]) == 'O' && char::from(buf[idx + 1]) == 'K')
+ || (idx + 3 < cb
+ && char::from(buf[idx]) == 'A'
+ && char::from(buf[idx + 1]) == 'C'
+ && char::from(buf[idx + 2]) == 'K')
+ {
+ more = false;
+ }
+ }
+ }
+
+ // Only doing this to trouble-shoot issue 11
+ String::from_utf8(buf.clone()).context(ENCODING_SNAFU)
+ }
+}
+
+/// Utility function to parse the initial response to a connection from mpd
+async fn parse_connect_rsp<T>(sock: &mut T) -> Result<String>
+where
+ T: AsyncReadExt + AsyncWriteExt + Send + Unpin,
+{
+ let mut buf = Vec::with_capacity(32);
+ let _cb = sock.read_buf(&mut buf).await.context(IO_SNAFU)?;
+
+ // Only doing this to trouble-shoot issue 11
+ let text = String::from_utf8(buf.clone()).context(ENCODING_SNAFU)?;
+
+ ensure!(
+ text.starts_with("OK MPD "),
+ "failed to connect: {}",
+ text.trim()
+ );
+ info!("Connected {}.", text[7..].trim());
+ Ok(text[7..].trim().to_string())
+}
+
+impl MpdConnection<TcpStream> {
+ pub async fn connect<A: ToSocketAddrs>(addr: A) -> Result<Box<dyn RequestResponse>> {
+ let mut sock = TcpStream::connect(addr).await.context(IO_SNAFU)?;
+ let proto_ver = parse_connect_rsp(&mut sock).await?;
+ Ok(Box::new(MpdConnection::<TcpStream> {
+ sock,
+ _protocol_ver: proto_ver,
+ }))
+ }
+}
+
+impl MpdConnection<UnixStream> {
+ // NTS: we have to box the return value because a `dyn RequestResponse` isn't Sized.
+ pub async fn connect<P: AsRef<Path>>(pth: P) -> Result<Box<dyn RequestResponse>> {
+ let mut sock = UnixStream::connect(pth).await.context(IO_SNAFU)?;
+ let proto_ver = parse_connect_rsp(&mut sock).await?;
+ Ok(Box::new(MpdConnection::<UnixStream> {
+ sock,
+ _protocol_ver: proto_ver,
+ }))
+ }
+}
+
+/// Quote an argument by backslash-escaping " & \ characters
+pub fn quote(text: &str) -> String {
+ if text.contains(&[' ', '\t', '\'', '"'][..]) {
+ let mut s = String::from("\"");
+ for c in text.chars() {
+ if c == '"' || c == '\\' {
+ s.push('\\');
+ }
+ s.push(c);
+ }
+ s.push('"');
+ s
+ } else {
+ text.to_string()
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Client //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/// General-purpose [mpd](https://www.musicpd.org)
+/// [client](https://www.musicpd.org/doc/html/protocol.html): "general-purpose" in the sense that we
+/// send commands through it; the interface is narrowly scoped to this program's needs.
+///
+/// # Introduction
+///
+/// This is the primary abstraction of the MPD client protocol, written for the convenience of
+/// [mpdpopm](crate). Construct instances with a TCP socket, a Unix socket, or any [RequestResponse]
+/// implementation. You can then carry out assorted operations in the MPD client protocol by
+/// invoking its methods.
+///
+/// ```no_run
+/// use std::path::Path;
+/// use mpdpopm::clients::Client;
+/// let client = Client::open(Path::new("/var/run/mpd.sock"));
+/// ```
+///
+/// `client` is now a [Future](https://doc.rust-lang.org/stable/std/future/trait.Future.html) that
+/// resolves to a [Client] instance talking to `/var/run/mpd.sock`.
+///
+/// ```no_run
+/// use mpdpopm::clients::Client;
+/// let client = Client::connect("localhost:6600");
+/// ```
+///
+/// `client` is now a [Future](https://doc.rust-lang.org/stable/std/future/trait.Future.html) that
+/// resolves to a [Client] instance talking TCP to the MPD server on localhost at port 6600.
+pub struct Client {
+ stream: Box<dyn RequestResponse>,
+}
+
+// Thanks to <https://stackoverflow.com/questions/35169259/how-to-make-a-compiled-regexp-a-global-variable>
+lazy_static! {
+ static ref RE_STATE: regex::Regex = Regex::new(r"(?m)^state: (play|pause|stop)$").unwrap();
+ static ref RE_SONGID: regex::Regex = Regex::new(r"(?m)^songid: ([0-9]+)$").unwrap();
+ static ref RE_ELAPSED: regex::Regex = Regex::new(r"(?m)^elapsed: ([.0-9]+)$").unwrap();
+ static ref RE_FILE: regex::Regex = Regex::new(r"(?m)^file: (.*)$").unwrap();
+ static ref RE_DURATION: regex::Regex = Regex::new(r"(?m)^duration: (.*)$").unwrap();
+}
+
+impl Client {
+ pub async fn connect<A: ToSocketAddrs>(addrs: A) -> Result<Client> {
+ Self::new(MpdConnection::<TcpStream>::connect(addrs).await?)
+ }
+
+ pub async fn open<P: AsRef<Path>>(pth: P) -> Result<Client> {
+ Self::new(MpdConnection::<UnixStream>::connect(pth).await?)
+ }
+
+ pub fn new(stream: Box<dyn RequestResponse>) -> Result<Client> {
+ Ok(Client { stream })
+ }
+}
+
+impl Client {
+ /// Retrieve the current server status.
+ pub async fn status(&mut self) -> Result<PlayerStatus> {
+ // We begin with sending the "status" command: "Reports the current status of the player and
+ // the volume level." Per the docs, "MPD may omit lines which have no (known) value", so I
+ // can't really count on particular lines being there. Tho nothing is said in the docs, I
+ // also don't want to depend on the order.
+ let text = self.stream.req("status").await?;
+
+ let proto = || -> Error { anyhow!("Failed to parse mpd status output (with regexes)") };
+
+ // I first thought to avoid the use (and cost) of regular expressions by just doing
+ // sub-string searching on "state: ", but when I realized I needed to only match at the
+ // beginning of a line I bailed & just went ahead. This makes for more succinct code, since
+ // I can't count on order, either.
+ let state = RE_STATE
+ .captures(&text)
+ .ok_or_else(proto)?
+ .get(1)
+ .ok_or_else(proto)?
+ .as_str();
+
+ match state {
+ "stop" => Ok(PlayerStatus::Stopped),
+ "play" | "pause" => {
+ let songid = RE_SONGID
+ .captures(&text)
+ .ok_or_else(proto)?
+ .get(1)
+ .ok_or_else(proto)?
+ .as_str()
+ .parse::<u64>()
+ .context("Failed to parse songid as u64")?;
+
+ let elapsed = RE_ELAPSED
+ .captures(&text)
+ .ok_or_else(proto)?
+ .get(1)
+ .ok_or_else(proto)?
+ .as_str()
+ .parse::<f64>()
+ .context("failed to parse `elapsed` as f64")?;
+
+ // navigate from `songid'-- don't send a "currentsong" message-- the current song
+ // could have changed
+ let text = self.stream.req(&format!("playlistid {}", songid)).await?;
+
+ let file = RE_FILE
+ .captures(&text)
+ .ok_or_else(proto)?
+ .get(1)
+ .ok_or_else(proto)?
+ .as_str();
+ let duration = RE_DURATION
+ .captures(&text)
+ .ok_or_else(proto)?
+ .get(1)
+ .ok_or_else(proto)?
+ .as_str()
+ .parse::<f64>()
+ .context("Failed to parse `duration` as f64")?;
+
+ let curr = CurrentSong::new(songid, PathBuf::from(file), elapsed, duration);
+
+ if state == "play" {
+ Ok(PlayerStatus::Play(curr))
+ } else {
+ Ok(PlayerStatus::Pause(curr))
+ }
+ }
+ _ => bail!("Encountered unknow state `{}`", state),
+ }
+ }
+
+ /// Retrieve a song sticker by name
+ pub async fn get_sticker<T: FromStr>(
+ &mut self,
+ file: &str,
+ sticker_name: &str,
+ ) -> Result<Option<T>>
+ where
+ <T as FromStr>::Err: std::error::Error + Sync + Send + 'static,
+ {
+ let msg = format!("sticker get song {} {}", quote(file), quote(sticker_name));
+ let text = self.stream.req(&msg).await?;
+ debug!("Sent message `{}'; got `{}'", &msg, &text);
+
+ let prefix = format!("sticker: {}=", sticker_name);
+ if text.starts_with(&prefix) {
+ let s = text[prefix.len()..]
+ .split('\n')
+ .next()
+ .with_context(|| format!("Failed to parse `{}` as get_sticker response", text))?;
+ Ok(Some(T::from_str(s).with_context(|| {
+ format!(
+ "Failed to parse sticker value as correct type: `{}`",
+ sticker_name
+ )
+ })?))
+ } else {
+ // ACK_ERROR_NO_EXIST = 50 (Ack.hxx:17)
+ ensure!(
+ text.starts_with("ACK [50@0]"),
+ "Missing no sticker response"
+ );
+ Ok(None)
+ }
+ }
+
+ /// Set a song sticker by name
+ pub async fn set_sticker<T: std::fmt::Display>(
+ &mut self,
+ file: &str,
+ sticker_name: &str,
+ sticker_value: &T,
+ ) -> Result<()> {
+ let value_as_str = format!("{}", sticker_value);
+ let msg = format!(
+ "sticker set song {} {} {}",
+ quote(file),
+ quote(sticker_name),
+ quote(&value_as_str)
+ );
+ let text = self.stream.req(&msg).await?;
+ debug!("Sent `{}'; got `{}'", &msg, &text);
+
+ ensure!(text.starts_with("OK"), "Set sticker, not acknowledged");
+ Ok(())
+ }
+
+ /// Send a file to a playlist
+ pub async fn send_to_playlist(&mut self, file: &str, pl: &str) -> Result<()> {
+ let msg = format!("playlistadd {} {}", quote(pl), quote(file));
+ let text = self.stream.req(&msg).await?;
+ debug!("Sent `{}'; got `{}'.", &msg, &text);
+ ensure!(text.starts_with("OK"), "send_to_playlist not acknowledged");
+ Ok(())
+ }
+
+ /// Send an arbitrary message
+ pub async fn send_message(&mut self, chan: &str, msg: &str) -> Result<()> {
+ let msg = format!("sendmessage {} {}", chan, quote(msg));
+ let text = self.stream.req(&msg).await?;
+ debug!("Sent `{}'; got `{}'.", &msg, &text);
+
+ ensure!(text.starts_with("OK"), "Send_message not acknowledged");
+ Ok(())
+ }
+
+ /// Update a URI
+ pub async fn update(&mut self, uri: &str) -> Result<u64> {
+ let msg = format!("update \"{}\"", uri);
+ let text = self.stream.req(&msg).await?;
+ debug!("Sent `{}'; got `{}'.", &msg, &text);
+
+ // We expect a response of the form:
+ // updating_db: JOBID
+ // OK
+ // on success, and
+ // ACK ERR
+ // on failure.
+
+ let prefix = "updating_db: ";
+ ensure!(
+ text.starts_with(prefix),
+ "update response doesn't start with correct prefix"
+ );
+ text[prefix.len()..].split('\n').collect::<Vec<&str>>()[0]
+ .to_string()
+ .parse::<u64>()
+ .context("Failed to treat update job id as u64")
+ }
+
+ /// Get the list of stored playlists
+ pub async fn get_stored_playlists(&mut self) -> Result<std::vec::Vec<String>> {
+ let text = self.stream.req("listplaylists").await?;
+ debug!("Sent listplaylists; got `{}'.", &text);
+
+ // We expect a response of the form:
+ // playlist: a
+ // Last-Modified: 2020-03-13T17:20:16Z
+ // playlsit: b
+ // Last-Modified: 2020-03-13T17:20:16Z
+ // ...
+ // OK
+ //
+ // or
+ //
+ // ACK...
+ ensure!(
+ !text.starts_with("ACK"),
+ "get_stored_playlists response not acknowledged"
+ );
+ Ok(text
+ .lines()
+ .filter_map(|x| x.strip_prefix("playlist: ").map(String::from))
+ .collect::<Vec<String>>())
+ }
+
+ /// Process a search (either find or search) response
+ fn search_rsp_to_uris(&self, text: &str) -> Result<std::vec::Vec<String>> {
+ // We expect a response of the form:
+ // file: P/Pogues, The - A Pistol For Paddy Garcia.mp3
+ // Last-Modified: 2007-12-26T19:18:00Z
+ // Format: 44100:24:2
+ // ...
+ // file: P/Pogues, The - Billy's Bones.mp3
+ // ...
+ // OK
+ //
+ // or
+ //
+ // ACK...
+ ensure!(!text.starts_with("ACK"), "rsp_to_uris not acknowledged");
+ Ok(text
+ .lines()
+ .filter_map(|x| x.strip_prefix("file: ").map(String::from))
+ .collect::<Vec<String>>())
+ }
+
+ /// Search the database for songs matching filter (unary operator)
+ ///
+ /// Set `case` to true to request a case-sensitive search (false yields case-insensitive)
+ pub async fn find1(
+ &mut self,
+ cond: &str,
+ val: &str,
+ case: bool,
+ ) -> Result<std::vec::Vec<String>> {
+ let cmd = format!(
+ "{} {}",
+ if case { "find" } else { "search" },
+ quote(&format!("({} {})", cond, val))
+ );
+ let text = self.stream.req(&cmd).await?;
+ self.search_rsp_to_uris(&text)
+ }
+
+ /// Search the database for songs matching filter (case-sensitive, binary operator)
+ ///
+ /// Set `case` to true to request a case-sensitive search (false yields case-insensitive)
+ pub async fn find2(
+ &mut self,
+ attr: &str,
+ op: &str,
+ val: &str,
+ case: bool,
+ ) -> Result<std::vec::Vec<String>> {
+ let cmd = format!(
+ "{} {}",
+ if case { "find" } else { "search" },
+ quote(&format!("({} {} {})", attr, op, val))
+ );
+ debug!("find2 sending ``{}''", cmd);
+ let text = self.stream.req(&cmd).await?;
+ self.search_rsp_to_uris(&text)
+ }
+
+ /// Retrieve all instances of a given sticker under the music directory
+ ///
+ /// Return a mapping from song URI to textual sticker value
+ pub async fn get_stickers(&mut self, sticker: &str) -> Result<HashMap<String, String>> {
+ let text = self
+ .stream
+ .req(&format!("sticker find song \"\" {}", sticker))
+ .await?;
+
+ // We expect a response of the form:
+ //
+ // file: U-Z/Zafari - Addis Adaba.mp3
+ // sticker: unwoundstack.com:rating=64
+ // ...
+ // file: U-Z/Zero 7 - In Time (Album Version).mp3
+ // sticker: unwoundstack.com:rating=255
+ // OK
+ //
+ // or
+ //
+ // ACK ...
+ ensure!(!text.starts_with("ACK"), "get_stickers not ACKed");
+ let mut m = HashMap::new();
+ let mut lines = text.lines();
+ loop {
+ let file = lines.next().context("get_stickers no new line")?;
+ if "OK" == file {
+ break;
+ }
+ let val = lines.next().context("get_stickers no val")?;
+
+ m.insert(
+ String::from(&file[6..]),
+ String::from(&val[10 + sticker.len()..]),
+ );
+ }
+ Ok(m)
+ }
+
+ /// Retrieve the song URIs of all songs in the database
+ ///
+ /// Returns a vector of String
+ pub async fn get_all_songs(&mut self) -> Result<std::vec::Vec<String>> {
+ let text = self.stream.req("find \"(base '')\"").await?;
+ // We expect a response of the form:
+ // file: 0-A/A Positive Life - Lighten Up!.mp3
+ // Last-Modified: 2020-11-18T22:47:07Z
+ // Format: 44100:24:2
+ // Time: 399
+ // duration: 398.550
+ // Artist: A Positive Life
+ // Title: Lighten Up!
+ // Genre: Electronic
+ // file: 0-A/A Positive Life - Pleidean Communication.mp3
+ // ...
+ // OK
+ //
+ // or "ACK..."
+ ensure!(!text.starts_with("ACK"), "get_all_songs not ACKed");
+ Ok(text
+ .lines()
+ .filter_map(|x| x.strip_prefix("file: ").map(String::from))
+ .collect::<Vec<String>>())
+ }
+
+ pub async fn add(&mut self, uri: &str) -> Result<()> {
+ let msg = format!("add {}", quote(uri));
+ let text = self.stream.req(&msg).await?;
+ debug!("Sent `{}'; got `{}'.", &msg, &text);
+
+ ensure!(text.starts_with("OK"), "add not Oked");
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod client_tests {
+ use super::test_mock::Mock;
+ use super::*;
+
+ /// Some basic "smoke" tests
+ #[tokio::test]
+ async fn client_smoke_test() {
+ let mock = Box::new(Mock::new(&[(
+ "sticker get song foo.mp3 stick",
+ "sticker: stick=splat\nOK\n",
+ )]));
+ let mut cli = Client::new(mock).unwrap();
+ let val = cli
+ .get_sticker::<String>("foo.mp3", "stick")
+ .await
+ .unwrap()
+ .unwrap();
+ assert_eq!(val, "splat");
+ }
+
+ /// Test the `status' method
+ #[tokio::test]
+ async fn test_status() {
+ let mock = Box::new(Mock::new(&[
+ (
+ "status",
+ // When the server is playing or paused, the response will look something like this:
+ "volume: -1
+repeat: 0
+random: 0
+single: 0
+consume: 0
+playlist: 3
+playlistlength: 87
+mixrampdb: 0.000000
+state: play
+song: 14
+songid: 15
+time: 141:250
+bitrate: 128
+audio: 44100:24:2
+nextsong: 15
+nextsongid: 16
+elapsed: 140.585
+OK",
+ ),
+ // Should respond with a playlist id request
+ (
+ "playlistid 15",
+ // Should look something like this:
+ "file: U-Z/U2 - Who's Gonna RIDE Your WILD HORSES.mp3
+Last-Modified: 2004-12-24T19:26:13Z
+Artist: U2
+Title: Who's Gonna RIDE Your WILD HOR
+Genre: Pop
+Time: 316
+Pos: 41
+Id: 42
+duration: 249.994
+OK",
+ ),
+ (
+ "status",
+ // But if the state is "stop", much of that will be missing; it will look more like:
+ "volume: -1
+repeat: 0
+random: 0
+single: 0
+consume: 0
+playlist: 84
+playlistlength: 27
+mixrampdb: 0.000000
+state: stop
+OK",
+ ),
+ // Finally, let's simulate something being really wrong
+ (
+ "status",
+ "volume: -1
+repeat: 0
+state: no-idea!?",
+ ),
+ ]));
+ let mut cli = Client::new(mock).unwrap();
+ let stat = cli.status().await.unwrap();
+ match stat {
+ PlayerStatus::Play(curr) => {
+ assert_eq!(curr.songid, 15);
+ assert_eq!(
+ curr.file.to_str().unwrap(),
+ "U-Z/U2 - Who's Gonna RIDE Your WILD HORSES.mp3"
+ );
+ assert_eq!(curr.elapsed, 140.585);
+ assert_eq!(curr.duration, 249.994);
+ }
+ _ => panic!(),
+ }
+
+ let stat = cli.status().await.unwrap();
+ match stat {
+ PlayerStatus::Stopped => (),
+ _ => panic!(),
+ }
+
+ let stat = cli.status().await;
+ match stat {
+ Err(_) => (),
+ Ok(_) => panic!(),
+ }
+ }
+
+ /// Test the `get_sticker' method
+ #[tokio::test]
+ async fn test_get_sticker() {
+ let mock = Box::new(Mock::new(&[
+ (
+ "sticker get song foo.mp3 stick",
+ // On success, should get something like this...
+ "sticker: stick=2\nOK\n",
+ ),
+ (
+ "sticker get song foo.mp3 stick",
+ // and on failure, something like this:
+ "ACK [50@0] {sticker} no such sticker\n",
+ ),
+ (
+ "sticker get song foo.mp3 stick",
+ // Finally, let's try something nuts
+ "",
+ ),
+ (
+ "sticker get song \"filename_with\\\"doublequotes\\\".flac\" unwoundstack.com:playcount",
+ "sticker: unwoundstack.com:playcount=11\nOK\n",
+ ),
+ ]));
+ let mut cli = Client::new(mock).unwrap();
+ let val = cli
+ .get_sticker::<String>("foo.mp3", "stick")
+ .await
+ .unwrap()
+ .unwrap();
+ assert_eq!(val, "2");
+ let _val = cli
+ .get_sticker::<String>("foo.mp3", "stick")
+ .await
+ .unwrap()
+ .is_none();
+ let _val = cli
+ .get_sticker::<String>("foo.mp3", "stick")
+ .await
+ .unwrap_err();
+ let val = cli
+ .get_sticker::<String>(
+ "filename_with\"doublequotes\".flac",
+ "unwoundstack.com:playcount",
+ )
+ .await
+ .unwrap()
+ .unwrap();
+ assert_eq!(val, "11");
+ }
+
+ /// Test the `set_sticker' method
+ #[tokio::test]
+ async fn test_set_sticker() {
+ let mock = Box::new(Mock::new(&[
+ ("sticker set song foo.mp3 stick 2", "OK\n"),
+ (
+ "sticker set song foo.mp3 stick 2",
+ "ACK [50@0] {sticker} some error",
+ ),
+ (
+ "sticker set song foo.mp3 stick 2",
+ "this makes no sense as a response",
+ ),
+ ]));
+ let mut cli = Client::new(mock).unwrap();
+ let () = cli.set_sticker("foo.mp3", "stick", &"2").await.unwrap();
+ let _val = cli.set_sticker("foo.mp3", "stick", &"2").await.unwrap_err();
+ let _val = cli.set_sticker("foo.mp3", "stick", &"2").await.unwrap_err();
+ }
+
+ /// Test the `send_to_playlist' method
+ #[tokio::test]
+ async fn test_send_to_playlist() {
+ let mock = Box::new(Mock::new(&[
+ ("playlistadd foo.m3u foo.mp3", "OK\n"),
+ (
+ "playlistadd foo.m3u foo.mp3",
+ "ACK [101@0] {playlist} some error\n",
+ ),
+ ]));
+ let mut cli = Client::new(mock).unwrap();
+ let () = cli.send_to_playlist("foo.mp3", "foo.m3u").await.unwrap();
+ let _val = cli
+ .send_to_playlist("foo.mp3", "foo.m3u")
+ .await
+ .unwrap_err();
+ }
+
+ /// Test the `update' method
+ #[tokio::test]
+ async fn test_update() {
+ let mock = Box::new(Mock::new(&[
+ ("update \"foo.mp3\"", "updating_db: 2\nOK\n"),
+ ("update \"foo.mp3\"", "ACK [50@0] {update} blahblahblah"),
+ ("update \"foo.mp3\"", "this makes no sense as a response"),
+ ]));
+ let mut cli = Client::new(mock).unwrap();
+ let _val = cli.update("foo.mp3").await.unwrap();
+ let _val = cli.update("foo.mp3").await.unwrap_err();
+ let _val = cli.update("foo.mp3").await.unwrap_err();
+ }
+
+ /// Test retrieving stored playlists
+ #[tokio::test]
+ async fn test_get_stored_playlists() {
+ let mock = Box::new(Mock::new(&[
+ (
+ "listplaylists",
+ "playlist: saturday-afternoons-in-santa-cruz
+Last-Modified: 2020-03-13T17:20:16Z
+playlist: gaelic-punk
+Last-Modified: 2020-05-24T00:36:02Z
+playlist: morning-coffee
+Last-Modified: 2020-03-13T17:20:16Z
+OK
+",
+ ),
+ ("listplaylists", "ACK [1@0] {listplaylists} blahblahblah"),
+ ]));
+
+ let mut cli = Client::new(mock).unwrap();
+ let val = cli.get_stored_playlists().await.unwrap();
+ assert_eq!(
+ val,
+ vec![
+ String::from("saturday-afternoons-in-santa-cruz"),
+ String::from("gaelic-punk"),
+ String::from("morning-coffee")
+ ]
+ );
+ let _val = cli.get_stored_playlists().await.unwrap_err();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// IdleClient //
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#[non_exhaustive]
+#[derive(Debug, PartialEq, Eq)]
+pub enum IdleSubSystem {
+ Player,
+ Message,
+}
+
+impl TryFrom<&str> for IdleSubSystem {
+ type Error = Error;
+ fn try_from(text: &str) -> std::result::Result<Self, Self::Error> {
+ let x = text.to_lowercase();
+ if x == "player" {
+ Ok(IdleSubSystem::Player)
+ } else if x == "message" {
+ Ok(IdleSubSystem::Message)
+ } else {
+ bail!("{}", text)
+ }
+ }
+}
+
+impl fmt::Display for IdleSubSystem {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ IdleSubSystem::Player => write!(f, "Player"),
+ IdleSubSystem::Message => write!(f, "Message"),
+ }
+ }
+}
+
+/// [MPD](https://www.musicpd.org) client for "idle" connections.
+///
+/// # Introduction
+///
+/// This is an MPD client designed to "idle": it opens a long-lived connection to the MPD server and
+/// waits for MPD to respond with a message indicating that there's been a change to a subsystem of
+/// interest. At present, there are only two subsystems in which [mpdpopm](crate) is interested: the player
+/// & messages (cf. [IdleSubSystem]).
+///
+/// ```no_run
+/// use std::path::Path;
+/// use tokio::runtime::Runtime;
+/// use mpdpopm::clients::IdleClient;
+///
+/// let mut rt = Runtime::new().unwrap();
+/// rt.block_on( async {
+/// let mut client = IdleClient::open(Path::new("/var/run/mpd.sock")).await.unwrap();
+/// client.subscribe("player").await.unwrap();
+/// client.idle().await.unwrap();
+/// // Arrives here when the player's state changes
+/// })
+/// ```
+///
+/// `client` is now a [Future](https://doc.rust-lang.org/stable/std/future/trait.Future.html) that
+/// resolves to an [IdleClient] instance talking to `/var/run/mpd.sock`.
+///
+pub struct IdleClient {
+ conn: Box<dyn RequestResponse>,
+}
+
+impl IdleClient {
+ /// Create a new [mpdpopm::client::IdleClient][IdleClient] instance from something that
+ /// implements [ToSocketAddrs]
+ pub async fn connect<A: ToSocketAddrs>(addr: A) -> Result<IdleClient> {
+ Self::new(MpdConnection::<TcpStream>::connect(addr).await?)
+ }
+
+ pub async fn open<P: AsRef<Path>>(pth: P) -> Result<IdleClient> {
+ Self::new(MpdConnection::<UnixStream>::connect(pth).await?)
+ }
+
+ pub fn new(stream: Box<dyn RequestResponse>) -> Result<IdleClient> {
+ Ok(IdleClient { conn: stream })
+ }
+
+ /// Subscribe to an mpd channel
+ pub async fn subscribe(&mut self, chan: &str) -> Result<()> {
+ let text = self.conn.req(&format!("subscribe {}", chan)).await?;
+ debug!("Sent subscribe message for {}; got `{}'.", chan, text);
+ ensure!(text.starts_with("OK"), "subscribe not Ok: `{}`", text);
+ debug!("Subscribed to {}.", chan);
+ Ok(())
+ }
+
+ /// Enter idle state-- return the subsystem that changed, causing the connection to return. NB
+ /// this may block for some time.
+ pub async fn idle(&mut self) -> Result<IdleSubSystem> {
+ let text = self.conn.req("idle player message").await?;
+ debug!("Sent idle message; got `{}'.", text);
+
+ // If the player state changes, we'll get: "changed: player\nOK\n"
+ //
+ // If a ratings message is sent, we'll get: "changed: message\nOK\n", to which we respond
+ // "readmessages", which should give us something like:
+ //
+ // channel: ratings
+ // message: 255
+ // OK
+ //
+ // We remain subscribed, but we need to send a new idle message.
+
+ ensure!(text.starts_with("changed: "), "idle not OK: `{}`", text);
+ let idx = text.find('\n').context("idle has no newline")?;
+
+ let result = IdleSubSystem::try_from(&text[9..idx])?;
+ let text = text[idx + 1..].to_string();
+ ensure!(text.starts_with("OK"), "idle not OKed");
+
+ Ok(result)
+ }
+
+ /// This method simply returns the results of a "readmessages" as a HashMap of channel name to
+ /// Vec of (String) messages for that channel
+ pub async fn get_messages(&mut self) -> Result<HashMap<String, Vec<String>>> {
+ let text = self.conn.req("readmessages").await?;
+ debug!("Sent readmessages; got `{}'.", text);
+
+ // We expect something like:
+ //
+ // channel: ratings
+ // message: 255
+ // OK
+ //
+ // We remain subscribed, but we need to send a new idle message.
+
+ let mut m: HashMap<String, Vec<String>> = HashMap::new();
+
+ // Populate `m' with a little state machine:
+ enum State {
+ Init,
+ Running,
+ Finished,
+ }
+ let mut state = State::Init;
+ let mut chan = String::new();
+ let mut msgs: Vec<String> = Vec::new();
+ for line in text.lines() {
+ match state {
+ State::Init => {
+ ensure!(line.starts_with("channel: "), "no `channel: ` given");
+ chan = String::from(&line[9..]);
+ state = State::Running;
+ }
+ State::Running => {
+ if let Some(stripped) = line.strip_prefix("message: ") {
+ msgs.push(String::from(stripped));
+ } else if let Some(stripped) = line.strip_prefix("channel: ") {
+ match m.get_mut(&chan) {
+ Some(v) => v.append(&mut msgs),
+ None => {
+ m.insert(chan.clone(), msgs.clone());
+ }
+ }
+ chan = String::from(stripped);
+ msgs = Vec::new();
+ } else if line == "OK" {
+ match m.get_mut(&chan) {
+ Some(v) => v.append(&mut msgs),
+ None => {
+ m.insert(chan.clone(), msgs.clone());
+ }
+ }
+ state = State::Finished;
+ } else {
+ bail!("Failed to get messages: `{}`", text)
+ }
+ }
+ State::Finished => {
+ // Should never be here!
+ bail!("Failed to get messages: `{}`", text)
+ }
+ }
+ }
+
+ Ok(m)
+ }
+}
+
+#[cfg(test)]
+/// Let's test IdleClient!
+mod idle_client_tests {
+
+ use super::test_mock::Mock;
+ use super::*;
+
+ /// Some basic "smoke" tests
+ #[tokio::test]
+ async fn test_get_messages() {
+ let mock = Box::new(Mock::new(&[(
+ "readmessages",
+ // If a ratings message is sent, we'll get: "changed: message\nOK\n", to which we
+ // respond "readmessages", which should give us something like:
+ //
+ // channel: ratings
+ // message: 255
+ // OK
+ //
+ // We remain subscribed, but we need to send a new idle message.
+ "channel: ratings
+message: 255
+message: 128
+channel: send-to-playlist
+message: foo.m3u
+OK
+",
+ )]));
+ let mut cli = IdleClient::new(mock).unwrap();
+ let hm = cli.get_messages().await.unwrap();
+ let val = hm.get("ratings").unwrap();
+ assert_eq!(val.len(), 2);
+ let val = hm.get("send-to-playlist").unwrap();
+ assert!(val.len() == 1);
+ }
+
+ /// Test issue #1
+ #[tokio::test]
+ async fn test_issue_1() {
+ let mock = Box::new(Mock::new(&[(
+ "readmessages",
+ "channel: playcounts
+message: a
+channel: playcounts
+message: b
+OK
+",
+ )]));
+ let mut cli = IdleClient::new(mock).unwrap();
+ let hm = cli.get_messages().await.unwrap();
+ let val = hm.get("playcounts").unwrap();
+ assert_eq!(val.len(), 2);
+ }
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/config.rs b/pkgs/by-name/mp/mpdpopm/src/config.rs
new file mode 100644
index 00000000..8bb5abfb
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/config.rs
@@ -0,0 +1,189 @@
+// Copyright (C) 2021-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/>.
+
+//! # mpdpopm Configuration
+//!
+//! ## Introduction
+//!
+//! This module defines the configuration struct & handles deserialization thereof.
+//!
+//! ## Discussion
+//!
+//! In the first releases of [mpdpopm](crate) I foolishly forgot to add a version field to the
+//! configuration structure. I am now paying for my sin by having to attempt serializing two
+//! versions until one succeeds.
+//!
+//! The idiomatic approach to versioning [serde](https://docs.serde.rs/serde/) structs seems to be
+//! using an
+//! [enumeration](https://www.reddit.com/r/rust/comments/44dds3/handling_multiple_file_versions_with_serde_or/). This
+//! implementation *now* uses that, but that leaves us with the problem of handling the initial,
+//! un-tagged version. I proceed as follows:
+//!
+//! 1. attempt to deserialize as a member of the modern enumeration
+//! 2. if that succeeds, with the most-recent version, we're good
+//! 3. if that succeeds with an archaic version, convert to the most recent and warn the user
+//! 4. if that fails, attempt to deserialize as the initial struct version
+//! 5. if that succeeds, convert to the most recent & warn the user
+//! 6. if that fails, I'm kind of stuck because I don't know what the user was trying to express;
+//! bundle-up all the errors, report 'em & urge the user to use the most recent version
+use crate::vars::{LOCALSTATEDIR, PREFIX};
+
+use anyhow::{Result, bail};
+use serde::{Deserialize, Serialize};
+
+use std::{env, path::PathBuf};
+
+/// [mpdpopm](crate) can communicate with MPD over either a local Unix socket, or over regular TCP
+#[derive(Debug, Deserialize, PartialEq, Serialize)]
+pub enum Connection {
+ /// Local Unix socket-- payload is the path to the socket
+ Local { path: PathBuf },
+ /// TCP-- payload is the hostname & port number
+ TCP { host: String, port: u16 },
+}
+
+impl Connection {
+ pub fn new() -> Result<Self> {
+ 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
+ Ok(Self::Local {
+ path: PathBuf::from(env),
+ })
+ } else {
+ todo!("Not yet able to auto-parse, MPD_HOST for remote connection")
+ }
+ }
+}
+
+impl Default for Connection {
+ fn default() -> Self {
+ Self::new().expect("Could not generate default connection")
+ }
+}
+
+#[cfg(test)]
+mod test_connection {
+ use super::Connection;
+
+ #[test]
+ fn test_serde() {
+ use serde_json::to_string;
+
+ use std::path::PathBuf;
+
+ let text = to_string(&Connection::Local {
+ path: PathBuf::from("/var/run/mpd.sock"),
+ })
+ .unwrap();
+
+ assert_eq!(
+ text,
+ String::from(r#"{"Local":{"path":"/var/run/mpd.sock"}}"#)
+ );
+
+ let text = to_string(&Connection::TCP {
+ host: String::from("localhost"),
+ port: 6600,
+ })
+ .unwrap();
+ assert_eq!(
+ text,
+ String::from(r#"{"TCP":{"host":"localhost","port":6600}}"#)
+ );
+ }
+}
+
+/// 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)]
+pub struct Config {
+ /// Configuration format version-- must be "1"
+ // Workaround to https://github.com/rotty/lexpr-rs/issues/77
+ // When this gets fixed, I can remove this element from the struct & deserialize as
+ // a Configurations element-- the on-disk format will be the same.
+ #[serde(rename = "version")]
+ _version: String,
+
+ /// Location of log file
+ pub log: PathBuf,
+
+ /// 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,
+
+ /// Percentage threshold, expressed as a number between zero & one, for considering a song to
+ /// have been played
+ pub played_thresh: f64,
+
+ /// The interval, in milliseconds, at which to poll `mpd' for the current state
+ pub poll_interval_ms: u64,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self::new().unwrap()
+ }
+}
+
+impl Config {
+ fn new() -> Result<Self> {
+ Ok(Self {
+ _version: String::from("1"),
+ log: [LOCALSTATEDIR, "log", "mppopmd.log"].iter().collect(),
+ conn: Connection::new()?,
+ local_music_dir: [PREFIX, "Music"].iter().collect(),
+ played_thresh: 0.6,
+ poll_interval_ms: 5000,
+ mode: Mode::default(),
+ })
+ }
+}
+
+pub fn from_str(text: &str) -> Result<Config> {
+ let cfg: Config = match serde_json::from_str(text) {
+ Ok(cfg) => cfg,
+ Err(err_outer) => {
+ bail!("Failed to parse config: `{}`", err_outer)
+ }
+ };
+ Ok(cfg)
+}
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..3587df93
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/dj/algorithms.rs
@@ -0,0 +1,233 @@
+use std::{
+ collections::HashSet,
+ time::{Duration, SystemTime},
+};
+
+use anyhow::{Context, Result};
+use rand::{RngExt, distr};
+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 negative = sorted_tracks.drain(..len).collect::<Vec<_>>();
+ let neutral = sorted_tracks.drain(..len).collect::<Vec<_>>();
+ let positive = sorted_tracks;
+
+ 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
new file mode 100644
index 00000000..970fc040
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/filters.lalrpop
@@ -0,0 +1,160 @@
+// Copyright (C) 2020-2025 Michael Herstine <sp1ff@pobox.com> -*- mode: rust; rust-format-on-save: nil -*-
+//
+// 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/>.
+
+use lalrpop_util::ParseError;
+
+use crate::filters_ast::{
+ Conjunction,
+ Disjunction,
+ Expression,
+ OpCode,
+ Selector,
+ Term,
+ Value,
+ expect_quoted,
+ parse_iso_8601
+};
+use tracing::debug;
+
+grammar;
+
+pub ExprOp: OpCode = {
+ "==" => OpCode::Equality,
+ "!=" => OpCode::Inequality,
+ "contains" => OpCode::Contains,
+ "=~" => OpCode::RegexMatch,
+ "!~" => OpCode::RegexExclude,
+ ">" => OpCode::GreaterThan,
+ "<" => OpCode::LessThan,
+ ">=" => OpCode::GreaterThanEqual,
+ "<=" => OpCode::LessThanEqual,
+};
+
+pub ExprSel: Selector = {
+ r"(?i)artist" => Selector::Artist,
+ r"(?i)album" => Selector::Album,
+ r"(?i)albumartist" => Selector::AlbumArtist,
+ r"(?i)title" => Selector::Title,
+ r"(?i)track" => Selector::Track,
+ r"(?i)name" => Selector::Name,
+ r"(?i)genre" => Selector::Genre,
+ r"(?i)date" => Selector::Date,
+ r"(?i)originaldate" => Selector::OriginalDate,
+ r"(?i)composer" => Selector::Composer,
+ r"(?i)performer" => Selector::Performer,
+ r"(?i)conductor" => Selector::Conductor,
+ r"(?i)work" => Selector::Work,
+ r"(?i)grouping" => Selector::Grouping,
+ r"(?i)comment" => Selector::Comment,
+ r"(?i)disc" => Selector::Disc,
+ r"(?i)label" => Selector::Label,
+ r"(?i)musicbrainz_aristid" => Selector::MusicbrainzAristID,
+ r"(?i)musicbrainz_albumid" => Selector::MusicbrainzAlbumID,
+ r"(?i)musicbrainz_albumartistid" => Selector::MusicbrainzAlbumArtistID,
+ r"(?i)musicbrainz_trackid" => Selector::MusicbrainzTrackID,
+ r"(?i)musicbrainz_releasetrackid" => Selector::MusicbrainzReleaseTrackID,
+ r"(?i)musicbrainz_workid" => Selector::MusicbrainzWorkID,
+ r"(?i)file" => Selector::File,
+ r"(?i)base" => Selector::Base,
+ r"(?i)modified-since" => Selector::ModifiedSince,
+ r"(?i)audioformat" => Selector::AudioFormat,
+ 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]+"> =>? {
+ debug!("matched token: ``{}''.", s);
+ // We need to yield a Result<Value, ParseError>
+ match s.parse::<usize>() {
+ Ok(n) => Ok(Value::Uint(n)),
+ 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~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\"|\\')+""#> => {
+ debug!("matched token: ``{}''.", s);
+ let s = expect_quoted(s).unwrap();
+ match parse_iso_8601(&mut s.as_bytes()) {
+ Ok(x) => Value::UnixEpoch(x),
+ Err(_) => Value::Text(s),
+ }
+ },
+ <s:r#"'([ \t"a-zA-Z0-9~!@#$%^&*()-=_+\[\]{}|;:<>,./?]|\\\\|\\'|\\")+'"#> => {
+ debug!("matched token: ``{}''.", s);
+ let s = expect_quoted(s).unwrap();
+ match parse_iso_8601(&mut s.as_bytes()) {
+ Ok(x) => Value::UnixEpoch(x),
+ Err(_) => Value::Text(s),
+ }
+ },
+};
+
+pub Term: Box<Term> = {
+ <t:ExprSel> <o:ExprOp> <u:Token> => {
+ 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> => {
+ debug!("matched conjunction: ``({:#?}, {:#?})''", e1, e2);
+ Box::new(Conjunction::Simple(e1, e2))
+ },
+ <c:Conjunction> "AND" <e:Expression> => {
+ debug!("matched conjunction: ``({:#?}, {:#?})''", c, e);
+ Box::new(Conjunction::Compound(c, e))
+ },
+}
+
+pub Disjunction: Box<Disjunction> = {
+ <e1:Expression> "OR" <e2:Expression> => {
+ debug!("matched disjunction: ``({:#?}, {:#?})''", e1, e2);
+ Box::new(Disjunction::Simple(e1, e2))
+ },
+ <c:Disjunction> "OR" <e:Expression> => {
+ debug!("matched disjunction: ``({:#?}, {:#?})''", c, e);
+ Box::new(Disjunction::Compound(c, e))
+ },
+}
+
+pub Expression: Box<Expression> = {
+ "(" <t:Term> ")" => {
+ debug!("matched parenthesized term: ``({:#?})''", t);
+ Box::new(Expression::Simple(t))
+ },
+ "(" "!" <e:Expression> ")" => Box::new(Expression::Negation(e)),
+ "(" <c:Conjunction> ")" => {
+ debug!("matched parenthesized conjunction: ``({:#?})''", c);
+ Box::new(Expression::Conjunction(c))
+ },
+ "(" <c:Disjunction> ")" => {
+ 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
new file mode 100644
index 00000000..9c68d329
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/filters_ast.rs
@@ -0,0 +1,1022 @@
+// 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/>.
+
+//! Types for building the Abstract Syntax Tree when parsing filters
+//!
+//! 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, skip_count};
+
+use anyhow::{Context, Error, Result, anyhow, bail};
+use boolinator::Boolinator;
+use chrono::prelude::*;
+use tracing::debug;
+
+use std::collections::{HashMap, HashSet};
+use std::str::FromStr;
+
+/// The operations that can appear in a filter term
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum OpCode {
+ Equality,
+ Inequality,
+ Contains,
+ RegexMatch,
+ RegexExclude,
+ GreaterThan,
+ LessThan,
+ GreaterThanEqual,
+ LessThanEqual,
+}
+
+impl std::fmt::Display for OpCode {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ OpCode::Equality => "==",
+ OpCode::Inequality => "!=",
+ OpCode::Contains => "contains",
+ OpCode::RegexMatch => "=~",
+ OpCode::RegexExclude => "!~",
+ OpCode::GreaterThan => ">",
+ OpCode::LessThan => "<",
+ OpCode::GreaterThanEqual => ">=",
+ OpCode::LessThanEqual => "<=",
+ }
+ )
+ }
+}
+
+/// The song attributes that can appear on the LHS of a filter term
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum Selector {
+ Artist,
+ Album,
+ AlbumArtist,
+ Title,
+ Track,
+ Name,
+ Genre,
+ Date,
+ OriginalDate,
+ Composer,
+ Performer,
+ Conductor,
+ Work,
+ Grouping,
+ Comment,
+ Disc,
+ Label,
+ MusicbrainzAristID,
+ MusicbrainzAlbumID,
+ MusicbrainzAlbumArtistID,
+ MusicbrainzTrackID,
+ MusicbrainzReleaseTrackID,
+ MusicbrainzWorkID,
+ File,
+ Base,
+ ModifiedSince,
+ AudioFormat,
+ Rating,
+ PlayCount,
+ LastPlayed,
+ Skipped,
+}
+
+impl std::fmt::Display for Selector {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ Selector::Artist => "artist",
+ Selector::Album => "album",
+ Selector::AlbumArtist => "albumartist",
+ Selector::Title => "title",
+ Selector::Track => "track",
+ Selector::Name => "name",
+ Selector::Genre => "genre",
+ Selector::Date => "date",
+ Selector::OriginalDate => "originaldate",
+ Selector::Composer => "composer",
+ Selector::Performer => "performer",
+ Selector::Conductor => "conductor",
+ Selector::Work => "work",
+ Selector::Grouping => "grouping",
+ Selector::Comment => "comment",
+ Selector::Disc => "disc",
+ Selector::Label => "label",
+ Selector::MusicbrainzAristID => "musicbrainz_aristid",
+ Selector::MusicbrainzAlbumID => "musicbrainz_albumid",
+ Selector::MusicbrainzAlbumArtistID => "musicbrainz_albumartistid",
+ Selector::MusicbrainzTrackID => "musicbrainz_trackid",
+ Selector::MusicbrainzReleaseTrackID => "musicbrainz_releasetrackid",
+ Selector::MusicbrainzWorkID => "musicbrainz_workid",
+ Selector::File => "file",
+ Selector::Base => "base",
+ Selector::ModifiedSince => "modified-since",
+ Selector::AudioFormat => "AudioFormat",
+ Selector::Rating => "rating",
+ Selector::PlayCount => "playcount",
+ Selector::LastPlayed => "lastplayed",
+ Selector::Skipped => "skipped",
+ }
+ )
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum Value {
+ Text(String),
+ UnixEpoch(i64),
+ Uint(usize),
+ Int(i64),
+}
+
+fn quote_value(x: &Value) -> String {
+ match x {
+ Value::Text(s) => {
+ let mut ret = String::new();
+
+ ret.push('"');
+ for c in s.chars() {
+ if c == '"' || c == '\\' {
+ ret.push('\\');
+ }
+ ret.push(c);
+ }
+ ret.push('"');
+ ret
+ }
+ Value::UnixEpoch(n) => {
+ format!("'{}'", n)
+ }
+ Value::Uint(n) => {
+ format!("'{}'", n)
+ }
+ Value::Int(n) => {
+ format!("'{}'", n)
+ }
+ }
+}
+
+#[derive(Clone, Debug)]
+pub enum Term {
+ UnaryCondition(Selector, Value),
+ BinaryCondition(Selector, OpCode, Value),
+}
+
+#[derive(Clone, Debug)]
+pub enum Conjunction {
+ Simple(Box<Expression>, Box<Expression>),
+ Compound(Box<Conjunction>, Box<Expression>),
+}
+
+#[derive(Clone, Debug)]
+pub enum Disjunction {
+ Simple(Box<Expression>, Box<Expression>),
+ Compound(Box<Disjunction>, Box<Expression>),
+}
+
+#[derive(Clone, Debug)]
+pub enum Expression {
+ Simple(Box<Term>),
+ Negation(Box<Expression>),
+ Conjunction(Box<Conjunction>),
+ Disjunction(Box<Disjunction>),
+}
+
+#[cfg(test)]
+mod smoke_tests {
+ use super::*;
+ use crate::filters::*;
+
+ #[test]
+ fn test_opcodes() {
+ assert!(ExprOpParser::new().parse("==").unwrap() == OpCode::Equality);
+ assert!(ExprOpParser::new().parse("!=").unwrap() == OpCode::Inequality);
+ assert!(ExprOpParser::new().parse("contains").unwrap() == OpCode::Contains);
+ assert!(ExprOpParser::new().parse("=~").unwrap() == OpCode::RegexMatch);
+ assert!(ExprOpParser::new().parse("!~").unwrap() == OpCode::RegexExclude);
+ assert!(ExprOpParser::new().parse(">").unwrap() == OpCode::GreaterThan);
+ assert!(ExprOpParser::new().parse("<").unwrap() == OpCode::LessThan);
+ assert!(ExprOpParser::new().parse(">=").unwrap() == OpCode::GreaterThanEqual);
+ assert!(ExprOpParser::new().parse("<=").unwrap() == OpCode::LessThanEqual);
+ }
+
+ #[test]
+ fn test_conditions() {
+ assert!(TermParser::new().parse("base 'foo'").is_ok());
+ assert!(TermParser::new().parse("artist == 'foo'").is_ok());
+ assert!(
+ TermParser::new()
+ .parse(r#"artist =~ "foo bar \"splat\"!""#)
+ .is_ok()
+ );
+ assert!(TermParser::new().parse("artist =~ 'Pogues'").is_ok());
+
+ match *TermParser::new()
+ .parse(r#"base "/Users/me/My Music""#)
+ .unwrap()
+ {
+ Term::UnaryCondition(a, b) => {
+ assert!(a == Selector::Base);
+ assert!(b == Value::Text(String::from(r#"/Users/me/My Music"#)));
+ }
+ _ => {
+ unreachable!();
+ }
+ }
+
+ match *TermParser::new()
+ .parse(r#"artist =~ "foo bar \"splat\"!""#)
+ .unwrap()
+ {
+ Term::BinaryCondition(t, op, s) => {
+ assert!(t == Selector::Artist);
+ assert!(op == OpCode::RegexMatch);
+ assert!(s == Value::Text(String::from(r#"foo bar "splat"!"#)));
+ }
+ _ => {
+ unreachable!();
+ }
+ }
+ }
+
+ #[test]
+ fn test_expressions() {
+ assert!(ExpressionParser::new().parse("( base 'foo' )").is_ok());
+ assert!(ExpressionParser::new().parse("(base \"foo\")").is_ok());
+ assert!(
+ ExpressionParser::new()
+ .parse("(!(artist == 'value'))")
+ .is_ok()
+ );
+ assert!(
+ ExpressionParser::new()
+ .parse(r#"((!(artist == "foo bar")) AND (base "/My Music"))"#)
+ .is_ok()
+ );
+ }
+
+ #[test]
+ fn test_quoted_expr() {
+ eprintln!("test_quoted_expr");
+ assert!(
+ ExpressionParser::new()
+ .parse(r#"(artist =~ "foo\\bar\"")"#)
+ .is_ok()
+ );
+ }
+
+ #[test]
+ fn test_real_expression() {
+ let result = ExpressionParser::new()
+ .parse(r#"(((Artist =~ 'Flogging Molly') OR (artist =~ 'Dropkick Murphys') OR (artist =~ 'Pogues')) AND ((rating > 128) OR (rating == 0)))"#);
+ eprintln!("{:#?}", result);
+ assert!(result.is_ok());
+ }
+
+ #[test]
+ fn test_conjunction() {
+ assert!(ExpressionParser::new()
+ .parse(
+ r#"((base "foo") AND (artist == "foo bar") AND (!(file == '/net/mp3/A/a.mp3')))"#
+ )
+ .is_ok());
+
+ eprintln!("==============================================================================");
+ eprintln!("{:#?}", ExpressionParser::new()
+ .parse(
+ r#"((base 'foo') AND (artist == "foo bar") AND ((!(file == "/net/mp3/A/a.mp3")) OR (file == "/pub/mp3/A/a.mp3")))"#
+ ));
+ assert!(ExpressionParser::new()
+ .parse(
+ r#"((base 'foo') AND (artist == "foo bar") AND ((!(file == '/net/mp3/A/a.mp3')) OR (file == '/pub/mp3/A/a.mp3')))"#
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn test_disjunction() {
+ assert!(ExpressionParser::new().
+ parse(r#"((artist =~ 'Flogging Molly') OR (artist =~ 'Dropkick Murphys') OR (artist =~ 'Pogues'))"#)
+ .is_ok());
+ }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum EvalOp {
+ And,
+ Or,
+ Not,
+}
+
+impl std::fmt::Display for EvalOp {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match self {
+ EvalOp::And => write!(f, "And"),
+ EvalOp::Or => write!(f, "Or"),
+ EvalOp::Not => write!(f, "Not"),
+ }
+ }
+}
+
+fn peek(buf: &[u8]) -> Option<char> {
+ match buf.len() {
+ 0 => None,
+ _ => Some(buf[0] as char),
+ }
+}
+
+// advancing a slice by `i` indicies can *not* be this difficult
+/// Pop a single byte off of `buf`
+fn take1(buf: &mut &[u8], i: usize) -> Result<()> {
+ if i > buf.len() {
+ bail!("Bad iso-8601 string: `{:#?}`", buf);
+ }
+ let (_first, second) = buf.split_at(i);
+ *buf = second;
+ Ok(())
+}
+
+/// Pop `i` bytes off of `buf` & parse them as a T
+fn take2<T>(buf: &mut &[u8], i: usize) -> Result<T>
+where
+ T: FromStr,
+ <T as std::str::FromStr>::Err: std::error::Error + Send + Sync + 'static,
+{
+ // 1. check len
+ if i > buf.len() {
+ bail!("Bad iso-8601 string: `{:#?}`", buf);
+ }
+
+ let (first, second) = buf.split_at(i);
+ *buf = second;
+ // 2. convert to a string
+ let s = std::str::from_utf8(first).context("Bad iso-8601 string")?;
+ // 3. parse as a T
+ s.parse::<T>()
+ .context("Failed to parse iso-8601 string as T")
+}
+
+/// Parse a timestamp in ISO 8601 format to a chrono DateTime instance
+///
+/// Surprisingly, I was unable to find an ISO 8601 parser in Rust. I *did* find a crate named
+/// iso-8601 that promised to do this, but it seemed derelict & I couldn't see what to do with the
+/// parse output in any event. The ISO 8601 format is simple enough that I've chosen to simply
+/// hand-parse it.
+pub fn parse_iso_8601(buf: &mut &[u8]) -> Result<i64> {
+ // I wonder if `nom` would be a better choice?
+
+ // The first four characters must be the year (expanded year representation is not supported by
+ // this parser).
+
+ let year: i32 = take2(buf, 4)?;
+
+ // Now at this point:
+ // 1. we may be done (i.e. buf.len() == 0)
+ // 2. we may have the timestamp (peek(buf) => Some('T'))
+ // - day & month := 0, consume the 'T', move on to parsing the time
+ // 3. we may have a month in extended format (i.e. peek(buf) => Some('-')
+ // - consume the '-', parse the month & move on to parsing the day
+ // 4. we may have a month in basic format (take(buf, 2) => Some('\d\d')
+ // - parse the month & move on to parsing the day
+ let mut month = 1;
+ let mut day = 1;
+ let mut hour = 0;
+ let mut minute = 0;
+ let mut second = 0;
+ if !buf.is_empty() {
+ let next = peek(buf);
+ if next != Some('T') {
+ let mut ext_fmt = false;
+ if next == Some('-') {
+ take1(buf, 1)?;
+ ext_fmt = true;
+ }
+ month = take2(buf, 2)?;
+
+ // At this point:
+ // 1. we may be done (i.e. buf.len() == 0)
+ // 2. we may have the timestamp (peek(buf) => Some('T'))
+ // 3. we may have the day (in basic or extended format)
+ if !buf.is_empty() && peek(buf) != Some('T') {
+ if ext_fmt {
+ take1(buf, 1)?;
+ }
+ day = take2(buf, 2)?;
+ }
+ }
+
+ // Parse time: at this point, buf will either be empty or begin with 'T'
+ if !buf.is_empty() {
+ take1(buf, 1)?;
+ // If there's a T, there must at least be an hour
+ hour = take2(buf, 2)?;
+ if !buf.is_empty() {
+ let mut ext_fmt = false;
+ if peek(buf) == Some(':') {
+ take1(buf, 1)?;
+ ext_fmt = true;
+ }
+ minute = take2(buf, 2)?;
+ if !buf.is_empty() {
+ if ext_fmt {
+ take1(buf, 1)?;
+ }
+ second = take2(buf, 2)?;
+ }
+ }
+ }
+
+ // At this point, there may be a timezone
+ if !buf.is_empty() {
+ if peek(buf) == Some('Z') {
+ return Ok(Utc
+ .with_ymd_and_hms(year, month, day, hour, minute, second)
+ .single()
+ .ok_or(anyhow!("bad iso-8601 string"))?
+ .timestamp());
+ } else {
+ let next = peek(buf);
+ if next != Some('-') && next != Some('+') {
+ bail!("bad iso-8601 string")
+ }
+ let west = next == Some('-');
+ take1(buf, 1)?;
+
+ let hours: i32 = take2(buf, 2)?;
+ let mut minutes = 0;
+
+ if !buf.is_empty() {
+ if peek(buf) == Some(':') {
+ take1(buf, 1)?;
+ }
+ minutes = take2(buf, 2)?;
+ }
+
+ if west {
+ return Ok(FixedOffset::west_opt(hours * 3600 + minutes * 60)
+ .ok_or(anyhow!("Bad iso-8601 string"))?
+ .with_ymd_and_hms(year, month, day, hour, minute, second)
+ .single()
+ .ok_or(anyhow!("Bad iso-8601 string"))?
+ .timestamp());
+ } else {
+ return Ok(FixedOffset::east_opt(hours * 3600 + minutes * 60)
+ .ok_or(anyhow!("Bad iso-8601 string"))?
+ .with_ymd_and_hms(year, month, day, hour, minute, second)
+ .single()
+ .ok_or(anyhow!("Bad iso-8601 string"))?
+ .timestamp());
+ }
+ }
+ }
+ }
+ Ok(Local
+ .with_ymd_and_hms(year, month, day, hour, minute, second)
+ .single()
+ .ok_or(anyhow!("Bad iso-8601 string"))?
+ .timestamp())
+}
+
+#[cfg(test)]
+mod iso_8601_tests {
+
+ use super::*;
+
+ #[test]
+ fn smoke_tests() {
+ let mut b = "19700101T00:00:00Z".as_bytes();
+ let t = parse_iso_8601(&mut b).unwrap();
+ assert!(t == 0);
+
+ let mut b = "19700101T00:00:01Z".as_bytes();
+ let t = parse_iso_8601(&mut b).unwrap();
+ assert!(t == 1);
+
+ let mut b = "20210327T02:26:53Z".as_bytes();
+ let t = parse_iso_8601(&mut b).unwrap();
+ assert_eq!(t, 1616812013);
+
+ let mut b = "20210327T07:29:05-07:00".as_bytes();
+ let t = parse_iso_8601(&mut b).unwrap();
+ assert_eq!(t, 1616855345);
+
+ let mut b = "2021".as_bytes();
+ // Should resolve to midnight, Jan 1 2021 in local time; don't want to test against the
+ // timestamp; just make sure it parses
+ parse_iso_8601(&mut b).unwrap();
+ }
+}
+
+/// "Un-quote" a token
+///
+/// Textual tokens must be quoted, and double-quote & backslashes within backslash-escaped. If the
+/// string is quoted with single-quotes, then any single-quotes inside the string will also need
+/// to be escaped.
+///
+/// In fact, *any* characters within may be harmlessly backslash escaped; the MPD implementation
+/// walks the the string, skipping backslashes as it goes, so this implementation will do the same.
+/// I have named this method in imitation of the corresponding MPD function.
+pub fn expect_quoted(qtext: &str) -> Result<String> {
+ let mut iter = qtext.chars();
+ let quote = iter.next();
+ if quote.is_none() {
+ return Ok(String::new());
+ }
+
+ if quote != Some('\'') && quote != Some('"') {
+ bail!("Expected text to be quoted: `{}`", qtext);
+ }
+
+ let mut ret = String::new();
+
+ // Walk qtext[1..]; copying characters to `ret'. If a '\' is found, skip to the next character
+ // (even if that is a '\'). The last character in qtext should be the closing quote.
+ let mut this = iter.next();
+ while this != quote {
+ if this == Some('\\') {
+ this = iter.next();
+ }
+ match this {
+ Some(c) => ret.push(c),
+ None => {
+ bail!("Expected text to be quoted: `{}`", qtext);
+ }
+ }
+ this = iter.next();
+ }
+
+ Ok(ret)
+}
+
+#[cfg(test)]
+mod quoted_tests {
+
+ use super::*;
+
+ #[test]
+ fn smoke_tests() {
+ let b = r#""foo bar \"splat!\"""#;
+ let s = expect_quoted(b).unwrap();
+ assert!(s == r#"foo bar "splat!""#);
+ }
+}
+
+/// Create a closure that will carry out an operator on its argument
+///
+/// Call this function with an [OpCode] and a value of type `T`. `T` must be [PartialEq],
+/// [`PartialOrd`] and [`Copy`]-- an integral type will do. It will return a closure that will carry
+/// out the given [OpCode] against the given value. For instance,
+/// `make_numeric_closure::<u8>(OpCode::Equality, 11)` will return a closure that takes a `u8` &
+/// will return true if its argument is 11 (and false otherwise).
+///
+/// If [OpCode] is not pertinent to a numeric type, then this function will return Err.
+fn make_numeric_closure<'a, T: 'a + PartialEq + PartialOrd + Copy>(
+ op: OpCode,
+ val: T,
+) -> Result<impl Fn(T) -> bool + 'a> {
+ // Rust closures each have their own type, so this was the only way I could find to
+ // return them from match arms. This seems ugly; perhaps there's something I'm
+ // missing.
+ //
+ // I have no idea why I have to make these `move` closures; T is constrained to by Copy-able,
+ // so I would have expected the closure to just take a copy.
+ match op {
+ OpCode::Equality => Ok(Box::new(move |x: T| x == val) as Box<dyn Fn(T) -> bool>),
+ OpCode::Inequality => Ok(Box::new(move |x: T| x != val) as Box<dyn Fn(T) -> bool>),
+ OpCode::GreaterThan => Ok(Box::new(move |x: T| x > val) as Box<dyn Fn(T) -> bool>),
+ OpCode::LessThan => Ok(Box::new(move |x: T| x < val) as Box<dyn Fn(T) -> bool>),
+ OpCode::GreaterThanEqual => Ok(Box::new(move |x: T| x >= val) as Box<dyn Fn(T) -> bool>),
+ OpCode::LessThanEqual => Ok(Box::new(move |x: T| x <= val) as Box<dyn Fn(T) -> bool>),
+ _ => bail!("Invalid operant: `{op}`"),
+ }
+}
+
+async fn eval_numeric_sticker_term<
+ // The `FromStr' trait bound is really weird, but if I don't constrain the associated
+ // Err type to be `ParseIntError' the compiler complains about not being able to convert
+ // it to type `Error'. I'm probably still "thinking in C++" and imagining the compiler
+ // instantiating this function for each type (u8, usize, &c) instead of realizing that the Rust
+ // compiler is processing this as a first-class function.
+ //
+ // For instance, I can do the conversion manually, so long as I constrain the Err type
+ // to implement std::error::Error. I should probably be doing that, but it clutters the
+ // code. I'll figure it out when I need to extend this function to handle non-integral types
+ // :)
+ T: PartialEq + PartialOrd + Copy + FromStr<Err = std::num::ParseIntError> + std::fmt::Display,
+>(
+ sticker: &str,
+ client: &mut Client,
+ op: OpCode,
+ numeric_val: T,
+ default_val: T,
+) -> Result<HashSet<String>> {
+ let cmp = make_numeric_closure(op, numeric_val)?;
+ // It would be better to idle on the sticker DB & just update our collection on change, but for
+ // a first impl. this will do.
+ //
+ // Call `get_stickers'; this will return a HashMap from song URIs to ratings expressed as text
+ // (as all stickers are). This stanza will drain that collection into a new one with the ratings
+ // expressed as T.
+ //
+ // The point is that conversion from text to rating, lastplayed, or whatever can fail; the
+ // invocation of `collect' will call `from_iter' to convert a collection of Result-s to a Result
+ // of a collection.
+ let mut m = client
+ .get_stickers(sticker)
+ .await
+ .context("Failed to get stickers from client")?
+ .drain()
+ .map(|(k, v)| v.parse::<T>().map(|x| (k, x)))
+ .collect::<std::result::Result<HashMap<String, T>, _>>()
+ .context("Failed to parse sticker as T")?;
+ // `m' is now a map of song URI to rating/playcount/wathever (expressed as a T)... for all songs
+ // that have the salient sticker.
+ //
+ // This seems horribly inefficient, but I'm going to fetch all the song URIs in the music DB,
+ // and augment `m' with entries of `default_val' for any that are not already there.
+ client
+ .get_all_songs()
+ .await
+ .context("Failed to get all songs from client")?
+ .drain(..)
+ .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()
+ .filter_map(|(k, v)| cmp(v).as_some(k))
+ .collect::<HashSet<String>>())
+}
+
+/// Convenience struct collecting the names for assorted stickers on which one may search
+///
+/// While the search terms 'rating', 'playcount' &c are fixed & part of the filter grammar offered
+/// by mpdpopm, the precise names of the corresponding stickers are configurable & hence must be
+/// passed in. Three references to str is already unweildy IMO, and since I expect the number of
+/// stickers on which one can search to grow further, I decided to wrap 'em up in a struct. The
+/// lifetime is there to support the caller just using a reference to an existing string rather than
+/// making a copy.
+pub struct FilterStickerNames<'a> {
+ rating: &'a str,
+ playcount: &'a str,
+ lastplayed: &'a str,
+ skipped: &'a str,
+}
+
+impl FilterStickerNames<'static> {
+ pub fn new() -> FilterStickerNames<'static> {
+ Self::default()
+ }
+}
+
+impl Default for FilterStickerNames<'static> {
+ fn default() -> Self {
+ Self {
+ rating: rating::STICKER,
+ playcount: play_count::STICKER,
+ lastplayed: last_played::STICKER,
+ skipped: skip_count::STICKER,
+ }
+ }
+}
+
+/// Evaluate a Term
+///
+/// Take a Term from the Abstract Syntax tree & resolve it to a collection of song URIs. Set `case`
+/// to `true` to search case-sensitively & `false` to make the search case-insensitive.
+async fn eval_term<'a>(
+ term: &Term,
+ case: bool,
+ client: &mut Client,
+ stickers: &FilterStickerNames<'a>,
+) -> Result<HashSet<String>> {
+ match term {
+ Term::UnaryCondition(op, val) => Ok(client
+ .find1(&format!("{}", op), &quote_value(val), case)
+ .await
+ .context("Failed to find1 on client")?
+ .drain(..)
+ .collect()),
+ Term::BinaryCondition(attr, op, val) => {
+ if *attr == Selector::Rating {
+ 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) => {
+ Ok(
+ eval_numeric_sticker_term(stickers.playcount, client, *op, *n, 0)
+ .await?,
+ )
+ }
+ _ => bail!("filter play_count expect an unsigned int; got {:#?}", val),
+ }
+ } else if *attr == Selector::LastPlayed {
+ match val {
+ Value::UnixEpoch(t) => {
+ Ok(
+ eval_numeric_sticker_term(stickers.lastplayed, client, *op, *t, 0)
+ .await?,
+ )
+ }
+ _ => 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
+ .find2(
+ &format!("{}", attr),
+ &format!("{}", op),
+ &quote_value(val),
+ case,
+ )
+ .await
+ .context("Failed to `find2` on client")?
+ .drain(..)
+ .collect())
+ }
+ }
+ }
+}
+
+/// The evaluation stack contains logical operators & sets of song URIs
+#[derive(Debug)]
+enum EvalStackNode {
+ Op(EvalOp),
+ Result(HashSet<String>),
+}
+
+async fn negate_result(
+ res: &HashSet<String>,
+ client: &mut Client,
+) -> std::result::Result<HashSet<String>, Error> {
+ Ok(client
+ .get_all_songs()
+ .await
+ .context("Failed to get all songs from client")?
+ .drain(..)
+ .filter_map(|song| {
+ // Some(thing) adds thing, None elides it
+ if !res.contains(&song) {
+ Some(song)
+ } else {
+ None
+ }
+ })
+ .collect::<HashSet<String>>())
+}
+
+/// Reduce the evaluation stack as far as possible.
+///
+/// We can pop the stack in two cases:
+///
+/// 1. S.len() > 2 and S[-3] is either And or Or, and both S[-1] & S[-2] are Result-s
+/// 2. S.len() > 1, S[-2] is Not, and S[-1] is a Result
+async fn reduce(stack: &mut Vec<EvalStackNode>, client: &mut Client) -> Result<()> {
+ loop {
+ let mut reduced = false;
+ let n = stack.len();
+ if n > 1 {
+ // Take care to compute the reduction *before* popping the stack-- thank you, borrow
+ // checker!
+ let reduction = if let (EvalStackNode::Op(EvalOp::Not), EvalStackNode::Result(r)) =
+ (&stack[n - 2], &stack[n - 1])
+ {
+ Some(negate_result(r, client).await?)
+ } else {
+ None
+ };
+
+ if let Some(res) = reduction {
+ stack.pop();
+ stack.pop();
+ stack.push(EvalStackNode::Result(res));
+ reduced = true;
+ }
+ }
+ let n = stack.len();
+ if n > 2 {
+ // Take care to compute the reduction *before* popping the stack-- thank you, borrow
+ // checker!
+ let and_reduction = if let (
+ EvalStackNode::Op(EvalOp::And),
+ EvalStackNode::Result(r1),
+ EvalStackNode::Result(r2),
+ ) = (&stack[n - 3], &stack[n - 2], &stack[n - 1])
+ {
+ Some(r1.intersection(r2).cloned().collect())
+ } else {
+ None
+ };
+
+ if let Some(res) = and_reduction {
+ stack.pop();
+ stack.pop();
+ stack.pop();
+ stack.push(EvalStackNode::Result(res));
+ reduced = true;
+ }
+ }
+ let n = stack.len();
+ if n > 2 {
+ let or_reduction = if let (
+ EvalStackNode::Op(EvalOp::Or),
+ EvalStackNode::Result(r1),
+ EvalStackNode::Result(r2),
+ ) = (&stack[n - 3], &stack[n - 2], &stack[n - 1])
+ {
+ Some(r1.union(r2).cloned().collect())
+ } else {
+ None
+ };
+
+ if let Some(res) = or_reduction {
+ stack.pop();
+ stack.pop();
+ stack.pop();
+ stack.push(EvalStackNode::Result(res));
+ reduced = true;
+ }
+ }
+
+ if !reduced {
+ break;
+ }
+ }
+
+ Ok(())
+}
+
+/// Evaluate an abstract syntax tree (AST)
+pub async fn evaluate<'a>(
+ expr: &Expression,
+ case: bool,
+ client: &mut Client,
+ stickers: &FilterStickerNames<'a>,
+) -> Result<HashSet<String>> {
+ // We maintain *two* stacks, one for parsing & one for evaluation. Let sp (for "stack(parse)")
+ // be a stack of references to nodes in the parse tree.
+ let mut sp = Vec::new();
+ // Initialize it with the root; as we walk the tree, we'll pop the "most recent" node, and push
+ // children.
+ sp.push(expr);
+
+ // Let se (for "stack(eval)") be a stack of operators & URIs.
+ let mut se = Vec::new();
+
+ // Simple DFS traversal of the AST:
+ while let Some(node) = sp.pop() {
+ // and dispatch based on what we've got:
+ match node {
+ // 1. we have a simple term: this can be immediately resolved to a set of song URIs. Do
+ // so & push the resulting set onto the evaluation stack.
+ Expression::Simple(bt) => se.push(EvalStackNode::Result(
+ eval_term(bt, case, client, stickers).await?,
+ )),
+ // 2. we have a negation: push the "not" operator onto the evaluation stack & the child
+ // onto the parse stack.
+ Expression::Negation(be) => {
+ se.push(EvalStackNode::Op(EvalOp::Not));
+ sp.push(be);
+ }
+ // 3. conjunction-- push the "and" operator onto the evaluation stack & the children
+ // onto the parse stack (be sure to push the right-hand child first, so it will be
+ // popped second)
+ // bc is &Box<Conjunction<'a>>, so &**bc is &Conjunction<'a>
+ Expression::Conjunction(bc) => {
+ let mut conj = &**bc;
+ loop {
+ match conj {
+ Conjunction::Simple(bel, ber) => {
+ se.push(EvalStackNode::Op(EvalOp::And));
+ sp.push(&**ber);
+ sp.push(&**bel);
+ break;
+ }
+ Conjunction::Compound(bc, be) => {
+ se.push(EvalStackNode::Op(EvalOp::And));
+ sp.push(&**be);
+ conj = bc;
+ }
+ }
+ }
+ }
+ Expression::Disjunction(bt) => {
+ let mut disj = &**bt;
+ loop {
+ match disj {
+ Disjunction::Simple(bel, ber) => {
+ se.push(EvalStackNode::Op(EvalOp::Or));
+ sp.push(ber);
+ sp.push(bel);
+ break;
+ }
+ Disjunction::Compound(bd, be) => {
+ se.push(EvalStackNode::Op(EvalOp::Or));
+ sp.push(&**be);
+ disj = bd;
+ }
+ }
+ }
+ }
+ }
+
+ reduce(&mut se, client).await?;
+ }
+
+ // At this point, sp is empty, but there had better be something on se. Keep reducing the stack
+ // until either we can't any further (in which case we error) or there is only one element left
+ // (in which case we return that).
+ reduce(&mut se, client).await?;
+
+ // Now, se had better have one element, and that element had better be a Result.
+ if 1 != se.len() {
+ debug!("Too many ({}) operands left on stack:", se.len());
+ se.iter()
+ .enumerate()
+ .for_each(|(i, x)| debug!(" {}: {:#?}", i, x));
+ bail!("The number of operants is too big `{}`", se.len());
+ }
+
+ let ret = se.pop().unwrap();
+ match ret {
+ EvalStackNode::Result(result) => Ok(result),
+ EvalStackNode::Op(op) => {
+ debug!("Operator left on stack (!?): {:#?}", op);
+ bail!("Operator left on stack: {op}")
+ }
+ }
+}
+
+#[cfg(test)]
+mod evaluation_tests {
+
+ use super::*;
+ use crate::filters::*;
+
+ use crate::clients::Client;
+ use crate::clients::test_mock::Mock;
+
+ #[tokio::test]
+ async fn smoke() {
+ let mock = Box::new(Mock::new(&[(
+ r#"find "(base \"foo\")""#,
+ "file: foo/a.mp3
+Artist: The Foobars
+file: foo/b.mp3
+Title: b!
+OK",
+ )]));
+ let mut cli = Client::new(mock).unwrap();
+
+ let stickers = FilterStickerNames::new();
+
+ let expr = ExpressionParser::new().parse(r#"(base "foo")"#).unwrap();
+ let result = evaluate(&expr, true, &mut cli, &stickers).await;
+ assert!(result.is_ok());
+
+ let g: HashSet<String> = ["foo/a.mp3", "foo/b.mp3"]
+ .iter()
+ .map(|x| x.to_string())
+ .collect();
+ assert!(result.unwrap() == g);
+ }
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/lib.rs b/pkgs/by-name/mp/mpdpopm/src/lib.rs
new file mode 100644
index 00000000..6d04a527
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/lib.rs
@@ -0,0 +1,185 @@
+// 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/>.
+
+//! # mpdpopm
+//!
+//! Maintain ratings & playcounts for your mpd server.
+//!
+//! # Introduction
+//!
+//! This is a companion daemon for [mpd](https://www.musicpd.org/) that maintains play counts &
+//! ratings. Similar to [mpdfav](https://github.com/vincent-petithory/mpdfav), but written in Rust
+//! (which I prefer to Go), it will allow you to maintain that information in your tags, as well as
+//! the sticker database, by invoking external commands to keep your tags up-to-date (something
+//! along the lines of [mpdcron](https://alip.github.io/mpdcron)).
+//!
+//! # Commands
+//!
+//! I'm currently sending all commands over one (configurable) channel.
+//!
+
+#![recursion_limit = "512"] // for the `select!' macro
+
+pub mod clients;
+pub mod config;
+pub mod dj;
+pub mod filters_ast;
+pub mod messanges;
+pub mod playcounts;
+pub mod storage;
+pub mod vars;
+
+#[rustfmt::skip]
+#[allow(clippy::extra_unused_lifetimes)]
+#[allow(clippy::needless_lifetimes)]
+#[allow(clippy::let_unit_value)]
+#[allow(clippy::just_underscores_and_digits)]
+pub mod filters {
+ include!(concat!(env!("OUT_DIR"), "/filters.rs"));
+}
+
+use crate::{
+ clients::{Client, IdleClient, IdleSubSystem},
+ config::{Config, Connection},
+ messanges::{COMMAND_CHANNEL, MessageQueue},
+ playcounts::PlayState,
+};
+
+use anyhow::{Context, Error};
+use futures::{future::FutureExt, pin_mut, select};
+use tokio::{
+ signal,
+ signal::unix::{SignalKind, signal},
+ time::{Duration, sleep},
+};
+use tracing::{debug, error, info};
+
+/// Core `mppopmd' logic
+pub async fn mpdpopm(cfg: Config) -> std::result::Result<(), Error> {
+ info!("mpdpopm {} beginning.", vars::VERSION);
+
+ let mut client =
+ match cfg.conn {
+ Connection::Local { ref path } => Client::open(path)
+ .await
+ .with_context(|| format!("Failed to open socket at `{}`", path.display()))?,
+ Connection::TCP { ref host, port } => Client::connect(format!("{}:{}", host, port))
+ .await
+ .with_context(|| format!("Failed to connect to client at `{}:{}`", host, port))?,
+ };
+
+ let mut state = PlayState::new(&mut client, cfg.played_thresh)
+ .await
+ .context("Failed to construct PlayState")?;
+
+ let mut idle_client = match cfg.conn {
+ Connection::Local { ref path } => IdleClient::open(path)
+ .await
+ .context("Failed to open idle client")?,
+ Connection::TCP { ref host, port } => IdleClient::connect(format!("{}:{}", host, port))
+ .await
+ .context("Failed to connect to TCP idle client")?,
+ };
+
+ let mut mqueue = MessageQueue::new(cfg.mode);
+
+ idle_client
+ .subscribe(COMMAND_CHANNEL)
+ .await
+ .context("Failed to subscribe to idle_client")?;
+
+ let mut hup = signal(SignalKind::hangup()).unwrap();
+ let mut kill = signal(SignalKind::terminate()).unwrap();
+ let ctrl_c = signal::ctrl_c().fuse();
+
+ let sighup = hup.recv().fuse();
+ let sigkill = kill.recv().fuse();
+
+ let tick = sleep(Duration::from_millis(cfg.poll_interval_ms)).fuse();
+ pin_mut!(ctrl_c, sighup, sigkill, tick);
+
+ let mut done = false;
+ let mut msg_check_needed = false;
+ while !done {
+ debug!("selecting...");
+ {
+ // `idle_client' mutably borrowed here
+ let mut idle = Box::pin(idle_client.idle().fuse());
+ loop {
+ select! {
+ _ = ctrl_c => {
+ info!("got ctrl-C");
+ done = true;
+ break;
+ },
+ _ = sighup => {
+ info!("got SIGHUP");
+ done = true;
+ break;
+ },
+ _ = sigkill => {
+ info!("got SIGKILL");
+ done = true;
+ break;
+ },
+ _ = tick => {
+ tick.set(sleep(Duration::from_millis(cfg.poll_interval_ms)).fuse());
+ state.update(&mut client)
+ .await
+ .context("PlayState update failed")?;
+ },
+ res = idle => match res {
+ Ok(subsys) => {
+ debug!("subsystem {} changed", subsys);
+ if subsys == IdleSubSystem::Player {
+ if state.update(&mut client)
+ .await
+ .context("PlayState update failed")? {
+ mqueue
+ .advance_dj(&mut client)
+ .await
+ .context("MessageQueue tick failed")?;
+ }
+ } else if subsys == IdleSubSystem::Message {
+ msg_check_needed = true;
+ }
+ break;
+ },
+ Err(err) => {
+ debug!("error {err:#?} on idle");
+ done = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ 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) = mqueue.check_messages(&mut client, &mut idle_client).await {
+ error!("Error while processing messages: {err:#?}");
+ }
+ }
+ }
+
+ info!("mpdpopm exiting.");
+
+ Ok(())
+}
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
new file mode 100644
index 00000000..8fbee133
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/playcounts.rs
@@ -0,0 +1,342 @@
+// 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/>.
+
+//! playcounts -- managing play counts & lastplayed times
+//!
+//! # Introduction
+//!
+//! Play counts & last played timestamps are maintained so long as [PlayState::update] is called
+//! regularly (every few seconds, say). For purposes of library maintenance, however, they can be
+//! set explicitly:
+//!
+//! - `setpc PLAYCOUNT( TRACK)?`
+//! - `setlp LASTPLAYED( TRACK)?`
+//!
+
+use crate::clients::{Client, PlayerStatus};
+use crate::storage::{last_played, play_count, skip_count};
+
+use anyhow::{Context, Error, Result, anyhow};
+use tracing::{debug, info};
+
+use std::time::SystemTime;
+
+/// Current server state in terms of the play status (stopped/paused/playing, current track, elapsed
+/// time in current track, &c)
+#[derive(Debug)]
+pub struct PlayState {
+ /// Last known server status
+ last_server_stat: PlayerStatus,
+
+ /// true if we have already incremented the last known track's playcount
+ have_incr_play_count: bool,
+
+ /// Percentage threshold, expressed as a number between zero & one, for considering a song to
+ /// have been played
+ played_thresh: f64,
+ last_song_was_skipped: bool,
+}
+
+impl PlayState {
+ /// Create a new PlayState instance; async because it will reach out to the mpd server
+ /// to get current status.
+ pub async fn new(
+ client: &mut Client,
+ played_thresh: f64,
+ ) -> std::result::Result<PlayState, Error> {
+ Ok(PlayState {
+ last_server_stat: client.status().await?,
+ have_incr_play_count: false,
+ last_song_was_skipped: false,
+ played_thresh,
+ })
+ }
+
+ /// Retrieve a copy of the last known player status
+ pub fn last_status(&self) -> PlayerStatus {
+ self.last_server_stat.clone()
+ }
+
+ /// 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
+ ///
+ /// 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")?;
+
+ 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))
+ | (PlayerStatus::Pause(last), PlayerStatus::Pause(curr)) => {
+ // Last we knew, we were playing, and we're playing now.
+ if last.songid != curr.songid {
+ debug!("New songid-- resetting PC incremented flag.");
+
+ if !self.have_incr_play_count {
+ // We didn't mark the previous song as played.
+ // As such, the user must have skipped it :(
+ self.last_song_was_skipped = true;
+ }
+
+ 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(_)) => {
+ 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) => {
+ // 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 {:.2}% played.",
+ curr.file.display(),
+ curr.songid,
+ (curr.elapsed / curr.duration) * 100.0
+ );
+
+ let file = curr.file.to_str().ok_or_else(|| {
+ anyhow!("Failed to parse path as utf8: `{}`", curr.file.display())
+ })?;
+
+ let curr_pc = play_count::get(client, file).await?.unwrap_or_default();
+
+ debug!("Current PC is {}.", curr_pc);
+
+ last_played::set(
+ client,
+ file,
+ SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .context("Failed to get system time")?
+ .as_secs(),
+ )
+ .await?;
+ self.have_incr_play_count = true;
+
+ play_count::set(client, file, curr_pc + 1).await?;
+ } else if self.last_song_was_skipped {
+ self.last_song_was_skipped = false;
+ let last = self
+ .last_server_stat
+ .current_song()
+ .expect("To exist, as it was skipped");
+
+ info!(
+ "Marking '{}' (songid: {}) as skipped at {:.2}%.",
+ last.file.display(),
+ last.songid,
+ (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 = 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(previous_song_finished)
+ }
+}
+
+#[cfg(test)]
+mod player_state_tests {
+ use super::*;
+ use crate::clients::test_mock::Mock;
+
+ /// "Smoke" tests for player state
+ #[tokio::test]
+ async fn player_state_smoke() {
+ let mock = Box::new(Mock::new(&[
+ (
+ "status",
+ "repeat: 0
+random: 1
+single: 0
+consume: 1
+playlist: 2
+playlistlength: 66
+mixrampdb: 0.000000
+state: stop
+xfade: 5
+song: 51
+songid: 52
+nextsong: 11
+nextsongid: 12
+OK
+",
+ ),
+ (
+ "status",
+ "volume: 100
+repeat: 0
+random: 1
+single: 0
+consume: 1
+playlist: 2
+playlistlength: 66
+mixrampdb: 0.000000
+state: play
+xfade: 5
+song: 51
+songid: 52
+time: 5:228
+elapsed: 5.337
+bitrate: 192
+duration: 227.637
+audio: 44100:24:2
+nextsong: 11
+nextsongid: 12
+OK
+",
+ ),
+ (
+ "playlistid 52",
+ "file: E/Enya - Wild Child.mp3
+Last-Modified: 2008-11-09T00:06:30Z
+Artist: Enya
+Title: Wild Child
+Album: A Day Without Rain (Japanese Retail)
+Date: 2000
+Genre: Celtic
+Time: 228
+duration: 227.637
+Pos: 51
+Id: 52
+OK
+",
+ ),
+ (
+ "status",
+ "volume: 100
+repeat: 0
+random: 1
+single: 0
+consume: 1
+playlist: 2
+playlistlength: 66
+mixrampdb: 0.000000
+state: play
+xfade: 5
+song: 51
+songid: 52
+time: 5:228
+elapsed: 200
+bitrate: 192
+duration: 227.637
+audio: 44100:24:2
+nextsong: 11
+nextsongid: 12
+OK
+",
+ ),
+ (
+ "playlistid 52",
+ "file: E/Enya - Wild Child.mp3
+Last-Modified: 2008-11-09T00:06:30Z
+Artist: Enya
+Title: Wild Child
+Album: A Day Without Rain (Japanese Retail)
+Date: 2000
+Genre: Celtic
+Time: 228
+duration: 227.637
+Pos: 51
+Id: 52
+OK
+",
+ ),
+ (
+ "sticker get song \"E/Enya - Wild Child.mp3\" unwoundstack.com:playcount",
+ "sticker: unwoundstack.com:playcount=11\nOK\n",
+ ),
+ (
+ &format!(
+ "sticker set song \"E/Enya - Wild Child.mp3\" unwoundstack.com:lastplayed {}",
+ SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .unwrap()
+ .as_secs()
+ ),
+ "OK\n",
+ ),
+ (
+ "sticker set song \"E/Enya - Wild Child.mp3\" unwoundstack.com:playcount 12",
+ "OK\n",
+ ),
+ ]));
+
+ let mut cli = Client::new(mock).unwrap();
+ let mut ps = PlayState::new(&mut cli, 0.6).await.unwrap();
+ let check = match ps.last_status() {
+ PlayerStatus::Play(_) | PlayerStatus::Pause(_) => false,
+ PlayerStatus::Stopped => true,
+ };
+ assert!(check);
+
+ 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
new file mode 100644
index 00000000..a6f20d5b
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/storage/mod.rs
@@ -0,0 +1,145 @@
+use anyhow::Result;
+
+pub mod play_count {
+ use anyhow::Context;
+
+ use crate::clients::Client;
+
+ use super::Result;
+
+ pub const STICKER: &str = "unwoundstack.com:playcount";
+
+ /// Retrieve the play count for a track
+ pub async fn get(client: &mut Client, file: &str) -> Result<Option<usize>> {
+ match client
+ .get_sticker::<usize>(file, STICKER)
+ .await
+ .context("Failed to get sticker from client")?
+ {
+ Some(n) => Ok(Some(n)),
+ None => Ok(None),
+ }
+ }
+
+ /// Set the play count for a track-- this will run the associated command, if any
+ pub async fn set(client: &mut Client, file: &str, play_count: usize) -> Result<()> {
+ client
+ .set_sticker(file, STICKER, &format!("{}", play_count))
+ .await
+ .context("Failed to set_sticker on client")?;
+
+ Ok(())
+ }
+
+ #[cfg(test)]
+ mod pc_lp_tests {
+ use super::*;
+ use crate::{clients::test_mock::Mock, storage::play_count};
+
+ /// "Smoke" tests for play counts & last played times
+ #[tokio::test]
+ async fn pc_smoke() {
+ let mock = Box::new(Mock::new(&[
+ (
+ "sticker get song a unwoundstack.com:playcount",
+ "sticker: unwoundstack.com:playcount=11\nOK\n",
+ ),
+ (
+ "sticker get song a unwoundstack.com:playcount",
+ "ACK [50@0] {sticker} no such sticker\n",
+ ),
+ ("sticker get song a unwoundstack.com:playcount", "splat!"),
+ ]));
+ let mut cli = Client::new(mock).unwrap();
+
+ assert_eq!(play_count::get(&mut cli, "a").await.unwrap().unwrap(), 11);
+ let val = play_count::get(&mut cli, "a").await.unwrap();
+ assert!(val.is_none());
+ play_count::get(&mut cli, "a").await.unwrap_err();
+ }
+ }
+}
+
+pub mod skip_count {
+ use anyhow::Context;
+
+ use crate::clients::Client;
+
+ use super::Result;
+
+ 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>> {
+ match client
+ .get_sticker::<usize>(file, STICKER)
+ .await
+ .context("Failed to get_sticker on client")?
+ {
+ Some(n) => Ok(Some(n)),
+ None => Ok(None),
+ }
+ }
+
+ /// Set the skip count for a track
+ pub async fn set(client: &mut Client, file: &str, skip_count: usize) -> Result<()> {
+ client
+ .set_sticker(file, STICKER, &format!("{}", skip_count))
+ .await
+ .context("Failed to set_sticker on client")
+ }
+}
+
+pub mod last_played {
+ use anyhow::Context;
+
+ use crate::clients::Client;
+
+ use super::Result;
+
+ pub const STICKER: &str = "unwoundstack.com:lastplayed";
+
+ /// Retrieve the last played timestamp for a track (seconds since Unix epoch)
+ pub async fn get(client: &mut Client, file: &str) -> Result<Option<u64>> {
+ client
+ .get_sticker::<u64>(file, STICKER)
+ .await
+ .context("Falied to get_sticker on client")
+ }
+
+ /// Set the last played for a track
+ pub async fn set(client: &mut Client, file: &str, last_played: u64) -> Result<()> {
+ client
+ .set_sticker(file, STICKER, &format!("{}", last_played))
+ .await
+ .context("Failed to set_sticker on client")?;
+ Ok(())
+ }
+}
+
+pub mod rating {
+ use anyhow::Context;
+
+ use crate::clients::Client;
+
+ use super::Result;
+
+ pub const STICKER: &str = "unwoundstack.com:ratings_count";
+
+ /// Retrieve the rating count for a track
+ pub async fn get(client: &mut Client, file: &str) -> Result<Option<i8>> {
+ client
+ .get_sticker::<i8>(file, STICKER)
+ .await
+ .context("Failed to get_sticker on client")
+ }
+
+ /// Set the rating count for a track
+ pub async fn set(client: &mut Client, file: &str, rating_count: i8) -> Result<()> {
+ client
+ .set_sticker(file, STICKER, &format!("{}", rating_count))
+ .await
+ .context("Failed to set_sticker on client")?;
+ Ok(())
+ }
+}
diff --git a/pkgs/by-name/mp/mpdpopm/src/vars.rs b/pkgs/by-name/mp/mpdpopm/src/vars.rs
new file mode 100644
index 00000000..7cacec66
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/src/vars.rs
@@ -0,0 +1,4 @@
+pub static VERSION: &str = env!("CARGO_PKG_VERSION");
+pub static AUTHOR: &str = env!("CARGO_PKG_AUTHORS");
+pub static LOCALSTATEDIR: &str = "/home/soispha/.local/state";
+pub static PREFIX: &str = "/home/soispha/.local/share/mpdpopm";
diff --git a/pkgs/by-name/mp/mpdpopm/update.sh b/pkgs/by-name/mp/mpdpopm/update.sh
new file mode 100755
index 00000000..5aedf3b3
--- /dev/null
+++ b/pkgs/by-name/mp/mpdpopm/update.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env sh
+
+# 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>.
+
+[ "$1" = "upgrade" ] && cargo upgrade --incompatible allow --pinned allow --recursive true
+cargo update --recursive
diff --git a/pkgs/by-name/mp/mpp-lyrics/mpp-lyrics.sh b/pkgs/by-name/mp/mpp-lyrics/mpp-lyrics.sh
index fa1cac49..6c96a9d9 100755
--- a/pkgs/by-name/mp/mpp-lyrics/mpp-lyrics.sh
+++ b/pkgs/by-name/mp/mpp-lyrics/mpp-lyrics.sh
@@ -16,6 +16,6 @@ die() {
}
cd "$XDG_MUSIC_DIR/beets" || die "No music dir!"
-exiftool "$(mpc --format '%file%' current)" -json | jq '.[0].Lyrics' --raw-output | less
+exiftool "$(mpc --format '%file%' current)" -json | jq '.[0] | if has("Lyrics") then .Lyrics elif has("Lyrics-xxx") then ."Lyrics-xxx" else "<No lyrics key>" end' --raw-output | less
# vim: ft=sh
diff --git a/pkgs/by-name/mp/mpp-searchadd/mpp-searchadd.sh b/pkgs/by-name/mp/mpp-searchadd/mpp-searchadd.sh
index d76e73b8..89909ed9 100755
--- a/pkgs/by-name/mp/mpp-searchadd/mpp-searchadd.sh
+++ b/pkgs/by-name/mp/mpp-searchadd/mpp-searchadd.sh
@@ -10,6 +10,11 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+# Run `beets` once, to trigger eventual beets migrations.
+# Otherwise, these might screw with our file adding (because `beets` prints them on stdout,
+# for some reason !?)
+beet --version >/dev/null
+
beet list "$@" --path | while read -r track; do
mpc add "$track"
done
diff --git a/pkgs/by-name/mp/mpp/mpp.sh b/pkgs/by-name/mp/mpp/mpp.sh
index 3ac98ddb..e52bfcdc 100755
--- a/pkgs/by-name/mp/mpp/mpp.sh
+++ b/pkgs/by-name/mp/mpp/mpp.sh
@@ -23,6 +23,10 @@ case "${1-}" in
shift 1
mpp-beetrm "$@"
;;
+"popm")
+ shift 1
+ mpdpopm "$@"
+ ;;
*)
mpc "$@"
;;
diff --git a/pkgs/by-name/mp/mpp/package.nix b/pkgs/by-name/mp/mpp/package.nix
index f0e454f7..fe08ba46 100644
--- a/pkgs/by-name/mp/mpp/package.nix
+++ b/pkgs/by-name/mp/mpp/package.nix
@@ -16,6 +16,7 @@
mpp-searchadd,
mpp-lyrics,
mpp-beetrm,
+ mpdpopm,
# Build dependencies
fd,
zsh,
@@ -30,6 +31,7 @@
mpp-searchadd
mpp-lyrics
mpp-beetrm
+ mpdpopm
];
};
diff --git a/pkgs/by-name/no/notify-run/Cargo.lock b/pkgs/by-name/no/notify-run/Cargo.lock
index 1e065d25..c1ff746e 100644
--- a/pkgs/by-name/no/notify-run/Cargo.lock
+++ b/pkgs/by-name/no/notify-run/Cargo.lock
@@ -13,9 +13,9 @@ version = 4
[[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 = "notify-run"
diff --git a/pkgs/by-name/no/notify-run/Cargo.toml b/pkgs/by-name/no/notify-run/Cargo.toml
index c4b9a659..ceff208e 100644
--- a/pkgs/by-name/no/notify-run/Cargo.toml
+++ b/pkgs/by-name/no/notify-run/Cargo.toml
@@ -17,4 +17,4 @@ edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-anyhow = "1.0.100"
+anyhow = "1.0.104"
diff --git a/pkgs/by-name/no/notify-run/flake.lock b/pkgs/by-name/no/notify-run/flake.lock
deleted file mode 100644
index 1e997998..00000000
--- a/pkgs/by-name/no/notify-run/flake.lock
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "nodes": {
- "nixpkgs": {
- "locked": {
- "lastModified": 1768661221,
- "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "3327b113f2ef698d380df83fbccefad7e83d7769",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "nixpkgs": "nixpkgs"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/pkgs/by-name/no/notify-run/flake.lock.license b/pkgs/by-name/no/notify-run/flake.lock.license
deleted file mode 100644
index eae6a84c..00000000
--- a/pkgs/by-name/no/notify-run/flake.lock.license
+++ /dev/null
@@ -1,9 +0,0 @@
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/pkgs/by-name/no/notify-run/flake.nix b/pkgs/by-name/no/notify-run/flake.nix
index 07be3258..f8649e8f 100644
--- a/pkgs/by-name/no/notify-run/flake.nix
+++ b/pkgs/by-name/no/notify-run/flake.nix
@@ -11,12 +11,13 @@
description = "An safe way to run applications, that might fail";
inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
- outputs = {nixpkgs, ...}: let
+ outputs = {...}: let
system = "x86_64-linux";
- pkgs = nixpkgs.legacyPackages."${system}";
+ sources = import ../../../../unflake.nix;
+
+ pkgs = sources.nixpkgs.legacyPackages."${system}";
in {
devShells."${system}".default = pkgs.mkShell {
packages = [
diff --git a/pkgs/by-name/no/notify-run/src/main.rs b/pkgs/by-name/no/notify-run/src/main.rs
index a6a0165a..94f9ad4e 100644
--- a/pkgs/by-name/no/notify-run/src/main.rs
+++ b/pkgs/by-name/no/notify-run/src/main.rs
@@ -8,7 +8,13 @@
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-use std::{env::args, path::PathBuf, process::Command};
+use std::{
+ env::args,
+ io::{BufRead, BufReader, Read, Write},
+ path::PathBuf,
+ process::{Command, Stdio},
+ thread::JoinHandle,
+};
use anyhow::{Context, Result};
@@ -20,18 +26,40 @@ fn main() -> Result<()> {
cmd.args(arguments.split(" ").collect::<Vec<_>>().as_slice());
}
- eprintln!("Spawning {:?}", cmd);
+ eprintln!("notify-run> Spawning {:?}", cmd);
- let output = cmd
- .output()
- .with_context(|| format!("Failed to spawn and await output of {:?}", cmd))?;
+ let mut child = cmd
+ .stderr(Stdio::piped())
+ .stdout(Stdio::piped())
+ .spawn()
+ .with_context(|| format!("Failed to spawn {:?}", cmd))?;
- if !output.status.success() {
+ let (stdout, stderr) = (
+ child.stdout.take().expect("Was piped"),
+ child.stderr.take().expect("Was piped"),
+ );
+
+ let name = PathBuf::from(&args[0])
+ .file_name()
+ .expect("this to be a command, and thus have a file_name")
+ .to_string_lossy()
+ .to_string();
+
+ let stdout_t = write_thread(stdout, name.to_owned());
+ let stderr_t = write_thread(stderr, name.to_owned());
+ stdout_t
+ .join()
+ .unwrap()
+ .context("Failed to join stdout thread")?;
+ stderr_t
+ .join()
+ .unwrap()
+ .context("Failed to join stderr thread")?;
+
+ let status = child.wait().context("Failed to wait for child output")?;
+ if !status.success() {
let mut notify_send = Command::new("notify-send");
- notify_send.args([
- format!("Command {:?} failed", cmd).as_str(),
- &String::from_utf8_lossy(output.stderr.as_slice()),
- ]);
+ notify_send.args([format!("Command {:?} failed", cmd).as_str()]);
notify_send.status().with_context(|| {
format!(
@@ -39,27 +67,29 @@ fn main() -> Result<()> {
cmd
)
})?;
- } else {
- let name = PathBuf::from(&args[0])
- .file_name()
- .expect("this to be a command, and thus have a file_name")
- .to_string_lossy()
- .to_string();
-
- print!("{}", append_name(&name, &output.stdout));
- eprint!("{}", append_name(&name, &output.stderr));
}
Ok(())
}
-fn append_name(name: &str, base: &[u8]) -> String {
- let base = String::from_utf8_lossy(base).to_string();
+fn write_thread<R: Read + Send + 'static>(input: R, name: String) -> JoinHandle<Result<()>> {
+ std::thread::spawn(move || {
+ let mut reader = BufReader::new(input);
- let mut output = String::new();
- for line in base.lines() {
- output.push_str(format!("{name}> {line}\n").as_str());
- }
+ let mut buf = String::new();
+ loop {
+ buf.clear();
+ if reader
+ .read_line(&mut buf)
+ .context("Failed to read from child output")?
+ == 0
+ {
+ break;
+ }
+
+ write!(std::io::stdout(), "{name}> {buf}")?;
+ }
- output
+ Ok(())
+ })
}
diff --git a/pkgs/by-name/no/notify-run/update.sh b/pkgs/by-name/no/notify-run/update.sh
index 23d90a86..188771c4 100755
--- a/pkgs/by-name/no/notify-run/update.sh
+++ b/pkgs/by-name/no/notify-run/update.sh
@@ -10,5 +10,5 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-[ "$1" = "upgrade" ] && cargo upgrade
-cargo update
+[ "$1" = "upgrade" ] && cargo upgrade --incompatible allow --pinned allow --recursive true
+cargo update --recursive
diff --git a/pkgs/by-name/qu/qutebrowser-patched/package.nix b/pkgs/by-name/qu/qutebrowser-patched/package.nix
index 1f2ea889..bf4fab1d 100644
--- a/pkgs/by-name/qu/qutebrowser-patched/package.nix
+++ b/pkgs/by-name/qu/qutebrowser-patched/package.nix
@@ -1,6 +1,26 @@
-{qutebrowser}:
-qutebrowser.overrideAttrs (final: prev: {
- pname = "${prev.pname}-patched";
+{
+ qutebrowser,
+ python3,
+}: let
+ python3NoSpeech = python3.override {
+ packageOverrides = self: super: {
+ # NOTE: The re-compile is not worth the space saving <2026-07-01>
+ # pyqt6 = super.pyqt6.override {withSpeech = false;};
+ };
+ };
+ # NOTE: It's just not feasible to re-compile a webengine. <2026-05-29>
+ # qt6PackagesNoFfmpeg = qt6Packages.overrideScope (
+ # _: prev: {
+ # qtwebengine = prev.qtwebengine.override {
+ # ffmpeg = ffmpeg-headless;
+ # };
+ # }
+ # );
+in
+ (qutebrowser.overrideAttrs (final: prev: {
+ pname = "${prev.pname}-patched";
- patches = (prev.patches or []) ++ [./0001-fix-standardpaths-Continue-to-work-with-xdg-while-ba.patch];
-})
+ patches = (prev.patches or []) ++ [./0001-fix-standardpaths-Continue-to-work-with-xdg-while-ba.patch];
+ })).override {
+ python3 = python3NoSpeech;
+ }
diff --git a/pkgs/by-name/ri/river-mk-keymap/Cargo.lock b/pkgs/by-name/ri/river-mk-keymap/Cargo.lock
index ef1ffbf7..0f7bae8a 100644
--- a/pkgs/by-name/ri/river-mk-keymap/Cargo.lock
+++ b/pkgs/by-name/ri/river-mk-keymap/Cargo.lock
@@ -29,9 +29,9 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[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",
@@ -44,15 +44,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",
]
@@ -79,15 +79,15 @@ dependencies = [
[[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 = "arrayvec"
-version = "0.7.6"
+version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]]
name = "bitflags"
@@ -97,9 +97,9 @@ 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 = "byteorder"
@@ -109,9 +109,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
-version = "1.2.53"
+version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
+checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -125,9 +125,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[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",
@@ -135,9 +135,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",
@@ -147,9 +147,9 @@ 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",
@@ -159,15 +159,15 @@ dependencies = [
[[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 = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "core-foundation"
@@ -244,9 +244,9 @@ dependencies = [
[[package]]
name = "dlib"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a"
dependencies = [
"libloading",
]
@@ -281,9 +281,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
-version = "0.1.8"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
+checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]]
name = "float-ord"
@@ -297,7 +297,7 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags 2.13.1",
"byteorder",
"core-foundation",
"core-graphics",
@@ -328,9 +328,9 @@ dependencies = [
[[package]]
name = "foreign-types-macros"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
dependencies = [
"proc-macro2",
"quote",
@@ -379,9 +379,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[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 = "keymaps"
@@ -401,9 +401,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
-version = "0.2.180"
+version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libloading"
@@ -417,46 +417,45 @@ dependencies = [
[[package]]
name = "libredox"
-version = "0.1.12"
+version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
+checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
dependencies = [
- "bitflags 2.10.0",
"libc",
]
[[package]]
name = "linux-raw-sys"
-version = "0.11.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "log"
-version = "0.4.29"
+version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[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 = "memmap2"
-version = "0.9.9"
+version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490"
+checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
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"
@@ -491,42 +490,42 @@ dependencies = [
[[package]]
name = "pathfinder_simd"
-version = "0.5.5"
+version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf9027960355bf3afff9841918474a81a5f972ac6d226d518060bba758b5ad57"
+checksum = "4500030c302e4af1d423f36f3b958d1aecb6c04184356ed5a833bf6b60435777"
dependencies = [
"rustc_version",
]
[[package]]
name = "pkg-config"
-version = "0.3.32"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "proc-macro2"
-version = "1.0.105"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
-version = "0.38.4"
+version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
-version = "1.0.43"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
@@ -574,11 +573,11 @@ dependencies = [
[[package]]
name = "rustix"
-version = "1.1.3"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags 2.13.1",
"errno",
"libc",
"linux-raw-sys",
@@ -596,15 +595,15 @@ dependencies = [
[[package]]
name = "semver"
-version = "1.0.27"
+version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
+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",
@@ -612,18 +611,18 @@ 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",
@@ -632,9 +631,9 @@ dependencies = [
[[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",
@@ -645,15 +644,15 @@ 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 = "smallvec"
-version = "1.15.1"
+version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "strsim"
@@ -663,9 +662,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
-version = "2.0.114"
+version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
@@ -674,18 +673,18 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.17"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.17"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
@@ -700,9 +699,9 @@ checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[package]]
name = "unicode-ident"
-version = "1.0.22"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "utf8parse"
@@ -738,9 +737,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wayland-backend"
-version = "0.3.12"
+version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fee64194ccd96bf648f42a65a7e589547096dfa702f7cadef84347b66ad164f9"
+checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078"
dependencies = [
"cc",
"downcast-rs",
@@ -751,11 +750,11 @@ dependencies = [
[[package]]
name = "wayland-client"
-version = "0.31.12"
+version = "0.31.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e6faa537fbb6c186cb9f1d41f2f811a4120d1b57ec61f50da451a0c5122bec"
+checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags 2.13.1",
"rustix",
"wayland-backend",
"wayland-scanner",
@@ -763,11 +762,11 @@ dependencies = [
[[package]]
name = "wayland-protocols"
-version = "0.32.10"
+version = "0.32.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baeda9ffbcfc8cd6ddaade385eaf2393bd2115a69523c735f12242353c3df4f3"
+checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags 2.13.1",
"wayland-backend",
"wayland-client",
"wayland-scanner",
@@ -775,11 +774,11 @@ dependencies = [
[[package]]
name = "wayland-protocols-wlr"
-version = "0.3.10"
+version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9597cdf02cf0c34cd5823786dce6b5ae8598f05c2daf5621b6e178d4f7345f3"
+checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
dependencies = [
- "bitflags 2.10.0",
+ "bitflags 2.13.1",
"wayland-backend",
"wayland-client",
"wayland-protocols",
@@ -788,9 +787,9 @@ dependencies = [
[[package]]
name = "wayland-scanner"
-version = "0.31.8"
+version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5423e94b6a63e68e439803a3e153a9252d5ead12fd853334e2ad33997e3889e3"
+checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
dependencies = [
"proc-macro2",
"quick-xml",
@@ -799,9 +798,9 @@ dependencies = [
[[package]]
name = "wayland-sys"
-version = "0.31.8"
+version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd"
+checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
dependencies = [
"pkg-config",
]
@@ -863,9 +862,9 @@ dependencies = [
[[package]]
name = "yeslogic-fontconfig-sys"
-version = "6.0.0"
+version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd"
+checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76"
dependencies = [
"dlib",
"once_cell",
@@ -874,6 +873,6 @@ dependencies = [
[[package]]
name = "zmij"
-version = "1.0.15"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/pkgs/by-name/ri/river-mk-keymap/Cargo.toml b/pkgs/by-name/ri/river-mk-keymap/Cargo.toml
index 31247cd7..fba97053 100644
--- a/pkgs/by-name/ri/river-mk-keymap/Cargo.toml
+++ b/pkgs/by-name/ri/river-mk-keymap/Cargo.toml
@@ -17,20 +17,24 @@ edition = "2021"
[dependencies]
ab_glyph = "0.2.32"
-anyhow = "1.0.100"
-clap = { version = "4.5.54", features = ["derive"] }
+anyhow = "1.0.104"
+clap = { version = "4.6.6", features = ["derive"] }
font-kit = "0.14.3"
-keymaps = { version = "1.2.0", features = ["serde", "mouse-keys", "modifier-keys"] }
-memmap2 = "0.9.9"
-rustix = { version = "1.1.3", features = ["fs", "shm"] }
-serde = { version = "1.0.228", features = ["derive"] }
-serde_json = "1.0.149"
-shlex = "1.3.0"
-thiserror = "2.0.17"
+keymaps = { version = "1.2.0", features = [
+ "serde",
+ "mouse-keys",
+ "modifier-keys",
+] }
+memmap2 = "0.9.11"
+rustix = { version = "1.1.4", features = ["fs", "shm"] }
+serde = { version = "1.0.229", features = ["derive"] }
+serde_json = "1.0.151"
+shlex = "2.0.1"
+thiserror = "2.0.20"
vte = "0.15.0"
-wayland-client = {version = "0.31.12", default-features = false}
-wayland-protocols-wlr = { version = "0.3.10", features = ["client"] }
-wayland-scanner = {version = "0.31.8", default-features = false}
+wayland-client = { version = "0.31.15", default-features = false }
+wayland-protocols-wlr = { version = "0.3.12", features = ["client"] }
+wayland-scanner = { version = "0.31.11", default-features = false }
[profile.release]
lto = true
diff --git a/pkgs/by-name/ri/river-mk-keymap/flake.lock b/pkgs/by-name/ri/river-mk-keymap/flake.lock
deleted file mode 100644
index 1e997998..00000000
--- a/pkgs/by-name/ri/river-mk-keymap/flake.lock
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "nodes": {
- "nixpkgs": {
- "locked": {
- "lastModified": 1768661221,
- "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "3327b113f2ef698d380df83fbccefad7e83d7769",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "nixpkgs": "nixpkgs"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/pkgs/by-name/ri/river-mk-keymap/flake.lock.license b/pkgs/by-name/ri/river-mk-keymap/flake.lock.license
deleted file mode 100644
index eae6a84c..00000000
--- a/pkgs/by-name/ri/river-mk-keymap/flake.lock.license
+++ /dev/null
@@ -1,9 +0,0 @@
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/pkgs/by-name/ri/river-mk-keymap/flake.nix b/pkgs/by-name/ri/river-mk-keymap/flake.nix
index b338e4c9..6cf70c97 100644
--- a/pkgs/by-name/ri/river-mk-keymap/flake.nix
+++ b/pkgs/by-name/ri/river-mk-keymap/flake.nix
@@ -11,12 +11,13 @@
description = "A smart way to configure river keybindings";
inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
- outputs = {nixpkgs, ...}: let
+ outputs = {...}: let
system = "x86_64-linux";
- pkgs = nixpkgs.legacyPackages."${system}";
+
+ sources = import ../../../../unflake.nix;
+ pkgs = sources.nixpkgs.legacyPackages."${system}";
nativeBuildInputs = [
pkgs.pkg-config
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs b/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs
index 8372b61d..ebae7939 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/key_map/commands.rs
@@ -10,7 +10,7 @@
use std::{env::current_exe, path::Path, process::Command};
-use anyhow::{bail, Result};
+use anyhow::{Result, bail};
use keymaps::key_repr::{Key, KeyValue, Keys, MediaKeyCode, ModifierKeyCode, MouseKeyValue};
use rustix::path::Arg;
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs
index 16dc02f4..106ae9bd 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/key_map/mod.rs
@@ -10,7 +10,7 @@
use std::{fmt::Display, ops::Deref, str::FromStr};
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{Context, Result, anyhow, bail};
use keymaps::{
key_repr::{Key, Keys},
map_tree::MapTrie,
@@ -98,8 +98,10 @@ impl FromStr for KeyMap {
for (key, value) in raw {
decode_value(
&mut out,
- vec![Key::from_str(&key)
- .with_context(|| format!("Failed to parse key ('{key}')"))?],
+ vec![
+ Key::from_str(&key)
+ .with_context(|| format!("Failed to parse key ('{key}')"))?,
+ ],
&value,
)?;
}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs
index c6e04fdf..5bdf9d3d 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/dispatches.rs
@@ -2,12 +2,12 @@ use std::num::NonZero;
use keymaps::key_repr::Key;
use wayland_client::{
+ Connection, Dispatch, QueueHandle,
globals::GlobalListContents,
protocol::{
wl_buffer::WlBuffer, wl_compositor::WlCompositor, wl_registry, wl_seat::WlSeat,
wl_shm::WlShm, wl_shm_pool::WlShmPool, wl_surface::WlSurface,
},
- Connection, Dispatch, QueueHandle,
};
use wayland_protocols_wlr::layer_shell::v1::client::{
@@ -16,12 +16,11 @@ use wayland_protocols_wlr::layer_shell::v1::client::{
};
use crate::wayland::{
- ansi, render,
+ AppData, ansi, render,
river::protocols::river_protocols::{
zriver_seat_status_v1::{self, ZriverSeatStatusV1},
zriver_status_manager_v1::ZriverStatusManagerV1,
},
- AppData,
};
impl Dispatch<ZriverSeatStatusV1, ()> for AppData {
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs
index 44c010d5..2b37e9b5 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/mod.rs
@@ -7,6 +7,7 @@
use anyhow::Result;
use wayland_client::{
+ Connection,
globals::registry_queue_init,
protocol::{
wl_compositor::WlCompositor,
@@ -14,7 +15,6 @@ use wayland_client::{
wl_shm::{self, WlShm},
wl_surface::WlSurface,
},
- Connection,
};
use wayland_protocols_wlr::layer_shell::v1::client::{
zwlr_layer_shell_v1::{self, ZwlrLayerShellV1},
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs
index 7f0aaec9..032e98a4 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/layout.rs
@@ -1,4 +1,4 @@
-use ab_glyph::{point, Font, Glyph, Point, ScaleFont};
+use ab_glyph::{Font, Glyph, Point, ScaleFont, point};
use crate::wayland::ansi::{StyledChar, StyledString};
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs
index e92def3c..fa44f602 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/render/mod.rs
@@ -1,6 +1,6 @@
use std::{fs::File, io::Read};
-use ab_glyph::{point, Font, FontVec, PxScale, ScaleFont};
+use ab_glyph::{Font, FontVec, PxScale, ScaleFont, point};
use anyhow::{Context, Result};
use font_kit::{
family_name::FamilyName, handle::Handle, properties::Properties, source::SystemSource,
@@ -76,11 +76,7 @@ pub(super) fn text(input: &StyledString) -> Result<(ColorVec, (u32, u32))> {
scaled_font.h_advance(g.id)
};
- if next > acc {
- next
- } else {
- acc
- }
+ if next > acc { next } else { acc }
})
.ceil() as usize;
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs
index e54b65e1..29f5aabe 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/river/protocols.rs
@@ -12,7 +12,7 @@ pub(crate) mod river_protocols {
pub(crate) mod __status {
use wayland_client::backend as wayland_backend;
use wayland_client::protocol::__interfaces::{
- wl_output_interface, wl_seat_interface, WL_OUTPUT_INTERFACE, WL_SEAT_INTERFACE,
+ WL_OUTPUT_INTERFACE, WL_SEAT_INTERFACE, wl_output_interface, wl_seat_interface,
};
wayland_scanner::generate_interfaces!("./resources/river-status-unstable-v1.xml");
}
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs
index 0b1fdc1b..db0e06c1 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/multi.rs
@@ -68,14 +68,14 @@ use std::io;
use std::os::unix::io::OwnedFd;
use std::sync::{
- atomic::{AtomicBool, Ordering},
Arc,
+ atomic::{AtomicBool, Ordering},
};
use wayland_client::backend::protocol::Message;
use wayland_client::backend::{Backend, ObjectData, ObjectId};
use wayland_client::{
- protocol::{wl_buffer, wl_shm},
Proxy,
+ protocol::{wl_buffer, wl_shm},
};
use crate::wayland::shm::CreatePoolError;
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs
index a12afaa0..3f4416de 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/raw.rs
@@ -18,9 +18,9 @@ use std::{
use memmap2::MmapMut;
use wayland_client::{
+ Dispatch, Proxy, QueueHandle, WEnum,
backend::ObjectData,
protocol::{wl_buffer, wl_shm, wl_shm_pool},
- Dispatch, Proxy, QueueHandle, WEnum,
};
use super::CreatePoolError;
diff --git a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs
index ab52c5f6..1f70c0e9 100644
--- a/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs
+++ b/pkgs/by-name/ri/river-mk-keymap/src/wayland/shm/slot.rs
@@ -4,20 +4,20 @@ use std::io;
use std::{
os::unix::io::{AsRawFd, OwnedFd},
sync::{
- atomic::{AtomicU8, AtomicUsize, Ordering},
Arc, Mutex, Weak,
+ atomic::{AtomicU8, AtomicUsize, Ordering},
},
};
use wayland_client::backend::protocol::Message;
use wayland_client::backend::{ObjectData, ObjectId};
use wayland_client::{
- protocol::{wl_buffer, wl_shm, wl_surface},
Proxy,
+ protocol::{wl_buffer, wl_shm, wl_surface},
};
-use crate::wayland::shm::raw::RawPool;
use crate::wayland::shm::CreatePoolError;
+use crate::wayland::shm::raw::RawPool;
#[derive(Debug, thiserror::Error)]
pub(crate) enum CreateBufferError {
@@ -420,7 +420,10 @@ impl Buffer {
/// Note: if you need to ensure that [`canvas()`](Buffer::canvas) calls never return data that
/// could be attached to a surface in a multi-threaded client, make this call while you have
/// exclusive access to the corresponding [`SlotPool`].
- pub(crate) fn attach_to(&self, surface: &wl_surface::WlSurface) -> Result<(), ActivateSlotError> {
+ pub(crate) fn attach_to(
+ &self,
+ surface: &wl_surface::WlSurface,
+ ) -> Result<(), ActivateSlotError> {
self.activate()?;
surface.attach(Some(&self.inner), 0, 0);
Ok(())
diff --git a/pkgs/by-name/ri/river-mk-keymap/update.sh b/pkgs/by-name/ri/river-mk-keymap/update.sh
index 8e36e13e..5ad524e8 100755
--- a/pkgs/by-name/ri/river-mk-keymap/update.sh
+++ b/pkgs/by-name/ri/river-mk-keymap/update.sh
@@ -10,5 +10,5 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-[ "$1" = "upgrade" ] && cargo upgrade
-cargo update
+[ "$1" = "upgrade" ] && cargo upgrade --incompatible allow --pinned allow --recursive true
+cargo update --recursive
diff --git a/pkgs/by-name/sw/sw/package.nix b/pkgs/by-name/sw/sw/package.nix
new file mode 100644
index 00000000..dea7e16c
--- /dev/null
+++ b/pkgs/by-name/sw/sw/package.nix
@@ -0,0 +1,25 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ writeShellApplication,
+ # Dependencies
+ river-classic,
+}:
+writeShellApplication {
+ name = "sw";
+ text = builtins.readFile ./sw.sh;
+
+ # We need to inherit the path, so that we can spawn stuff in a swallowed mode.
+ inheritPath = true;
+
+ runtimeInputs = [
+ river-classic
+ ];
+}
diff --git a/pkgs/by-name/sw/sw/sw.sh b/pkgs/by-name/sw/sw/sw.sh
new file mode 100755
index 00000000..f71f65ee
--- /dev/null
+++ b/pkgs/by-name/sw/sw/sw.sh
@@ -0,0 +1,49 @@
+#!/usr/bin/env dash
+# Based on: https://codeberg.org/nirodhvana/reservoir/src/commit/2fa8c14877799a03bb927f048c2907dbb418fd68/dot-local/bin/gobble
+
+# Inspired by https://github.com/swindlesmccoop/not-just-dotfiles/blob/master/.local/bin/swallow
+
+swallow_tag=$((1 << 9))
+eat() {
+ riverctl set-view-tags $swallow_tag
+}
+
+throwup() {
+ riverctl set-focused-tags $swallow_tag &&
+ riverctl send-to-previous-tags &&
+ riverctl focus-previous-tags
+}
+
+swallow() {
+ # Make sure that we actually `throwup`, when the command fails
+ set +e
+
+ eat && "$@"
+ throwup
+
+ set -e
+}
+
+if [ -z "$*" ]; then
+ printf "sw: No arguments supplied\n"
+ exit 2
+else
+ case "$1" in
+ "-n" | "--nix")
+ shift 1
+ [ -z "$1" ] && {
+ printf "sw: --nix requires a nix installible\n"
+ exit 2
+ }
+
+ # Build it first, so the user still sees a progress bar
+ nix build --no-link "n#$1"
+
+ swallow nix run "n#$1"
+ ;;
+ *)
+ swallow "$@"
+ ;;
+ esac
+
+fi
diff --git a/pkgs/by-name/sy/systemd-toggle/package.nix b/pkgs/by-name/sy/systemd-toggle/package.nix
new file mode 100644
index 00000000..04ed576f
--- /dev/null
+++ b/pkgs/by-name/sy/systemd-toggle/package.nix
@@ -0,0 +1,23 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ writeShellApplication,
+ systemd,
+}:
+writeShellApplication {
+ name = "systemd-toggle";
+ text = builtins.readFile ./systemd-toggle.sh;
+ runtimeInputs = [
+ systemd
+ ];
+ meta = {
+ mainProgram = "systemd-toggle";
+ };
+}
diff --git a/pkgs/by-name/sy/systemd-toggle/systemd-toggle.sh b/pkgs/by-name/sy/systemd-toggle/systemd-toggle.sh
new file mode 100755
index 00000000..a8290238
--- /dev/null
+++ b/pkgs/by-name/sy/systemd-toggle/systemd-toggle.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env sh
+
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+
+use_user=false
+service=""
+while [ "$#" -ne 0 ]; do
+ case "$1" in
+ "--user")
+ use_user=true
+ ;;
+ *)
+ if [ -n "$service" ]; then
+ echo "Service was already specified as: '$service'"
+ exit 2
+ else
+ service="$1"
+ fi
+ ;;
+ esac
+
+ shift 1
+done
+
+user_str=""
+if [ "$use_user" = "true" ]; then
+ user_str="--user"
+fi
+
+service_status="$(systemctl $user_str show "$service" | grep ActiveState | sed 's/ActiveState=//')"
+
+if [ "$service_status" = "inactive" ]; then
+ systemctl $user_str start "$service"
+else
+ systemctl $user_str stop "$service"
+fi
+
+# vim: ft=sh
diff --git a/pkgs/by-name/ta/tails-iso/files.json b/pkgs/by-name/ta/tails-iso/files.json
new file mode 100644
index 00000000..096193f4
--- /dev/null
+++ b/pkgs/by-name/ta/tails-iso/files.json
@@ -0,0 +1,7 @@
+{
+ "version": "7.11",
+ "files": {
+ "iso.sig": "sha256-npEcaVwrY4F1ot79WE75h1iIxtDv8q3JcNfi9Ppd1cA=",
+ "iso": "sha256-1L7peM7wf9S5qCtiWz472vxEHZCfeUU0YF0LC36jP0o="
+ }
+}
diff --git a/pkgs/by-name/ta/tails-iso/package.nix b/pkgs/by-name/ta/tails-iso/package.nix
new file mode 100644
index 00000000..3325f178
--- /dev/null
+++ b/pkgs/by-name/ta/tails-iso/package.nix
@@ -0,0 +1,87 @@
+# nixos-config - My current NixOS configuration
+#
+# Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+# This file is part of my nixos-config.
+#
+# You should have received a copy of the License along with this program.
+# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
+{
+ stdenv,
+ fetchurl,
+ sequoia-sq,
+ libarchive, # for bsdtar
+}: let
+ files = builtins.fromJSON (builtins.readFile (./files.json));
+
+ checked_iso = stdenv.mkDerivation (finalAttrs: {
+ pname = "raw-tails-iso";
+ version = "amd64-${files.version}";
+
+ srcs = [
+ (fetchurl {
+ url = "https://tails.net/torrents/files/tails-${finalAttrs.version}.iso.sig";
+ hash = files.files."iso.sig";
+ })
+ (fetchurl {
+ url = "https://download.tails.net/tails/stable/tails-${finalAttrs.version}/tails-${finalAttrs.version}.iso";
+ hash = files.files."iso";
+ })
+ (fetchurl {
+ url = "https://tails.net/tails-signing.key";
+ hash = "sha256-LhWSTlgT9FJnd6RQZA3avLzhHKgfanRZkiipMjL1ZP8=";
+ })
+ ];
+
+ dontUnpack = true;
+
+ nativeBuildInputs = [
+ sequoia-sq
+ ];
+
+ buildPhase = ''
+ for src in $srcs; do
+ cp --recursive "$src" "$(stripHash "$src")"
+ done
+
+ sq verify \
+ --signer-file=tails-signing.key \
+ --signature-file=tails-${finalAttrs.version}.iso.sig \
+ tails-${finalAttrs.version}.iso
+ '';
+
+ installPhase = ''
+ cp tails-${finalAttrs.version}.iso "$out";
+ '';
+ });
+in
+ stdenv.mkDerivation {
+ pname = "tails-iso-package";
+ inherit (checked_iso) version;
+
+ src = checked_iso;
+
+ dontUnpack = true;
+
+ nativeBuildInputs = [
+ libarchive
+ ];
+
+ buildPhase = ''
+ mkdir iso
+ bsdtar -xf "$src" -C iso
+ '';
+
+ passthru = {
+ inherit (files) version;
+ };
+
+ installPhase = ''
+ install -D "$src" "$out/tails.iso"
+ install -D ./iso/live/vmlinuz "$out/live/vmlinuz-linux"
+ install -D ./iso/live/initrd.img "$out/live/initrd.img"
+
+ chmod --recursive -x $out/tails.iso $out/live/*
+ '';
+ }
diff --git a/pkgs/by-name/ta/tails-iso/update.sh b/pkgs/by-name/ta/tails-iso/update.sh
new file mode 100755
index 00000000..363df8c5
--- /dev/null
+++ b/pkgs/by-name/ta/tails-iso/update.sh
@@ -0,0 +1,74 @@
+#! /usr/bin/env sh
+
+set -e
+
+root="$(git rev-parse --show-toplevel)"
+
+cd "$root/pkgs/by-name/ta/tails-iso/"
+
+tmpHomePath="$(mktemp -d "${TMPDIR:-/tmp}/nix-prefetch-url-XXXXXXXXXX")"
+cleanup() {
+ chmod -R u+w "$tmpHomePath"
+ rm -rf "$tmpHomePath"
+}
+trap cleanup EXIT
+
+info() {
+ echo "$1" 1>&2
+}
+
+# Returns a name based on the url and reference
+#
+# This function needs to be in sync with nix's fetchgit implementation
+# of urlToName() to re-use the same nix store paths.
+url_to_name() {
+ url=$1
+
+ basename "$url" .git | cut -d: -f2
+}
+
+get_sha256() {
+ url="$1"
+ storePathName="$(url_to_name "$url")"
+ hashType="sha256"
+
+ tmpOut="$tmpHomePath/$storePathName"
+ info "Prefetching '$url'..."
+
+ curl --follow "$url" >"$tmpOut"
+
+ # Compute the hash.
+ hash=$(nix-hash --flat --type "$hashType" --sri "$tmpOut")
+
+ # Add the downloaded file to the Nix store.
+ finalPath=$(nix-store --add-fixed "$hashType" "$tmpOut")
+
+ info " -> Downloaded to '$finalPath'"
+ echo "$hash"
+}
+
+new_version="$(curl --follow https://download.tails.net/tails/stable/ 2>/dev/null | html2text -links | grep --regexp='\s*[0-9]\. tails-amd64-' | sed 's/\s*[0-9]\. tails-amd64-\(.*\)\//\1/')"
+
+# The `files.json` file might not be present, if a previous update failed.
+if test -f ./files.json; then
+ old_version="$(jq .version --raw-output <./files.json)"
+
+ if [ "$old_version" = "$new_version" ]; then
+ # No need to update.
+ exit 0
+ fi
+fi
+
+final_version="amd64-$new_version"
+
+cat <<EOF | jq . | tee ./files.json
+{
+ "version": "$new_version",
+ "files": {
+ "iso.sig": "$(get_sha256 "https://tails.net/torrents/files/tails-$final_version.iso.sig")",
+ "iso": "$(get_sha256 "https://download.tails.net/tails/stable/tails-$final_version/tails-$final_version.iso")"
+ }
+}
+EOF
+
+# vim: ft=sh
diff --git a/pkgs/by-name/ts/tskm/Cargo.lock b/pkgs/by-name/ts/tskm/Cargo.lock
index 2e253ebd..22e23813 100644
--- a/pkgs/by-name/ts/tskm/Cargo.lock
+++ b/pkgs/by-name/ts/tskm/Cargo.lock
@@ -18,31 +18,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
-name = "ahash"
-version = "0.8.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
-dependencies = [
- "cfg-if",
- "once_cell",
- "version_check",
- "zerocopy",
-]
-
-[[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",
@@ -55,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",
]
@@ -74,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]]
@@ -85,14 +73,14 @@ 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 = "arraydeque"
@@ -101,22 +89,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
+name = "async-trait"
+version = "0.1.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
name = "autocfg"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[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 = "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 = "byteorder"
@@ -126,9 +125,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
-version = "1.2.53"
+version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
+checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -142,9 +141,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[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",
@@ -156,9 +155,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",
@@ -166,9 +165,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",
@@ -178,9 +177,9 @@ dependencies = [
[[package]]
name = "clap_complete"
-version = "4.5.65"
+version = "4.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d"
+checksum = "3be2ad0423bdbbb0e25bc89add796f3559706d4a95e1bc98e4d9662a957b6a19"
dependencies = [
"clap",
"clap_lex",
@@ -190,27 +189,27 @@ 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.3",
]
[[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 = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "core-foundation-sys"
@@ -220,9 +219,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crc32fast"
-version = "1.5.0"
+version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
dependencies = [
"cfg-if",
]
@@ -245,18 +244,18 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
- "windows-sys 0.61.2",
+ "windows-sys",
]
[[package]]
name = "displaydoc"
-version = "0.2.5"
+version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.3",
]
[[package]]
@@ -282,15 +281,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "find-msvc-tools"
-version = "0.1.8"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
+checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]]
name = "flate2"
-version = "1.1.8"
+version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -298,9 +297,9 @@ dependencies = [
[[package]]
name = "foldhash"
-version = "0.1.5"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "form_urlencoded"
@@ -312,6 +311,30 @@ dependencies = [
]
[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -324,50 +347,51 @@ dependencies = [
[[package]]
name = "getrandom"
-version = "0.3.4"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"r-efi",
- "wasip2",
+ "wasm-bindgen",
]
[[package]]
name = "hashbrown"
-version = "0.14.5"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
- "ahash",
+ "foldhash",
]
[[package]]
name = "hashbrown"
-version = "0.15.5"
+version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"foldhash",
]
[[package]]
name = "hashlink"
-version = "0.9.1"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
+checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
dependencies = [
- "hashbrown 0.14.5",
+ "hashbrown 0.16.1",
]
[[package]]
name = "hashlink"
-version = "0.10.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
+checksum = "32069d97bb81e38fa67eab65e3393bf804bb85969f2bc06bf13f64aef5aba248"
dependencies = [
- "hashbrown 0.15.5",
+ "hashbrown 0.17.1",
]
[[package]]
@@ -384,9 +408,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[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",
@@ -408,12 +432,13 @@ dependencies = [
[[package]]
name = "icu_collections"
-version = "2.1.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
+checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
dependencies = [
"displaydoc",
"potential_utf",
+ "utf8_iter",
"yoke",
"zerofrom",
"zerovec",
@@ -421,9 +446,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
-version = "2.1.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
+checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
dependencies = [
"displaydoc",
"litemap",
@@ -434,9 +459,9 @@ dependencies = [
[[package]]
name = "icu_normalizer"
-version = "2.1.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
+checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
dependencies = [
"icu_collections",
"icu_normalizer_data",
@@ -448,16 +473,17 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
-version = "2.1.1"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
+checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
[[package]]
name = "icu_properties"
-version = "2.1.2"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
+checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
dependencies = [
+ "displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
@@ -468,15 +494,15 @@ dependencies = [
[[package]]
name = "icu_properties_data"
-version = "2.1.2"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
+checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
[[package]]
name = "icu_provider"
-version = "2.1.1"
+version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
+checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
dependencies = [
"displaydoc",
"icu_locale_core",
@@ -500,9 +526,9 @@ dependencies = [
[[package]]
name = "idna_adapter"
-version = "1.2.1"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
"icu_normalizer",
"icu_properties",
@@ -516,16 +542,16 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
- "windows-sys 0.61.2",
+ "windows-sys",
]
[[package]]
name = "is_executable"
-version = "1.0.5"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baabb8b4867b26294d818bf3f651a454b6901431711abb96e296245888d6e8c4"
+checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd"
dependencies = [
- "windows-sys 0.60.2",
+ "windows-sys",
]
[[package]]
@@ -536,41 +562,41 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[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.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
+checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
dependencies = [
- "once_cell",
+ "cfg-if",
+ "futures-util",
"wasm-bindgen",
]
[[package]]
name = "libc"
-version = "0.2.180"
+version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libredox"
-version = "0.1.12"
+version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
+checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a"
dependencies = [
- "bitflags",
"libc",
]
[[package]]
name = "libsqlite3-sys"
-version = "0.30.1"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
+checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1"
dependencies = [
"pkg-config",
"vcpkg",
@@ -578,27 +604,27 @@ dependencies = [
[[package]]
name = "litemap"
-version = "0.8.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
[[package]]
name = "log"
-version = "0.4.29"
+version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "md5"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0"
+checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c"
[[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 = "miniz_oxide"
@@ -621,9 +647,9 @@ dependencies = [
[[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"
@@ -644,43 +670,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
name = "pkg-config"
-version = "0.3.32"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "potential_utf"
-version = "0.1.4"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
+checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
dependencies = [
"zerovec",
]
[[package]]
name = "proc-macro2"
-version = "1.0.105"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.43"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
-version = "5.3.0"
+version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "redox_users"
@@ -694,24 +726,35 @@ dependencies = [
]
[[package]]
+name = "rsqlite-vfs"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
+dependencies = [
+ "hashbrown 0.16.1",
+ "thiserror",
+]
+
+[[package]]
name = "rusqlite"
-version = "0.32.1"
+version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
+checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
- "hashlink 0.9.1",
+ "hashlink 0.11.1",
"libsqlite3-sys",
"smallvec",
+ "sqlite-wasm-rs",
]
[[package]]
name = "rustversion"
-version = "1.0.22"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "same-file"
@@ -724,39 +767,50 @@ dependencies = [
[[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",
]
[[package]]
+name = "serde-wasm-bindgen"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
+dependencies = [
+ "js-sys",
+ "serde",
+ "wasm-bindgen",
+]
+
+[[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.3",
]
[[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",
@@ -767,21 +821,39 @@ 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 = "simd-adler32"
-version = "0.3.8"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
-version = "1.15.1"
+version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+
+[[package]]
+name = "sqlite-wasm-rs"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
+dependencies = [
+ "cc",
+ "js-sys",
+ "rsqlite-vfs",
+ "wasm-bindgen",
+]
[[package]]
name = "stable_deref_trait"
@@ -809,27 +881,38 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
-version = "0.27.2"
+version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
+checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
[[package]]
name = "strum_macros"
-version = "0.27.2"
+version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7"
+checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[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.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
@@ -844,27 +927,33 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
name = "taskchampion"
-version = "2.0.3"
+version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b010f5ebe51e88ae490691ed2a43b699e3468c8e3838e244accd8526aca7751b"
+checksum = "82f3be5bd922568eaaa1cbf30d4daf7979723c53465f3b202a88c7746fd0d7b6"
dependencies = [
"anyhow",
+ "async-trait",
"byteorder",
"chrono",
"flate2",
+ "getrandom 0.4.3",
"log",
"rusqlite",
"serde",
+ "serde-wasm-bindgen",
"serde_json",
"strum",
"strum_macros",
"thiserror",
+ "tokio",
"uuid",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
]
[[package]]
@@ -878,44 +967,65 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.17"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.17"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.3",
]
[[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 = "tinystr"
-version = "0.8.2"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
+name = "tokio"
+version = "1.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
+dependencies = [
+ "pin-project-lite",
+ "tokio-macros",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.3",
+]
+
+[[package]]
name = "tskm"
version = "0.1.0"
dependencies = [
@@ -929,6 +1039,7 @@ dependencies = [
"serde_json",
"stderrlog",
"taskchampion",
+ "tokio",
"url",
"walkdir",
"yaml-rust2",
@@ -936,9 +1047,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
-version = "1.0.22"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "url"
@@ -967,11 +1078,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
-version = "1.19.0"
+version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
+checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc"
dependencies = [
- "getrandom 0.3.4",
+ "getrandom 0.4.3",
"js-sys",
"serde_core",
"wasm-bindgen",
@@ -984,12 +1095,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
-name = "version_check"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-
-[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1006,19 +1111,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
-name = "wasip2"
-version = "1.0.2+wasi-0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
-dependencies = [
- "wit-bindgen",
-]
-
-[[package]]
name = "wasm-bindgen"
-version = "0.2.108"
+version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
+checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
dependencies = [
"cfg-if",
"once_cell",
@@ -1028,10 +1124,20 @@ dependencies = [
]
[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.108"
+version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
+checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1039,22 +1145,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.108"
+version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
+checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.108"
+version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
+checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
dependencies = [
"unicode-ident",
]
@@ -1065,7 +1171,7 @@ 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]]
@@ -1089,7 +1195,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
@@ -1100,7 +1206,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
@@ -1129,15 +1235,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"
@@ -1146,98 +1243,27 @@ 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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
-
-[[package]]
-name = "wit-bindgen"
-version = "0.51.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
-
-[[package]]
name = "writeable"
-version = "0.6.2"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
[[package]]
name = "yaml-rust2"
-version = "0.10.4"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9"
+checksum = "c6edb26322e610d4f04b7cd34478317685d24d0999437e551fb97c5441151041"
dependencies = [
"arraydeque",
"encoding_rs",
- "hashlink 0.10.0",
+ "hashlink 0.12.1",
]
[[package]]
name = "yoke"
-version = "0.8.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
dependencies = [
"stable_deref_trait",
"yoke-derive",
@@ -1246,62 +1272,42 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
"synstructure",
]
[[package]]
-name = "zerocopy"
-version = "0.8.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd"
-dependencies = [
- "zerocopy-derive",
-]
-
-[[package]]
-name = "zerocopy-derive"
-version = "0.8.33"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "zerofrom"
-version = "0.1.6"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
-version = "0.1.6"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
+checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
"synstructure",
]
[[package]]
name = "zerotrie"
-version = "0.2.3"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
+checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
dependencies = [
"displaydoc",
"yoke",
@@ -1310,9 +1316,9 @@ dependencies = [
[[package]]
name = "zerovec"
-version = "0.11.5"
+version = "0.11.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
dependencies = [
"yoke",
"zerofrom",
@@ -1321,17 +1327,17 @@ dependencies = [
[[package]]
name = "zerovec-derive"
-version = "0.11.2"
+version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
+checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.3",
]
[[package]]
name = "zmij"
-version = "1.0.15"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/pkgs/by-name/ts/tskm/Cargo.toml b/pkgs/by-name/ts/tskm/Cargo.toml
index 49774037..844cd652 100644
--- a/pkgs/by-name/ts/tskm/Cargo.toml
+++ b/pkgs/by-name/ts/tskm/Cargo.toml
@@ -16,19 +16,31 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-anyhow = { version = "1.0.100", default-features = false }
-clap = { version = "4.5.54", features = [ "derive", "std", "color", "help", "usage", "error-context", "suggestions", ], default-features = false }
-clap_complete = { version = "4.5.65", features = ["unstable-dynamic"] }
+anyhow = { version = "1.0.104", default-features = false }
+clap = { version = "4.6.6", features = [
+ "derive",
+ "std",
+ "color",
+ "help",
+ "usage",
+ "error-context",
+ "suggestions",
+], default-features = false }
+clap_complete = { version = "4.6.9", features = ["unstable-dynamic"] }
dirs = { version = "6.0.0", default-features = false }
-log = { version = "0.4.29", default-features = false }
-serde = { version = "1.0.228", features = ["derive"], default-features = false }
-serde_json = { version = "1.0.149", default-features = false }
+log = { version = "0.4.34", default-features = false }
+serde = { version = "1.0.229", features = ["derive"], default-features = false }
+serde_json = { version = "1.0.151", default-features = false }
stderrlog = { version = "0.6.0", default-features = false }
-taskchampion = { version = "2.0.3", default-features = false }
-url = { version = "2.5.8", features = ["serde", "std"], default-features = false }
+taskchampion = { version = "3.1.0", features =["storage", "storage-sqlite"], default-features = false }
+url = { version = "2.5.8", features = [
+ "serde",
+ "std",
+], default-features = false }
walkdir = { version = "2.5.0", default-features = false }
-md5 = { version = "0.8.0", default-features = false }
-yaml-rust2 = "0.10.4"
+md5 = { version = "0.8.1", default-features = false }
+yaml-rust2 = "0.12.0"
+tokio = { version = "1.53.1", features = ["rt-multi-thread"] }
[profile.release]
lto = true
diff --git a/pkgs/by-name/ts/tskm/flake.lock b/pkgs/by-name/ts/tskm/flake.lock
deleted file mode 100644
index 1e997998..00000000
--- a/pkgs/by-name/ts/tskm/flake.lock
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "nodes": {
- "nixpkgs": {
- "locked": {
- "lastModified": 1768661221,
- "narHash": "sha256-MJwOjrIISfOpdI9x4C+5WFQXvHtOuj5mqLZ4TMEtk1M=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "3327b113f2ef698d380df83fbccefad7e83d7769",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixpkgs-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "nixpkgs": "nixpkgs"
- }
- }
- },
- "root": "root",
- "version": 7
-}
diff --git a/pkgs/by-name/ts/tskm/flake.lock.license b/pkgs/by-name/ts/tskm/flake.lock.license
deleted file mode 100644
index eae6a84c..00000000
--- a/pkgs/by-name/ts/tskm/flake.lock.license
+++ /dev/null
@@ -1,9 +0,0 @@
-nixos-config - My current NixOS configuration
-
-Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
-SPDX-License-Identifier: GPL-3.0-or-later
-
-This file is part of my nixos-config.
-
-You should have received a copy of the License along with this program.
-If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
diff --git a/pkgs/by-name/ts/tskm/flake.nix b/pkgs/by-name/ts/tskm/flake.nix
index 6217f942..a4a010ae 100644
--- a/pkgs/by-name/ts/tskm/flake.nix
+++ b/pkgs/by-name/ts/tskm/flake.nix
@@ -11,12 +11,13 @@
description = "This is the core interface to the system-integrated task management";
inputs = {
- nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
- outputs = {nixpkgs, ...}: let
+ outputs = {...}: let
system = "x86_64-linux";
- pkgs = nixpkgs.legacyPackages."${system}";
+
+ sources = import ../../../../unflake.nix;
+ pkgs = sources.nixpkgs.legacyPackages."${system}";
in {
devShells."${system}".default = pkgs.mkShell {
buildInputs = [
diff --git a/pkgs/by-name/ts/tskm/src/browser/mod.rs b/pkgs/by-name/ts/tskm/src/browser/mod.rs
index 2129982f..fd90b820 100644
--- a/pkgs/by-name/ts/tskm/src/browser/mod.rs
+++ b/pkgs/by-name/ts/tskm/src/browser/mod.rs
@@ -14,7 +14,7 @@ use url::Url;
use crate::{state::State, task};
#[allow(clippy::too_many_lines)]
-pub fn open_in_browser<U>(
+pub async fn open_in_browser<U>(
selected_project: &task::Project,
state: &mut State,
urls: Option<Vec<U>>,
@@ -24,8 +24,9 @@ where
{
let old_project: Option<task::Project> =
task::Project::get_current().context("Failed to get currently active project")?;
- let old_task: Option<task::Task> =
- task::Task::get_current(state).context("Failed to get currently active task")?;
+ let old_task: Option<task::Task> = task::Task::get_current(state)
+ .await
+ .context("Failed to get currently active task")?;
selected_project.activate().with_context(|| {
format!(
@@ -35,25 +36,36 @@ where
})?;
let tracking_task = {
- let all_tasks = selected_project.get_tasks(state).with_context(|| {
+ let all_tasks = selected_project.get_tasks(state).await.with_context(|| {
format!(
"Failed to get assoctiated tasks for project: '{}'",
selected_project.to_project_display()
)
})?;
- let tracking_task = all_tasks.into_iter().find(|t| {
- let maybe_desc = t.description(state);
- if let Ok(desc) = maybe_desc {
- desc == "tracking"
- } else {
- error!(
- "Getting task description returned error: {}",
- maybe_desc.expect_err("We already check for Ok")
- );
- false
+ let tracking_task = {
+ let mut output = None;
+
+ for t in all_tasks {
+ let maybe_desc = t.description(state).await;
+ let found = if let Ok(desc) = maybe_desc {
+ desc == "tracking"
+ } else {
+ error!(
+ "Getting task description returned error: {}",
+ maybe_desc.expect_err("We already check for Ok")
+ );
+ false
+ };
+
+ if found {
+ output = Some(t);
+ break;
+ }
}
- });
+
+ output
+ };
if let Some(task) = tracking_task {
info!(
@@ -61,6 +73,7 @@ where
selected_project.to_project_display()
);
task.start(state)
+ .await
.with_context(|| format!("Failed to start task {task}"))?;
}
tracking_task
@@ -180,10 +193,12 @@ where
if let Some(task) = tracking_task {
task.stop(state)
+ .await
.with_context(|| format!("Failed to stop task {task}"))?;
}
if let Some(task) = old_task {
task.start(state)
+ .await
.with_context(|| format!("Failed to start task {task}"))?;
}
diff --git a/pkgs/by-name/ts/tskm/src/cli.rs b/pkgs/by-name/ts/tskm/src/cli.rs
index 359c1050..3dc1181d 100644
--- a/pkgs/by-name/ts/tskm/src/cli.rs
+++ b/pkgs/by-name/ts/tskm/src/cli.rs
@@ -8,11 +8,12 @@
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-use std::{ffi::OsStr, path::PathBuf};
+use std::{ffi::OsStr, path::PathBuf, thread};
use anyhow::{bail, Result};
use clap::{builder::StyledStr, ArgAction, Parser, Subcommand, ValueEnum};
use clap_complete::{ArgValueCompleter, CompletionCandidate};
+use tokio::runtime::Runtime;
use crate::{
interface::{
@@ -23,6 +24,43 @@ use crate::{
state, task,
};
+macro_rules! as_sync {
+ (
+ wrap $old_name_1:ident($($arg_name_1:ident : $arg_type_1:ty),*) -> $output_1:ty => $new_name_1:ident;
+ $(
+ wrap $old_name:ident($($arg_name:ident : $arg_type:ty),*) -> $output:ty => $new_name:ident;
+ )+
+ ) => {
+ as_sync!(
+ wrap $old_name_1($($arg_name_1 : $arg_type_1),*) -> $output_1 => $new_name_1;
+ );
+ as_sync!(
+ $(
+ wrap $old_name($($arg_name : $arg_type),*) -> $output => $new_name;
+ )+
+ );
+ };
+ (
+ wrap $old_name:ident($($arg_name:ident : $arg_type:ty),*) -> $output:ty => $new_name:ident $(;)?
+ ) => {
+ fn $new_name($($arg_name: $arg_type),*) -> $output {
+ $(
+ let $arg_name = $arg_name.to_owned();
+ ),*
+
+ let handle: std::thread::JoinHandle<$output> = thread::spawn(move || {
+ let rt = Runtime::new().expect("No runtime issue");
+
+ let output = rt.block_on($old_name($($arg_name.as_ref()),*));
+
+ output
+ });
+
+ handle.join().expect("The thread should be joinable")
+ }
+ };
+}
+
#[derive(Parser, Debug)]
#[command(author, version, about, long_about, verbatim_doc_comment)]
/// This is the core interface to the system-integrated task management
@@ -94,16 +132,21 @@ pub enum NeorgCommand {
/// Open the `neorg` project associated with id of the task.
Task {
/// The working set id of the task
- #[arg(value_name = "ID", value_parser = task_from_working_set_id, add = ArgValueCompleter::new(complete_task_id))]
+ #[arg(value_name = "ID", value_parser = task_from_working_set_id_sync, add = ArgValueCompleter::new(complete_task_id_sync))]
task: task::Task,
},
}
-fn task_from_working_set_id(id: &str) -> Result<task::Task> {
+as_sync!(
+ wrap task_from_working_set_id(id: &str) -> Result<task::Task> => task_from_working_set_id_sync;
+ wrap complete_task_id(current: &OsStr) -> Vec<CompletionCandidate> => complete_task_id_sync;
+);
+
+async fn task_from_working_set_id(id: &str) -> Result<task::Task> {
let id: usize = id.parse()?;
- let mut state = state::State::new_ro()?;
+ let mut state = state::State::new_ro().await?;
- let Some(task) = task::Task::from_working_set(id, &mut state)? else {
+ let Some(task) = task::Task::from_working_set(id, &mut state).await? else {
bail!("Working set id '{id}' is not valid!")
};
Ok(task)
@@ -201,14 +244,14 @@ pub enum InputCommand {
Tags {},
}
-fn complete_task_id(current: &OsStr) -> Vec<CompletionCandidate> {
- fn format_task(
+async fn complete_task_id(current: &OsStr) -> Vec<CompletionCandidate> {
+ async fn format_task(
task: task::Task,
current: &str,
state: &mut state::State,
) -> Option<CompletionCandidate> {
let id = {
- let Ok(base) = task.working_set_id(state) else {
+ let Ok(base) = task.working_set_id(state).await else {
return None;
};
base.to_string()
@@ -219,7 +262,7 @@ fn complete_task_id(current: &OsStr) -> Vec<CompletionCandidate> {
}
let description = {
- let Ok(base) = task.description(state) else {
+ let Ok(base) = task.description(state).await else {
return None;
};
StyledStr::from(base)
@@ -234,11 +277,11 @@ fn complete_task_id(current: &OsStr) -> Vec<CompletionCandidate> {
return output;
};
- let Ok(mut state) = state::State::new_ro() else {
+ let Ok(mut state) = state::State::new_ro().await else {
return output;
};
- let Ok(pending) = state.replica().pending_tasks() else {
+ let Ok(pending) = state.replica().pending_tasks().await else {
return output;
};
@@ -249,9 +292,9 @@ fn complete_task_id(current: &OsStr) -> Vec<CompletionCandidate> {
if let Some(current_project) = current_project {
for t in pending {
let task = task::Task::from(&t);
- if let Ok(project) = task.project(&mut state) {
+ if let Ok(project) = task.project(&mut state).await {
if project == current_project {
- if let Some(out) = format_task(task, current, &mut state) {
+ if let Some(out) = format_task(task, current, &mut state).await {
output.push(out);
}
}
@@ -260,7 +303,7 @@ fn complete_task_id(current: &OsStr) -> Vec<CompletionCandidate> {
} else {
for t in pending {
let task = task::Task::from(&t);
- if let Some(out) = format_task(task, current, &mut state) {
+ if let Some(out) = format_task(task, current, &mut state).await {
output.push(out);
}
}
diff --git a/pkgs/by-name/ts/tskm/src/interface/input/handle.rs b/pkgs/by-name/ts/tskm/src/interface/input/handle.rs
index 76eea6dc..cd868f7a 100644
--- a/pkgs/by-name/ts/tskm/src/interface/input/handle.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/input/handle.rs
@@ -16,6 +16,7 @@ use std::{
use anyhow::{Context, Result};
use log::info;
+use taskchampion::chrono::Utc;
use crate::{browser::open_in_browser, cli::InputCommand, state::State};
@@ -27,7 +28,7 @@ use super::{Input, Tag};
/// # Panics
/// When internal assertions fail.
#[allow(clippy::too_many_lines)]
-pub fn handle(command: InputCommand, state: &mut State) -> Result<()> {
+pub async fn handle(command: InputCommand, state: &mut State) -> Result<()> {
match command {
InputCommand::Add { inputs } => {
for input in inputs {
@@ -52,6 +53,11 @@ pub fn handle(command: InputCommand, state: &mut State) -> Result<()> {
tag_set.insert(tag);
}
+ tag_set.insert(
+ Tag::new(format!("+{}", Utc::now().format("%Y-%m-%d")).as_str())
+ .expect("hardcoded"),
+ );
+
for line in file.lines().map(str::trim) {
if line.is_empty() {
continue;
@@ -73,7 +79,8 @@ pub fn handle(command: InputCommand, state: &mut State) -> Result<()> {
&project,
state,
Some(all.iter().map(|f| f.url.clone()).collect()),
- )?;
+ )
+ .await?;
{
use std::io::{stdin, stdout, Write};
diff --git a/pkgs/by-name/ts/tskm/src/interface/input/mod.rs b/pkgs/by-name/ts/tskm/src/interface/input/mod.rs
index 1d1d67f4..b6176a96 100644
--- a/pkgs/by-name/ts/tskm/src/interface/input/mod.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/input/mod.rs
@@ -18,7 +18,7 @@ use std::{
str::FromStr,
};
-use anyhow::{bail, Context, Result};
+use anyhow::{Context, Result, bail};
use url::Url;
use walkdir::WalkDir;
diff --git a/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs b/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs
index ea3a89ae..12a0180d 100644
--- a/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/neorg/handle.rs
@@ -19,10 +19,10 @@ use anyhow::{bail, Context, Result};
use crate::{cli::NeorgCommand, state::State};
-pub fn handle(command: NeorgCommand, state: &mut State) -> Result<()> {
+pub async fn handle(command: NeorgCommand, state: &mut State) -> Result<()> {
match command {
NeorgCommand::Task { task } => {
- let project = task.project(state)?;
+ let project = task.project(state).await?;
let base = dirs::data_local_dir()
.expect("This should exists")
.join("tskm/notes");
@@ -46,7 +46,8 @@ pub fn handle(command: NeorgCommand, state: &mut State) -> Result<()> {
let mut file = options.open(&path)?;
file.write_all(
- format!("* {} (% {})", task.description(state)?, task.uuid()).as_bytes(),
+ format!("* {} (% {})", task.description(state).await?, task.uuid())
+ .as_bytes(),
)
.with_context(|| {
format!("Failed to write task uuid to file: '{}'", path.display())
@@ -92,7 +93,7 @@ pub fn handle(command: NeorgCommand, state: &mut State) -> Result<()> {
}
{
- task.mark_neorg_data(state)?;
+ task.mark_neorg_data(state).await?;
}
}
}
diff --git a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
index 3897a63b..5b9100bc 100644
--- a/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/open/handle.rs
@@ -31,7 +31,7 @@ fn is_empty(project: &task::Project) -> Result<bool> {
}
#[allow(clippy::too_many_lines)]
-pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> {
+pub async fn handle(command: OpenCommand, state: &mut State) -> Result<()> {
match command {
OpenCommand::Review { non_empty } => {
for project in task::Project::all().context("Failed to get all project files")? {
@@ -43,12 +43,14 @@ pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> {
project.to_project_display(),
if is_empty { "is empty" } else { "is not empty" }
);
- open_in_browser(project, state, None::<Vec<Url>>).with_context(|| {
- format!(
- "Failed to open project ('{}') in qutebrowser",
- project.to_project_display()
- )
- })?;
+ open_in_browser(project, state, None::<Vec<Url>>)
+ .await
+ .with_context(|| {
+ format!(
+ "Failed to open project ('{}') in qutebrowser",
+ project.to_project_display()
+ )
+ })?;
if project.is_touched() {
project.untouch().with_context(|| {
@@ -63,9 +65,11 @@ pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> {
}
OpenCommand::Project { project, urls } => {
project.touch().context("Failed to touch project")?;
- open_in_browser(&project, state, urls).with_context(|| {
- format!("Failed to open project: {}", project.to_project_display())
- })?;
+ open_in_browser(&project, state, urls)
+ .await
+ .with_context(|| {
+ format!("Failed to open project: {}", project.to_project_display())
+ })?;
}
OpenCommand::Select { urls } => {
let selected_project: task::Project = task::Project::from_project_string(
@@ -85,7 +89,9 @@ pub fn handle(command: OpenCommand, state: &mut State) -> Result<()> {
.touch()
.context("Failed to touch project")?;
- open_in_browser(&selected_project, state, urls).context("Failed to open project")?;
+ open_in_browser(&selected_project, state, urls)
+ .await
+ .context("Failed to open project")?;
}
OpenCommand::ListTabs { projects, mode } => {
let projects = {
diff --git a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
index e403b4a8..407536d2 100644
--- a/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
+++ b/pkgs/by-name/ts/tskm/src/interface/open/mod.rs
@@ -14,7 +14,7 @@ use std::{
str::FromStr,
};
-use anyhow::{anyhow, Context, Result};
+use anyhow::{Context, Result, anyhow};
use taskchampion::chrono::NaiveDateTime;
use url::Url;
use yaml_rust2::Yaml;
diff --git a/pkgs/by-name/ts/tskm/src/main.rs b/pkgs/by-name/ts/tskm/src/main.rs
index e6113111..a852bd7b 100644
--- a/pkgs/by-name/ts/tskm/src/main.rs
+++ b/pkgs/by-name/ts/tskm/src/main.rs
@@ -24,7 +24,8 @@ pub mod rofi;
pub mod state;
pub mod task;
-fn main() -> Result<(), anyhow::Error> {
+#[tokio::main]
+async fn main() -> Result<(), anyhow::Error> {
clap_complete::CompleteEnv::with_factory(CliArgs::command).complete();
let args = CliArgs::parse();
@@ -38,12 +39,12 @@ fn main() -> Result<(), anyhow::Error> {
.init()
.expect("Let's just hope that this does not panic");
- let mut state = State::new_rw()?;
+ let mut state = State::new_rw().await?;
match args.command {
- Command::Inputs { command } => input::handle(command, &mut state)?,
- Command::Neorg { command } => neorg::handle(command, &mut state)?,
- Command::Open { command } => open::handle(command, &mut state)?,
+ Command::Inputs { command } => input::handle(command, &mut state).await?,
+ Command::Neorg { command } => neorg::handle(command, &mut state).await?,
+ Command::Open { command } => open::handle(command, &mut state).await?,
Command::Projects { command } => project::handle(command)?,
}
diff --git a/pkgs/by-name/ts/tskm/src/state.rs b/pkgs/by-name/ts/tskm/src/state.rs
index ae71764e..57495bb8 100644
--- a/pkgs/by-name/ts/tskm/src/state.rs
+++ b/pkgs/by-name/ts/tskm/src/state.rs
@@ -11,10 +11,13 @@
use std::path::PathBuf;
use anyhow::Result;
-use taskchampion::{Replica, StorageConfig, storage::AccessMode};
+use taskchampion::{
+ storage::{sqlite::SqliteStorage, AccessMode},
+ Replica,
+};
pub struct State {
- replica: Replica,
+ replica: Replica<SqliteStorage>,
}
impl std::fmt::Debug for State {
@@ -28,28 +31,23 @@ impl State {
dirs::data_local_dir().expect("Should exist").join("task")
}
- fn new(taskdb_dir: PathBuf, access_mode: AccessMode) -> Result<Self> {
- let storage = StorageConfig::OnDisk {
- taskdb_dir,
- create_if_missing: false,
- access_mode,
- }
- .into_storage()?;
+ async fn new(taskdb_dir: PathBuf, access_mode: AccessMode) -> Result<Self> {
+ let storage = SqliteStorage::new(taskdb_dir, access_mode, false).await?;
let replica = Replica::new(storage);
Ok(Self { replica })
}
- pub fn new_ro() -> Result<Self> {
- Self::new(Self::taskdb_dir(), AccessMode::ReadOnly)
+ pub async fn new_ro() -> Result<Self> {
+ Self::new(Self::taskdb_dir(), AccessMode::ReadOnly).await
}
- pub fn new_rw() -> Result<Self> {
- Self::new(Self::taskdb_dir(), AccessMode::ReadWrite)
+ pub async fn new_rw() -> Result<Self> {
+ Self::new(Self::taskdb_dir(), AccessMode::ReadWrite).await
}
#[must_use]
- pub fn replica(&mut self) -> &mut Replica {
+ pub fn replica(&mut self) -> &mut Replica<SqliteStorage> {
&mut self.replica
}
}
diff --git a/pkgs/by-name/ts/tskm/src/task/mod.rs b/pkgs/by-name/ts/tskm/src/task/mod.rs
index 9c671273..1362615d 100644
--- a/pkgs/by-name/ts/tskm/src/task/mod.rs
+++ b/pkgs/by-name/ts/tskm/src/task/mod.rs
@@ -45,18 +45,20 @@ impl From<&taskchampion::TaskData> for Task {
}
impl Task {
- pub fn from_working_set(id: usize, state: &mut State) -> Result<Option<Self>> {
+ pub async fn from_working_set(id: usize, state: &mut State) -> Result<Option<Self>> {
Ok(state
.replica()
- .working_set()?
+ .working_set()
+ .await?
.by_index(id)
.map(|uuid| Self { uuid }))
}
- pub fn get_current(state: &mut State) -> Result<Option<Self>> {
+ pub async fn get_current(state: &mut State) -> Result<Option<Self>> {
let tasks = state
.replica()
- .pending_tasks()?
+ .pending_tasks()
+ .await?
.into_iter()
.filter(taskchampion::Task::is_active)
.collect::<Vec<_>>();
@@ -76,62 +78,65 @@ impl Task {
pub fn uuid(&self) -> &taskchampion::Uuid {
&self.uuid
}
- pub fn working_set_id(&self, state: &mut State) -> Result<usize> {
+ pub async fn working_set_id(&self, state: &mut State) -> Result<usize> {
Ok(state
.replica()
- .working_set()?
+ .working_set()
+ .await?
.by_uuid(self.uuid)
.expect("The task should be in the working set"))
}
- fn as_task(&self, state: &mut State) -> Result<taskchampion::Task> {
+ async fn as_task(&self, state: &mut State) -> Result<taskchampion::Task> {
Ok(state
.replica()
- .get_task(self.uuid)?
+ .get_task(self.uuid)
+ .await?
.expect("We have the task from this replica, it should still be in it"))
}
/// Adds a tag to the task, to show the user that it has additional neorg data.
- pub fn mark_neorg_data(&self, state: &mut State) -> Result<()> {
+ pub async fn mark_neorg_data(&self, state: &mut State) -> Result<()> {
let mut ops = vec![];
- self.as_task(state)?
+ self.as_task(state)
+ .await?
.add_tag(&Tag::from_str("neorg_data").expect("Is valid"), &mut ops)?;
- state.replica().commit_operations(ops)?;
+ state.replica().commit_operations(ops).await?;
Ok(())
}
/// Try to start this task.
/// It will stop previously active tasks.
- pub fn start(&self, state: &mut State) -> Result<()> {
+ pub async fn start(&self, state: &mut State) -> Result<()> {
info!("Activating {self}");
- if let Some(active) = Self::get_current(state)? {
- active.stop(state)?;
+ if let Some(active) = Self::get_current(state).await? {
+ active.stop(state).await?;
}
let mut ops = vec![];
- self.as_task(state)?.start(&mut ops)?;
- state.replica().commit_operations(ops)?;
+ self.as_task(state).await?.start(&mut ops)?;
+ state.replica().commit_operations(ops).await?;
Ok(())
}
/// Stops this task.
- pub fn stop(&self, state: &mut State) -> Result<()> {
+ pub async fn stop(&self, state: &mut State) -> Result<()> {
info!("Stopping {self}");
let mut ops = vec![];
- self.as_task(state)?.stop(&mut ops)?;
- state.replica().commit_operations(ops)?;
+ self.as_task(state).await?.stop(&mut ops)?;
+ state.replica().commit_operations(ops).await?;
Ok(())
}
- pub fn description(&self, state: &mut State) -> Result<String> {
- Ok(self.as_task(state)?.get_description().to_owned())
+ pub async fn description(&self, state: &mut State) -> Result<String> {
+ Ok(self.as_task(state).await?.get_description().to_owned())
}
- pub fn project(&self, state: &mut State) -> Result<Project> {
+ pub async fn project(&self, state: &mut State) -> Result<Project> {
let output = {
- let task = self.as_task(state)?;
+ let task = self.as_task(state).await?;
let task_data = task.into_task_data();
task_data
.get("project")
@@ -303,10 +308,11 @@ impl Project {
/// # Errors
/// When `task` execution fails.
- pub fn get_tasks(&self, state: &mut State) -> Result<Vec<Task>> {
+ pub async fn get_tasks(&self, state: &mut State) -> Result<Vec<Task>> {
Ok(state
.replica()
- .pending_task_data()?
+ .pending_task_data()
+ .await?
.into_iter()
.filter(|t| t.get("project").expect("Is set") == self.to_project_display())
.map(|t| Task::from(&t))
diff --git a/pkgs/by-name/ts/tskm/update.sh b/pkgs/by-name/ts/tskm/update.sh
index 8e36e13e..5ad524e8 100755
--- a/pkgs/by-name/ts/tskm/update.sh
+++ b/pkgs/by-name/ts/tskm/update.sh
@@ -10,5 +10,5 @@
# You should have received a copy of the License along with this program.
# If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
-[ "$1" = "upgrade" ] && cargo upgrade
-cargo update
+[ "$1" = "upgrade" ] && cargo upgrade --incompatible allow --pinned allow --recursive true
+cargo update --recursive
diff --git a/pkgs/by-name/yt/yt/package.nix b/pkgs/by-name/yt/yt/package.nix
index 4e1b2ecb..41234e19 100644
--- a/pkgs/by-name/yt/yt/package.nix
+++ b/pkgs/by-name/yt/yt/package.nix
@@ -34,6 +34,8 @@
}: let
version = "1.9.0";
python = python3.withPackages (ps: [ps.yt-dlp]);
+
+ mpv = mpv-unwrapped.override {ffmpeg = ffmpeg-headless;};
in
rustPlatform.buildRustPackage (finalAttrs: {
inherit version;
@@ -48,7 +50,7 @@ in
cargoHash = "sha256-U0alYK9mhz6esVf0mad9o7Ra6tRaL9HKCOftyOg34HE=";
buildInputs = [
- mpv-unwrapped.dev
+ mpv.dev
ffmpeg-headless
openssl
libffi
diff --git a/pkgs/update_pkgs.sh b/pkgs/update_pkgs.sh
index d036142e..17f97a35 100755
--- a/pkgs/update_pkgs.sh
+++ b/pkgs/update_pkgs.sh
@@ -40,8 +40,6 @@ fd . --type directory --max-depth 1 | while read -r shard; do
if [ -x update.sh ]; then
printf " \033[34;1m> \033[0m\033[34;1m%s\033[0m\n" "Running '${shard}${package}update.sh' .."
- [ -f flake.nix ] && nix flake update
-
direnv allow
eval "$(direnv export bash 2>/dev/null)"
./update.sh "$@"
diff --git a/scripts/npins_to_unflake.jq b/scripts/npins_to_unflake.jq
new file mode 100644
index 00000000..80e6b5a3
--- /dev/null
+++ b/scripts/npins_to_unflake.jq
@@ -0,0 +1,34 @@
+def make_url(value):
+ value.repository as $repo |
+ if value.type == "Git" then
+ if $repo.type == "Git" then
+ "git+\($repo.url)?ref=\(value.branch)"
+ elif $repo.type == "GitHub" then
+ "github:\($repo.owner)/\($repo.repo)/\(value.branch)"
+ elif $repo.type == "Forgejo" then
+ "git+\($repo.server)/\($repo.owner)/\($repo.repo)?ref=\(value.branch)"
+ else
+ error("Invalid repository type: '\($repo.type)'")
+ end
+ elif value.type == "GitRelease" then
+ if $repo.type == "GitHub" then
+ "github:\($repo.owner)/\($repo.repo)/\(value.version)"
+ else
+ error("Invalid repository type: '\($repo.type)'")
+ end
+ elif value.type == "Channel" then
+ empty
+ else
+ error("Invalid value type: '\(value.type)'")
+ end
+ ;
+
+"{
+\(
+ .pins | to_entries | map(
+ " \"\(.key)\" = {
+ url = \"\(make_url(.value))\";
+ };"
+ ) | join("\n")
+ )
+}"
diff --git a/scripts/unflake.sh b/scripts/unflake.sh
new file mode 100755
index 00000000..f916759a
--- /dev/null
+++ b/scripts/unflake.sh
@@ -0,0 +1,7 @@
+#! /usr/bin/env sh
+
+set -e
+
+NIX_CONFIG="pure-eval = false" nix run -f https://codeberg.org/goldstein/unflake/archive/main.tar.gz unflake -- "$@"
+
+# vim: ft=sh
diff --git a/scripts/why-depends b/scripts/why-depends
index 1afe9150..ae1ac6f7 100755
--- a/scripts/why-depends
+++ b/scripts/why-depends
@@ -1,23 +1,56 @@
#! /usr/bin/env sh
-search_string="$1-"
-shift 1
+main() {
+ use_running="$1"
+ host="$2"
+ package="$3"
-if [ "$1" != "" ]; then
- # Add the version
- search_string="${search_string}${1}$"
- shift 1
-fi
+ if [ "$use_running" = "true" ]; then
+ base="/run/current-system"
+ else
+ base=".#nixosConfigurations.$host.config.system.build.toplevel"
+ fi
+
+ fd "[a-zA-Z0-9]{32}-$package" /nix/store --type directory --threads 1 --exec nix --option warn-dirty false why-depends "$base"
+}
+
+running=false
+host="$(hostname)"
+packages=""
+direct_packages=""
+
+while [ "$#" -ne 0 ]; do
+ case "$1" in
+ "--running")
+ running=true
+ ;;
+ "--host")
+ shift 1
+ host="$1"
+ ;;
+ "--direct")
+ shift 1
+ direct_packages="$direct_packages $1"
+ ;;
+ *)
+ # Treat everything else as a package to check
+ packages="$packages $1"
+ ;;
+ esac
-if [ "$1" = "--running" ]; then
shift 1
- base="/run/current-system"
-else
- host="$(hostname)"
- base=".#nixosConfigurations.$host.config.system.build.toplevel"
-fi
+done
+if [ -n "$packages" ]; then
+ for package in $packages; do
+ main "$running" "$host" "$package-"
+ done
+fi
-fd "$search_string" /nix/store --type directory --threads 1 --exec nix why-depends "$@" "$base"
+if [ -n "$direct_packages" ]; then
+ for package in $direct_packages; do
+ main "$running" "$host" "$package-[0-9]"
+ done
+fi
# vim: ft=sh
diff --git a/secrets.nix b/secrets.nix
index fc5a9249..443ed562 100644
--- a/secrets.nix
+++ b/secrets.nix
@@ -14,6 +14,7 @@ let
apzu = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBivF5b6PyxsR/t+4Qg4IEDXHVXrjmZpslTUNXpvcVbO";
in {
"modules/by-name/at/atuin/secrets/encryption_key.age".publicKeys = [soispha tiamat apzu];
+ "modules/by-name/at/atuin/secrets/user_id.age".publicKeys = [soispha tiamat apzu];
"modules/by-name/ta/taskwarrior/secrets/sync_server_encryption_key.age".publicKeys = [soispha tiamat apzu];
diff --git a/unflake.nix b/unflake.nix
new file mode 100644
index 00000000..a4ce51fb
--- /dev/null
+++ b/unflake.nix
@@ -0,0 +1,393 @@
+# @generated by https://codeberg.org/goldstein/unflake
+let
+ indirect_deps = {
+ };
+ specs = rec {
+ unflake_github_cachix_pre-commit-hooks-nix = {
+ type = "github";
+ owner = "cachix";
+ repo = "pre-commit-hooks.nix";
+ rev = "809414f0cdadf82cf11b06c2b29ba9b3168b3297";
+ lastModified = 1787424939;
+ narHash = "sha256-O2tBn84NNuHrnqNVxx/XqsXwfYvS1YwBh+7CBnbCYsk=";
+ };
+ unflake_github_hercules-ci_flake-parts = {
+ type = "github";
+ owner = "hercules-ci";
+ repo = "flake-parts";
+ rev = "427bf4bd9435fdf21321c8cc628c24efc14c0f7a";
+ lastModified = 1785627969;
+ narHash = "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=";
+ };
+ unflake_github_ipetkov_crane = {
+ type = "github";
+ owner = "ipetkov";
+ repo = "crane";
+ rev = "692f7e9ef2ece8125b466f66f2af532b3edaed0d";
+ lastModified = 1787326676;
+ narHash = "sha256-lWhBbBvC05/xwivKBBiM2YNizpmgqCgyOIzomvRuwxs=";
+ };
+ unflake_github_lnl7_nix-darwin_ref_master = {
+ type = "github";
+ owner = "lnl7";
+ repo = "nix-darwin";
+ rev = "4cff07de74b50e64bdd68cd4e722ab5b6b35ee48";
+ lastModified = 1786845137;
+ narHash = "sha256-oQFip+v0luP8NIxJzmiW4Wu8bILsbFWom5l0zonl8hQ=";
+ };
+ unflake_github_nix-community_nix-index-database_ref_main = {
+ type = "github";
+ owner = "nix-community";
+ repo = "nix-index-database";
+ rev = "c7962dc97b45129df8d751bedaf37beb5a17706e";
+ lastModified = 1786852476;
+ narHash = "sha256-IM5CYtf86W4w8eUPpKcY/LpdHElmVBtJhaKnoTKxZEA=";
+ };
+ unflake_github_nix-community_nixvim_ref_main = {
+ type = "github";
+ owner = "nix-community";
+ repo = "nixvim";
+ rev = "bd46166bd8308861cd36984a52ed5d25522d0ba8";
+ lastModified = 1787270640;
+ narHash = "sha256-L3ZXW7MSCPsOZz5powJZZqzZLxuPLh9QXHp5shwzqb8=";
+ };
+ unflake_github_nix-community_disko_ref_master = {
+ type = "github";
+ owner = "nix-community";
+ repo = "disko";
+ rev = "ff8702b4de27f72b4c78573dfb89ec74e36abdf1";
+ lastModified = 1781152676;
+ narHash = "sha256-RxWs5ND31KzTG7wvMM+PMfUjyNpmIEr999lqNARaM5o=";
+ };
+ unflake_github_nix-community_home-manager_ref_master = {
+ type = "github";
+ owner = "nix-community";
+ repo = "home-manager";
+ rev = "ec1a8fdf74ed3f276148ee106299a2ba0e65d51f";
+ lastModified = 1787379775;
+ narHash = "sha256-XL2qfqmocfsvuEGyomZg4nOZfkuSe8vZtrOX99I+b6I=";
+ };
+ unflake_github_nix-community_impermanence_ref_master = {
+ type = "github";
+ owner = "nix-community";
+ repo = "impermanence";
+ rev = "7b1d382faf603b6d264f58627330f9faa5cba149";
+ lastModified = 1769548169;
+ narHash = "sha256-03+JxvzmfwRu+5JafM0DLbxgHttOQZkUtDWBmeUkN8Y=";
+ };
+ unflake_github_nix-community_nixos-generators_ref_master = {
+ type = "github";
+ owner = "nix-community";
+ repo = "nixos-generators";
+ rev = "8946737ff703382fda7623b9fab071d037e897d5";
+ lastModified = 1769813415;
+ narHash = "sha256-nnVmNNKBi1YiBNPhKclNYDORoHkuKipoz7EtVnXO50A=";
+ };
+ unflake_github_nix-community_lanzaboote_ref_v1-1-0 = {
+ type = "github";
+ owner = "nix-community";
+ repo = "lanzaboote";
+ rev = "7c9a54a7f87b4539ddbd8bda09a8a5f5f9361aa9";
+ lastModified = 1782141370;
+ narHash = "sha256-hqijVSEETttmo8Okql9/LG0Ua34hdciKW1a5zzlj8mU=";
+ };
+ unflake_github_nix-community_nixpkgs-lib = {
+ type = "github";
+ owner = "nix-community";
+ repo = "nixpkgs.lib";
+ rev = "6c072ec3b5c474f370866628582bb6533fe6f822";
+ lastModified = 1786841782;
+ narHash = "sha256-auVxpg6qxDnBG7bhSgqcolnbzsNfYvUNTyvOZJoHcrY=";
+ };
+ unflake_github_nix-systems_default_ref_main = {
+ type = "github";
+ owner = "nix-systems";
+ repo = "default";
+ rev = "da67096a3b9bf56a91d16901293e51ba5b49a27e";
+ lastModified = 1681028828;
+ narHash = "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=";
+ };
+ unflake_github_nixos_nixpkgs_ref_nixos-unstable = {
+ type = "github";
+ owner = "nixos";
+ repo = "nixpkgs";
+ rev = "2c423e03bbafcff28bfadc6781a4a8257f205cb5";
+ lastModified = 1787360063;
+ narHash = "sha256-dt4WdcvsA8/RCe+VZZwqU0X+XMM3wBbGCWA0/sFWzGo=";
+ };
+ unflake_github_nixos_flake-compat = {
+ type = "github";
+ owner = "nixos";
+ repo = "flake-compat";
+ rev = "5edf11c44bc78a0d334f6334cdaf7d60d732daab";
+ lastModified = 1767039857;
+ narHash = "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=";
+ };
+ unflake_github_numtide_treefmt-nix_ref_main = {
+ type = "github";
+ owner = "numtide";
+ repo = "treefmt-nix";
+ rev = "27b3b12a8e6375f28ebe122f07d230ca5459bbfa";
+ lastModified = 1786901030;
+ narHash = "sha256-WSFCsDSE5ffgD2MqzkM2CYjeFiKhRF/dJUN8uedb6YE=";
+ };
+ unflake_github_numtide_flake-utils = {
+ type = "github";
+ owner = "numtide";
+ repo = "flake-utils";
+ rev = "11707dc2f618dd54ca8739b309ec4fc024de578b";
+ lastModified = 1731533236;
+ narHash = "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=";
+ };
+ unflake_github_oxalica_rust-overlay = {
+ type = "github";
+ owner = "oxalica";
+ repo = "rust-overlay";
+ rev = "fd2ebb9cc4323d0c5a1336138dab5c3c5a5d8bd9";
+ lastModified = 1787367552;
+ narHash = "sha256-YT4Fs2k7bi+7YzuLt93EtIRgjpwHK5ZfsQEIh5dEQSk=";
+ };
+ unflake_github_ryantm_agenix_ref_main = {
+ type = "github";
+ owner = "ryantm";
+ repo = "agenix";
+ rev = "b027ee29d959fda4b60b57566d64c98a202e0feb";
+ lastModified = 1770165109;
+ narHash = "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=";
+ };
+ unflake_git_https---git-foss-syndicate-org-bpeetz-forks-atuin_ref_main = {
+ type = "git";
+ url = "https://git.foss-syndicate.org/bpeetz/forks/atuin";
+ ref = "main";
+ rev = "9a78305b20aba97617a94ab8e52acd5719593a93";
+ revCount = 1861;
+ lastModified = 1787516633;
+ narHash = "sha256-eSitueOrO/xYyMP437GVPMJLg3NxF52KVvhal4/3Pa8=";
+ };
+ unflake_git_https---git-lix-systems-lix-project-flake-compat_ref_main = {
+ type = "git";
+ url = "https://git.lix.systems/lix-project/flake-compat";
+ ref = "main";
+ rev = "382052b74656a369c5408822af3f2501e9b1af81";
+ revCount = 94;
+ lastModified = 1777699697;
+ narHash = "sha256-Eg9b/rq/ECYwNwEXs5i9wHyhxNI0JrYx2srdI2uZMaQ=";
+ };
+ unflake_git_https---codeberg-org-bpeetz-flake-templates_ref_prime = {
+ type = "git";
+ url = "https://codeberg.org/bpeetz/flake-templates";
+ ref = "prime";
+ rev = "0294fb03df7c265f8fae24a9e775d69a953bbf03";
+ revCount = 204;
+ lastModified = 1733053857;
+ narHash = "sha256-rI1qMFzbXVjfEvmf2OS4upnibXpL21its6cCXqhz86o=";
+ };
+ unflake_git_https---git-foss-syndicate-org-bpeetz-devshells_ref_prime = {
+ type = "git";
+ url = "https://git.foss-syndicate.org/bpeetz/devshells";
+ ref = "prime";
+ rev = "eeadc1096d4f122e09109bb10195ee9a8b6e3be6";
+ revCount = 3;
+ lastModified = 1786571173;
+ narHash = "sha256-qnSNf9dhKAAFmRTDKCzz8+PDyAvQ+wG3vCoOJCChdxU=";
+ };
+ unflake_git_https---git-foss-syndicate-org-bpeetz-qmk_layout_ref_prime = {
+ type = "git";
+ url = "https://git.foss-syndicate.org/bpeetz/qmk_layout";
+ ref = "prime";
+ rev = "4dff2e6ba5c9c80de3e3d2213ad28802814c3bba";
+ revCount = 39;
+ lastModified = 1753517111;
+ narHash = "sha256-dv5P3ahDICDacdzEmcyxrtKgbRWhVFiKQaLEz+WniGM=";
+ };
+ unflake_git_https---git-foss-syndicate-org-vhack-eu-nix-library_ref_prime = {
+ type = "git";
+ url = "https://git.foss-syndicate.org/vhack.eu/nix-library";
+ ref = "prime";
+ rev = "65bf71bb6ef05ce684924a1dc248bb2e8e2869fb";
+ revCount = 17;
+ lastModified = 1738443114;
+ narHash = "sha256-IV7n/l3rFoz5UuavrDv0a7IIOPne0jDQVmJAR8bve8U=";
+ };
+ unflake_tarball_https---channels-nixos-org-nixos-26-05-nixexprs-tar-zst = {
+ type = "tarball";
+ url = "https://releases.nixos.org/nixos/26.05/nixos-26.05.8111.5880666fd9eb/nixexprs.tar.zst?lastModified=1787204541";
+ rev = "5880666fd9eb563038431edb35c2d0aa595884e6";
+ lastModified = 1787204541;
+ narHash = "sha256-Rb7J0ykni6QA+m5ZC6t5gbBBgkS0xn3bf2fgKpSSbFE=";
+ };
+ unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst = {
+ type = "tarball";
+ url = "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1059570.2c423e03bbaf/nixexprs.tar.zst?lastModified=1787360063";
+ rev = "2c423e03bbafcff28bfadc6781a4a8257f205cb5";
+ lastModified = 1787360063;
+ narHash = "sha256-95aJfHyQTLWslCXFVNB0odgmVkpdmDezQwTg9mhqV1E=";
+ };
+ unflake_github_nix-systems_default_ref_main_flake_false = unflake_github_nix-systems_default_ref_main;
+ unflake_github_nixos_flake-compat_flake_false = unflake_github_nixos_flake-compat;
+ };
+ deps = indirect_deps // builtins.mapAttrs (_: spec: builtins.fetchTree spec) specs;
+ injections = rec {
+ unflake_github_cachix_pre-commit-hooks-nix = {
+ flake-compat = "unflake_github_nixos_flake-compat_flake_false";
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ };
+ unflake_github_hercules-ci_flake-parts = {
+ nixpkgs-lib = "unflake_github_nix-community_nixpkgs-lib";
+ };
+ unflake_github_ipetkov_crane = {
+ };
+ unflake_github_lnl7_nix-darwin_ref_master = {
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ };
+ unflake_github_nix-community_nix-index-database_ref_main = {
+ nixpkgs = "unflake_github_nixos_nixpkgs_ref_nixos-unstable";
+ };
+ unflake_github_nix-community_nixvim_ref_main = {
+ flake-parts = "unflake_github_hercules-ci_flake-parts";
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ systems = "unflake_github_nix-systems_default_ref_main_flake_false";
+ };
+ unflake_github_nix-community_disko_ref_master = {
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ };
+ unflake_github_nix-community_home-manager_ref_master = {
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ };
+ unflake_github_nix-community_impermanence_ref_master = {
+ home-manager = "unflake_github_nix-community_home-manager_ref_master";
+ nixpkgs = "unflake_github_nixos_nixpkgs_ref_nixos-unstable";
+ };
+ unflake_github_nix-community_nixos-generators_ref_master = {
+ nixlib = "unflake_github_nix-community_nixpkgs-lib";
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ };
+ unflake_github_nix-community_lanzaboote_ref_v1-1-0 = {
+ crane = "unflake_github_ipetkov_crane";
+ nixpkgs = "unflake_github_nixos_nixpkgs_ref_nixos-unstable";
+ pre-commit = "unflake_github_cachix_pre-commit-hooks-nix";
+ rust-overlay = "unflake_github_oxalica_rust-overlay";
+ };
+ unflake_github_nix-community_nixpkgs-lib = {
+ };
+ unflake_github_nix-systems_default_ref_main = {
+ };
+ unflake_github_nixos_nixpkgs_ref_nixos-unstable = {
+ };
+ unflake_github_numtide_treefmt-nix_ref_main = {
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ };
+ unflake_github_numtide_flake-utils = {
+ systems = "unflake_github_nix-systems_default_ref_main";
+ };
+ unflake_github_oxalica_rust-overlay = {
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ };
+ unflake_github_ryantm_agenix_ref_main = {
+ darwin = "unflake_github_lnl7_nix-darwin_ref_master";
+ home-manager = "unflake_github_nix-community_home-manager_ref_master";
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-26-05-nixexprs-tar-zst";
+ systems = "unflake_github_nix-systems_default_ref_main";
+ };
+ unflake_git_https---git-foss-syndicate-org-bpeetz-forks-atuin_ref_main = {
+ crane = "unflake_github_ipetkov_crane";
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ };
+ unflake_git_https---git-lix-systems-lix-project-flake-compat_ref_main = {
+ };
+ unflake_git_https---codeberg-org-bpeetz-flake-templates_ref_prime = {
+ flake-utils = "unflake_github_numtide_flake-utils";
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ systems = "unflake_github_nix-systems_default_ref_main";
+ treefmt-nix = "unflake_github_numtide_treefmt-nix_ref_main";
+ };
+ unflake_git_https---git-foss-syndicate-org-bpeetz-devshells_ref_prime = {
+ library = "unflake_git_https---git-foss-syndicate-org-vhack-eu-nix-library_ref_prime";
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ };
+ unflake_git_https---git-foss-syndicate-org-bpeetz-qmk_layout_ref_prime = {
+ flake-utils = "unflake_github_numtide_flake-utils";
+ nixpkgs = "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst";
+ systems = "unflake_github_nix-systems_default_ref_main";
+ treefmt-nix = "unflake_github_numtide_treefmt-nix_ref_main";
+ };
+ unflake_git_https---git-foss-syndicate-org-vhack-eu-nix-library_ref_prime = {
+ };
+ unflake_tarball_https---channels-nixos-org-nixos-26-05-nixexprs-tar-zst = {
+ };
+ unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst = {
+ };
+ };
+ inject = name: flake_path: subdir:
+ let
+ inputs = builtins.mapAttrs (_: dep: universe.${dep}) injections.${name} // {
+ inherit self;
+ };
+ sourceInfo = deps.${name};
+ outPath = "${sourceInfo.outPath}${subdir}";
+ outputs = (import "${sourceInfo.outPath}/${flake_path}").outputs inputs;
+ self = outputs // sourceInfo // {
+ inherit inputs outputs outPath sourceInfo;
+ _type = "flake";
+ _flake = true;
+ };
+ in self;
+ universe = rec {
+ unflake_github_cachix_pre-commit-hooks-nix = inject "unflake_github_cachix_pre-commit-hooks-nix" "flake.nix" "";
+ unflake_github_hercules-ci_flake-parts = inject "unflake_github_hercules-ci_flake-parts" "flake.nix" "";
+ unflake_github_ipetkov_crane = inject "unflake_github_ipetkov_crane" "flake.nix" "";
+ unflake_github_lnl7_nix-darwin_ref_master = inject "unflake_github_lnl7_nix-darwin_ref_master" "flake.nix" "";
+ unflake_github_nix-community_nix-index-database_ref_main = inject "unflake_github_nix-community_nix-index-database_ref_main" "flake.nix" "";
+ unflake_github_nix-community_nixvim_ref_main = inject "unflake_github_nix-community_nixvim_ref_main" "flake.nix" "";
+ unflake_github_nix-community_disko_ref_master = inject "unflake_github_nix-community_disko_ref_master" "flake.nix" "";
+ unflake_github_nix-community_home-manager_ref_master = inject "unflake_github_nix-community_home-manager_ref_master" "flake.nix" "";
+ unflake_github_nix-community_impermanence_ref_master = inject "unflake_github_nix-community_impermanence_ref_master" "flake.nix" "";
+ unflake_github_nix-community_nixos-generators_ref_master = inject "unflake_github_nix-community_nixos-generators_ref_master" "flake.nix" "";
+ unflake_github_nix-community_lanzaboote_ref_v1-1-0 = inject "unflake_github_nix-community_lanzaboote_ref_v1-1-0" "flake.nix" "";
+ unflake_github_nix-community_nixpkgs-lib = inject "unflake_github_nix-community_nixpkgs-lib" "flake.nix" "";
+ unflake_github_nix-systems_default_ref_main_flake_false = deps.unflake_github_nix-systems_default_ref_main_flake_false;
+ unflake_github_nix-systems_default_ref_main = inject "unflake_github_nix-systems_default_ref_main" "flake.nix" "";
+ unflake_github_nixos_nixpkgs_ref_nixos-unstable = inject "unflake_github_nixos_nixpkgs_ref_nixos-unstable" "flake.nix" "";
+ unflake_github_nixos_flake-compat_flake_false = deps.unflake_github_nixos_flake-compat_flake_false;
+ unflake_github_numtide_treefmt-nix_ref_main = inject "unflake_github_numtide_treefmt-nix_ref_main" "flake.nix" "";
+ unflake_github_numtide_flake-utils = inject "unflake_github_numtide_flake-utils" "flake.nix" "";
+ unflake_github_oxalica_rust-overlay = inject "unflake_github_oxalica_rust-overlay" "flake.nix" "";
+ unflake_github_ryantm_agenix_ref_main = inject "unflake_github_ryantm_agenix_ref_main" "flake.nix" "";
+ unflake_git_https---git-foss-syndicate-org-bpeetz-forks-atuin_ref_main = inject "unflake_git_https---git-foss-syndicate-org-bpeetz-forks-atuin_ref_main" "flake.nix" "";
+ unflake_git_https---git-lix-systems-lix-project-flake-compat_ref_main = inject "unflake_git_https---git-lix-systems-lix-project-flake-compat_ref_main" "flake.nix" "";
+ unflake_git_https---codeberg-org-bpeetz-flake-templates_ref_prime = inject "unflake_git_https---codeberg-org-bpeetz-flake-templates_ref_prime" "flake.nix" "";
+ unflake_git_https---git-foss-syndicate-org-bpeetz-devshells_ref_prime = inject "unflake_git_https---git-foss-syndicate-org-bpeetz-devshells_ref_prime" "flake.nix" "";
+ unflake_git_https---git-foss-syndicate-org-bpeetz-qmk_layout_ref_prime = inject "unflake_git_https---git-foss-syndicate-org-bpeetz-qmk_layout_ref_prime" "flake.nix" "";
+ unflake_git_https---git-foss-syndicate-org-vhack-eu-nix-library_ref_prime = inject "unflake_git_https---git-foss-syndicate-org-vhack-eu-nix-library_ref_prime" "flake.nix" "";
+ unflake_tarball_https---channels-nixos-org-nixos-26-05-nixexprs-tar-zst = inject "unflake_tarball_https---channels-nixos-org-nixos-26-05-nixexprs-tar-zst" "flake.nix" "";
+ unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst = inject "unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst" "flake.nix" "";
+ };
+ inputs = {
+ agenix = universe.unflake_github_ryantm_agenix_ref_main;
+ devshells = universe.unflake_git_https---git-foss-syndicate-org-bpeetz-devshells_ref_prime;
+ disko = universe.unflake_github_nix-community_disko_ref_master;
+ flake-compat = universe.unflake_git_https---git-lix-systems-lix-project-flake-compat_ref_main;
+ home-manager = universe.unflake_github_nix-community_home-manager_ref_master;
+ impermanence = universe.unflake_github_nix-community_impermanence_ref_master;
+ lanzaboote = universe.unflake_github_nix-community_lanzaboote_ref_v1-1-0;
+ library = universe.unflake_git_https---git-foss-syndicate-org-vhack-eu-nix-library_ref_prime;
+ nix-index-database = universe.unflake_github_nix-community_nix-index-database_ref_main;
+ nixos-generators = universe.unflake_github_nix-community_nixos-generators_ref_master;
+ nixpkgs = universe.unflake_tarball_https---channels-nixos-org-nixos-unstable-nixexprs-tar-zst;
+ nixpkgs-stable = universe.unflake_tarball_https---channels-nixos-org-nixos-26-05-nixexprs-tar-zst;
+ nixvim = universe.unflake_github_nix-community_nixvim_ref_main;
+ qmk_firmware = universe.unflake_git_https---git-foss-syndicate-org-bpeetz-qmk_layout_ref_prime;
+ templates = universe.unflake_git_https---codeberg-org-bpeetz-flake-templates_ref_prime;
+ treefmt-nix = universe.unflake_github_numtide_treefmt-nix_ref_main;
+ turtle = universe.unflake_git_https---git-foss-syndicate-org-bpeetz-forks-atuin_ref_main;
+ };
+in inputs // {
+ withInputs = fn: let outputs = fn (inputs // { inherit self; }); self = outputs // {
+ inherit inputs outputs;
+ _type = "flake";
+ outPath = builtins.toString ./.;
+ }; in self;
+ __functor = self: self.withInputs;
+ self = throw "to use inputs.self, write `import ./unflake.nix (inputs: ...)`";
+ _unflake = { inherit specs deps injections; };
+}
diff --git a/update.sh b/update.sh
index 980df783..19522e59 100755
--- a/update.sh
+++ b/update.sh
@@ -16,12 +16,15 @@ __update_sh_run() {
printf "\033[35;1m> \033[0m\033[35;1m%s\033[0m\n" "Running '$(basename "$__update_sh_command") $*' .."
- "$__update_sh_command" "$@"
+ if ! "$__update_sh_command" "$@"; then
+ printf "ERROR: Command failed ('%s')\n" "$__update_sh_command"
+ exit 1
+ fi
unset __update_sh_command
}
-__update_sh_run nix flake update
+__update_sh_run unflake.sh --backend=nix
__update_sh_run ./pkgs/update_pkgs.sh "$@"
# __update_sh_run nix flake check