aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-05-20 16:10:21 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2024-05-20 16:14:26 +0200
commit368cb6b0d25db2ae23be42ad51584de059997e51 (patch)
tree3282e45d3ebced63c8498a47e83a255c35de620b /pkgs
parentrefactor(hm): Rename to `modules/home` (diff)
downloadnixos-config-368cb6b0d25db2ae23be42ad51584de059997e51.zip
refactor(sys): Modularize and move to `modules/system` or `pkgs`
Diffstat (limited to 'pkgs')
-rw-r--r--pkgs/default.nix24
-rw-r--r--pkgs/sources/comments/.envrc4
-rw-r--r--pkgs/sources/comments/.gitignore3
-rw-r--r--pkgs/sources/comments/Cargo.lock446
-rw-r--r--pkgs/sources/comments/Cargo.toml16
-rw-r--r--pkgs/sources/comments/comments.nix25
-rw-r--r--pkgs/sources/comments/default.nix18
-rw-r--r--pkgs/sources/comments/flake.lock61
-rw-r--r--pkgs/sources/comments/flake.nix31
-rw-r--r--pkgs/sources/comments/src/info_json.rs223
-rw-r--r--pkgs/sources/comments/src/main.rs322
-rwxr-xr-xpkgs/sources/comments/update.sh8
-rw-r--r--pkgs/sources/default.nix27
-rw-r--r--pkgs/sources/generate_moz_extension/.envrc1
-rw-r--r--pkgs/sources/generate_moz_extension/.gitignore3
-rw-r--r--pkgs/sources/generate_moz_extension/Cargo.lock1275
-rw-r--r--pkgs/sources/generate_moz_extension/Cargo.toml14
-rw-r--r--pkgs/sources/generate_moz_extension/default.nix16
-rwxr-xr-xpkgs/sources/generate_moz_extension/examples/generate_extensions.sh17
-rw-r--r--pkgs/sources/generate_moz_extension/flake.lock106
-rw-r--r--pkgs/sources/generate_moz_extension/flake.nix75
-rw-r--r--pkgs/sources/generate_moz_extension/generate_firefox_extensions.nix20
-rw-r--r--pkgs/sources/generate_moz_extension/res/generate_extensions.py44
-rw-r--r--pkgs/sources/generate_moz_extension/res/reference.json30
-rw-r--r--pkgs/sources/generate_moz_extension/res/test.json30
-rw-r--r--pkgs/sources/generate_moz_extension/src/main.rs138
-rw-r--r--pkgs/sources/generate_moz_extension/src/types.rs71
-rwxr-xr-xpkgs/sources/generate_moz_extension/update.sh8
-rw-r--r--pkgs/sources/lf-make-map/.envrc11
-rw-r--r--pkgs/sources/lf-make-map/.gitignore6
-rw-r--r--pkgs/sources/lf-make-map/Cargo.lock505
-rw-r--r--pkgs/sources/lf-make-map/Cargo.toml14
-rw-r--r--pkgs/sources/lf-make-map/README.md12
-rw-r--r--pkgs/sources/lf-make-map/default.nix12
-rw-r--r--pkgs/sources/lf-make-map/flake.lock147
-rw-r--r--pkgs/sources/lf-make-map/flake.nix125
-rw-r--r--pkgs/sources/lf-make-map/lf_make_map.nix10
-rw-r--r--pkgs/sources/lf-make-map/src/cli.rs49
-rw-r--r--pkgs/sources/lf-make-map/src/main.rs229
-rw-r--r--pkgs/sources/lf-make-map/src/mapping/map_tree/display.rs91
-rw-r--r--pkgs/sources/lf-make-map/src/mapping/map_tree/iterator.rs53
-rw-r--r--pkgs/sources/lf-make-map/src/mapping/map_tree/lf_mapping.rs19
-rw-r--r--pkgs/sources/lf-make-map/src/mapping/map_tree/mod.rs402
-rw-r--r--pkgs/sources/lf-make-map/src/mapping/mod.rs156
-rwxr-xr-xpkgs/sources/lf-make-map/update.sh6
-rw-r--r--pkgs/sources/plgs-pkgs/README.md92
-rw-r--r--pkgs/sources/plgs-pkgs/check.nix37
-rw-r--r--pkgs/sources/plgs-pkgs/default.nix15
-rw-r--r--pkgs/sources/plgs-pkgs/overrides.nix34
-rw-r--r--pkgs/sources/plgs-pkgs/plugins/.plugins.json7
-rw-r--r--pkgs/sources/plgs-pkgs/plugins/blacklist.txt1
-rw-r--r--pkgs/sources/plgs-pkgs/plugins/default.nix55
-rw-r--r--pkgs/sources/plgs-pkgs/plugins/manifest.txt3
-rw-r--r--pkgs/sources/plgs-pkgs/plugins/plugins.md7
-rw-r--r--pkgs/sources/plgs-pkgs/plugins/whitelist.txt0
-rwxr-xr-xpkgs/sources/plgs-pkgs/update.sh27
-rw-r--r--pkgs/sources/scripts/default.nix412
-rwxr-xr-xpkgs/sources/scripts/source/apps/aumo.sh28
-rwxr-xr-xpkgs/sources/scripts/source/apps/con2pdf.sh234
-rw-r--r--pkgs/sources/scripts/source/apps/fupdate.1.md70
-rwxr-xr-xpkgs/sources/scripts/source/apps/fupdate.sh197
-rwxr-xr-xpkgs/sources/scripts/source/apps/git-edit-index.sh98
-rwxr-xr-xpkgs/sources/scripts/source/small_functions/brightness.sh80
-rwxr-xr-xpkgs/sources/scripts/source/small_functions/nato.py106
-rwxr-xr-xpkgs/sources/scripts/source/small_functions/screenshot_persistent.sh22
-rwxr-xr-xpkgs/sources/scripts/source/small_functions/screenshot_temporary.sh8
-rwxr-xr-xpkgs/sources/scripts/source/small_functions/update-sys.sh85
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/neorg_id_function.sh16
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/add.sh23
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/context.sh43
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/dmenu.sh14
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/f_start.sh7
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/f_stop.sh7
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/list.sh8
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/project.sh41
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/review.sh12
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/utils.sh40
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/functions/workspace.sh9
-rwxr-xr-xpkgs/sources/scripts/source/specific/neorg/sh/main.sh164
-rwxr-xr-xpkgs/sources/scripts/source/specific/spodi/sh/download.sh58
-rwxr-xr-xpkgs/sources/scripts/source/specific/spodi/sh/update.sh52
-rwxr-xr-xpkgs/sources/scripts/source/specific/spodi/spodi.sh71
-rwxr-xr-xpkgs/sources/scripts/source/specific/ytcc/description.sh8
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/battery.sh11
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/hibernate.sh15
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/ll.sh14
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/lock.sh18
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/lyrics.sh11
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/mpc-fav.sh16
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/mpc-rm.sh10
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/mpc.sh20
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/show.sh9
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/sort_song.sh34
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/virsh-del.sh10
-rwxr-xr-xpkgs/sources/scripts/source/wrappers/yti.sh33
-rw-r--r--pkgs/sources/snap-sync-forked/default.nix24
-rwxr-xr-xpkgs/sources/snap-sync-forked/snap-sync-forked.sh534
-rw-r--r--pkgs/sources/tree-sitter-yts/.editorconfig21
-rw-r--r--pkgs/sources/tree-sitter-yts/.envrc1
-rw-r--r--pkgs/sources/tree-sitter-yts/.gitignore3
-rw-r--r--pkgs/sources/tree-sitter-yts/Cargo.toml21
-rw-r--r--pkgs/sources/tree-sitter-yts/binding.gyp19
-rw-r--r--pkgs/sources/tree-sitter-yts/bindings/node/binding.cc33
-rw-r--r--pkgs/sources/tree-sitter-yts/bindings/node/index.js19
-rw-r--r--pkgs/sources/tree-sitter-yts/bindings/rust/build.rs40
-rw-r--r--pkgs/sources/tree-sitter-yts/bindings/rust/lib.rs52
-rw-r--r--pkgs/sources/tree-sitter-yts/corpus/comments.txt51
-rw-r--r--pkgs/sources/tree-sitter-yts/corpus/comments_correct.txt27
-rw-r--r--pkgs/sources/tree-sitter-yts/corpus/duration.txt84
-rw-r--r--pkgs/sources/tree-sitter-yts/corpus/url.txt84
-rw-r--r--pkgs/sources/tree-sitter-yts/default.nix11
-rw-r--r--pkgs/sources/tree-sitter-yts/flake.lock97
-rw-r--r--pkgs/sources/tree-sitter-yts/flake.nix82
-rw-r--r--pkgs/sources/tree-sitter-yts/grammar.js26
-rw-r--r--pkgs/sources/tree-sitter-yts/highlight.yts4
-rw-r--r--pkgs/sources/tree-sitter-yts/package.json31
-rw-r--r--pkgs/sources/tree-sitter-yts/package.nix63
-rw-r--r--pkgs/sources/tree-sitter-yts/queries/highlights.scm11
-rw-r--r--pkgs/sources/tree-sitter-yts/src/grammar.json238
-rw-r--r--pkgs/sources/tree-sitter-yts/src/node-types.json200
-rw-r--r--pkgs/sources/tree-sitter-yts/src/parser.c1108
-rw-r--r--pkgs/sources/tree-sitter-yts/src/tree_sitter/parser.h241
-rw-r--r--pkgs/sources/tree-sitter-yts/treefmt.toml35
-rwxr-xr-xpkgs/sources/update_pkgs.sh25
-rw-r--r--pkgs/sources/update_vim_plugins/.envrc1
-rwxr-xr-xpkgs/sources/update_vim_plugins/check-duplicates.sh43
-rw-r--r--pkgs/sources/update_vim_plugins/default.nix17
-rw-r--r--pkgs/sources/update_vim_plugins/flake.lock61
-rw-r--r--pkgs/sources/update_vim_plugins/flake.nix24
-rw-r--r--pkgs/sources/update_vim_plugins/package.nix47
-rw-r--r--pkgs/sources/update_vim_plugins/poetry.lock680
-rw-r--r--pkgs/sources/update_vim_plugins/pyproject.toml45
-rwxr-xr-xpkgs/sources/update_vim_plugins/update.sh5
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/__init__.py0
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/__main__.py15
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/cleanup.py100
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/helpers.py61
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/nix.py121
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/plugin.py182
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/spec.py143
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/tests/__init__.py0
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/tests/fixtures.py44
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_nix.py32
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_plugin.py144
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_spec.py136
-rw-r--r--pkgs/sources/update_vim_plugins/update_vim_plugins/update.py212
-rwxr-xr-xpkgs/sources/yt/.env3
-rw-r--r--pkgs/sources/yt/.envrc1
-rw-r--r--pkgs/sources/yt/.gitignore3
-rw-r--r--pkgs/sources/yt/Cargo.lock640
-rw-r--r--pkgs/sources/yt/Cargo.toml35
-rw-r--r--pkgs/sources/yt/default.nix51
-rw-r--r--pkgs/sources/yt/flake.lock61
-rw-r--r--pkgs/sources/yt/flake.nix30
-rw-r--r--pkgs/sources/yt/src/bin/yt/main.rs91
-rw-r--r--pkgs/sources/yt/src/bin/ytc/args.rs26
-rw-r--r--pkgs/sources/yt/src/bin/ytc/main.rs77
-rw-r--r--pkgs/sources/yt/src/bin/yts/args.rs41
-rw-r--r--pkgs/sources/yt/src/bin/yts/main.rs91
-rw-r--r--pkgs/sources/yt/src/constants.rs51
-rw-r--r--pkgs/sources/yt/src/downloader.rs212
-rw-r--r--pkgs/sources/yt/src/help.str8
-rw-r--r--pkgs/sources/yt/src/lib.rs185
-rw-r--r--pkgs/sources/yt/todo1
-rwxr-xr-xpkgs/sources/yt/update.sh8
-rw-r--r--pkgs/sources/yt/yt.nix29
-rw-r--r--pkgs/sources/yt/ytc.nix29
-rw-r--r--pkgs/sources/yt/yts.nix27
168 files changed, 14293 insertions, 0 deletions
diff --git a/pkgs/default.nix b/pkgs/default.nix
new file mode 100644
index 00000000..38c58a95
--- /dev/null
+++ b/pkgs/default.nix
@@ -0,0 +1,24 @@
+{
+ lib,
+ system,
+ overlays ? [],
+ sysLib,
+ homeConfig,
+ nixosConfig,
+}: let
+ additionalPackages = (import ./pkgs) {inherit homeConfig nixosConfig sysLib;};
+ complete_overlays = overlays ++ additionalPackages;
+in {
+ # TODO: inheriting system here is discouraged, localSystem or hostSystem should be inspected
+ inherit system;
+ overlays = complete_overlays;
+ config = {
+ # TODO: this fails because of the root tempsize, which should be increased
+ # contentAddressedByDefault = true;
+
+ allowUnfreePredicate = pkg:
+ builtins.elem (lib.getName pkg) [
+ "pypemicro" # required by pynitrokey
+ ];
+ };
+}
diff --git a/pkgs/sources/comments/.envrc b/pkgs/sources/comments/.envrc
new file mode 100644
index 00000000..2b5fbb29
--- /dev/null
+++ b/pkgs/sources/comments/.envrc
@@ -0,0 +1,4 @@
+use flake
+
+PATH_add ./target/debug
+PATH_add ./target/release
diff --git a/pkgs/sources/comments/.gitignore b/pkgs/sources/comments/.gitignore
new file mode 100644
index 00000000..c84fa049
--- /dev/null
+++ b/pkgs/sources/comments/.gitignore
@@ -0,0 +1,3 @@
+# build dirs
+/target
+/result
diff --git a/pkgs/sources/comments/Cargo.lock b/pkgs/sources/comments/Cargo.lock
new file mode 100644
index 00000000..54f19c46
--- /dev/null
+++ b/pkgs/sources/comments/Cargo.lock
@@ -0,0 +1,446 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "cc"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets",
+]
+
+[[package]]
+name = "chrono-humanize"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b"
+dependencies = [
+ "chrono",
+]
+
+[[package]]
+name = "cli-log"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d2ab00dc4c82ec28af25ac085aecc11ffeabf353755715a3113a7aa044ca5cc"
+dependencies = [
+ "chrono",
+ "file-size",
+ "log",
+ "proc-status",
+]
+
+[[package]]
+name = "comments"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "chrono",
+ "chrono-humanize",
+ "cli-log",
+ "log",
+ "regex",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "file-size"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9544f10105d33957765016b8a9baea7e689bf1f0f2f32c2fa2f568770c38d2b3"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "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 = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.154"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proc-status"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0e0c0ac915e7b76b47850ba4ffc377abde6c6ff9eeace61d0a89623db449712"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "regex"
+version = "1.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "serde"
+version = "1.0.201"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.201"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+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.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
diff --git a/pkgs/sources/comments/Cargo.toml b/pkgs/sources/comments/Cargo.toml
new file mode 100644
index 00000000..3ae3aa4c
--- /dev/null
+++ b/pkgs/sources/comments/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "comments"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.82"
+chrono = "0.4.38"
+chrono-humanize = "0.2.3"
+cli-log = "2.0.0"
+log = "0.4.21"
+regex = "1.10.4"
+serde = { version = "1.0.200", features = ["derive"] }
+serde_json = "1.0.116"
diff --git a/pkgs/sources/comments/comments.nix b/pkgs/sources/comments/comments.nix
new file mode 100644
index 00000000..e8a33bff
--- /dev/null
+++ b/pkgs/sources/comments/comments.nix
@@ -0,0 +1,25 @@
+{
+ lib,
+ rustPlatform,
+ makeWrapper,
+ less,
+ coreutils,
+}:
+rustPlatform.buildRustPackage {
+ pname = "comments";
+ version = "0.1.0";
+
+ src = ./.;
+ cargoLock = {
+ lockFile = ./Cargo.lock;
+ };
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ postInstall = ''
+ wrapProgram $out/bin/comments \
+ --set PATH ${lib.makeBinPath [less coreutils]}
+ '';
+}
diff --git a/pkgs/sources/comments/default.nix b/pkgs/sources/comments/default.nix
new file mode 100644
index 00000000..6205dcbe
--- /dev/null
+++ b/pkgs/sources/comments/default.nix
@@ -0,0 +1,18 @@
+[
+ (
+ final: prev: {
+ comments = import ./comments.nix {
+ inherit
+ (prev)
+ lib
+ makeWrapper
+ rustPlatform
+ # dependencies
+
+ less
+ coreutils
+ ;
+ };
+ }
+ )
+]
diff --git a/pkgs/sources/comments/flake.lock b/pkgs/sources/comments/flake.lock
new file mode 100644
index 00000000..50494465
--- /dev/null
+++ b/pkgs/sources/comments/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1715087517,
+ "narHash": "sha256-CLU5Tsg24Ke4+7sH8azHWXKd0CFd4mhLWfhYgUiDBpQ=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "b211b392b8486ee79df6cdfb1157ad2133427a29",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/pkgs/sources/comments/flake.nix b/pkgs/sources/comments/flake.nix
new file mode 100644
index 00000000..f5e44a65
--- /dev/null
+++ b/pkgs/sources/comments/flake.nix
@@ -0,0 +1,31 @@
+{
+ description = "comments";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ flake-utils,
+ }: (flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = nixpkgs.legacyPackages."${system}";
+ in {
+ devShells.default = pkgs.mkShell {
+ packages = with pkgs; [
+ # rust stuff
+ cargo
+ clippy
+ rustc
+ rustfmt
+
+ cargo-edit
+ cargo-expand
+ cargo-audit
+ ];
+ };
+ }));
+}
diff --git a/pkgs/sources/comments/src/info_json.rs b/pkgs/sources/comments/src/info_json.rs
new file mode 100644
index 00000000..eca4fae3
--- /dev/null
+++ b/pkgs/sources/comments/src/info_json.rs
@@ -0,0 +1,223 @@
+use std::collections::HashMap;
+
+use serde::{Deserialize, Deserializer};
+
+#[derive(Debug, Deserialize)]
+pub struct InfoJson {
+ pub id: String,
+ pub title: String,
+ pub formats: Vec<Format>,
+ pub thumbnails: Vec<ThumbNail>,
+ pub thumbnail: String,
+ pub description: String,
+ pub channel_id: String,
+ pub channel_url: String,
+ pub duration: u32,
+ pub view_count: u32,
+ pub age_limit: u32,
+ pub webpage_url: String,
+ pub categories: Vec<String>,
+ pub tags: Vec<String>,
+ pub playable_in_embed: bool,
+ pub live_status: String,
+ _format_sort_fields: Vec<String>,
+ pub automatic_captions: HashMap<String, Vec<Caption>>,
+ pub subtitles: Subtitles,
+ pub comment_count: u32,
+ pub like_count: u32,
+ pub channel: String,
+ pub channel_follower_count: u32,
+ pub channel_is_verified: Option<bool>,
+ pub uploader: String,
+ pub uploader_id: String,
+ pub uploader_url: String,
+ pub upload_date: String,
+ pub availability: String,
+ pub webpage_url_basename: String,
+ pub webpage_url_domain: String,
+ pub extractor: String,
+ pub extractor_key: String,
+ pub display_id: String,
+ pub fulltitle: String,
+ pub duration_string: String,
+ pub is_live: bool,
+ pub was_live: bool,
+ pub epoch: u32,
+ pub comments: Vec<Comment>,
+ pub sponsorblock_chapters: Option<Vec<SponsorblockChapter>>,
+ pub format: String,
+ pub format_id: String,
+ pub ext: String,
+ pub protocol: String,
+ pub language: Option<String>,
+ pub format_note: String,
+ pub filesize_approx: u64,
+ pub tbr: f64,
+ pub width: u32,
+ pub height: u32,
+ pub resolution: String,
+ pub fps: f64,
+ pub dynamic_range: String,
+ pub vcodec: String,
+ pub vbr: f64,
+ pub aspect_ratio: f64,
+ pub acodec: String,
+ pub abr: f64,
+ pub asr: u32,
+ pub audio_channels: u32,
+ _type: String,
+ _version: Version,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Subtitles {}
+
+#[derive(Debug, Deserialize)]
+pub struct Version {
+ pub version: String,
+ pub release_git_head: String,
+ pub repository: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct SponsorblockChapter {}
+
+#[derive(Debug, Deserialize, Clone)]
+#[serde(from = "String")]
+pub enum Parent {
+ Root,
+ Id(String),
+}
+
+impl Parent {
+ pub fn id(&self) -> Option<&str> {
+ if let Self::Id(id) = self {
+ Some(id)
+ } else {
+ None
+ }
+ }
+}
+
+impl From<String> for Parent {
+ fn from(value: String) -> Self {
+ if value == "root" {
+ Self::Root
+ } else {
+ Self::Id(value)
+ }
+ }
+}
+
+#[derive(Debug, Deserialize, Clone)]
+#[serde(from = "String")]
+pub struct Id {
+ pub id: String,
+}
+impl From<String> for Id {
+ fn from(value: String) -> Self {
+ Self {
+ // Take the last element if the string is split with dots, otherwise take the full id
+ id: value.split('.').last().unwrap_or(&value).to_owned(),
+ }
+ }
+}
+
+#[derive(Debug, Deserialize, Clone)]
+pub struct Comment {
+ pub id: Id,
+ pub text: String,
+ #[serde(default = "zero")]
+ pub like_count: u32,
+ pub author_id: String,
+ #[serde(default = "unknown")]
+ pub author: String,
+ pub author_thumbnail: String,
+ pub parent: Parent,
+ #[serde(deserialize_with = "edited_from_time_text", alias = "_time_text")]
+ pub edited: bool,
+ // Can't also be deserialized, as it's already used in 'edited'
+ // _time_text: String,
+ pub timestamp: i64,
+ pub author_url: String,
+ pub author_is_uploader: bool,
+ pub is_favorited: bool,
+}
+fn unknown() -> String {
+ "<Unknown>".to_string()
+}
+fn zero() -> u32 {
+ 0
+}
+fn edited_from_time_text<'de, D>(d: D) -> Result<bool, D::Error>
+where
+ D: Deserializer<'de>,
+{
+ let s = String::deserialize(d)?;
+ if s.contains(" (edited)") {
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Caption {
+ pub ext: String,
+ pub url: String,
+ pub name: Option<String>,
+ pub protocol: Option<String>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct ThumbNail {
+ pub url: String,
+ pub preference: i32,
+ pub id: String,
+ pub height: Option<u32>,
+ pub width: Option<u32>,
+ pub resolution: Option<String>,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Format {
+ pub format_id: String,
+ pub format_note: Option<String>,
+ pub ext: String,
+ pub protocol: String,
+ pub acodec: Option<String>,
+ pub vcodec: String,
+ pub url: String,
+ pub width: Option<u32>,
+ pub height: Option<u32>,
+ pub fps: Option<f64>,
+ pub rows: Option<u32>,
+ pub columns: Option<u32>,
+ pub fragments: Option<Vec<Fragment>>,
+ pub resolution: String,
+ pub aspect_ratio: Option<f64>,
+ pub http_headers: HttpHeader,
+ pub audio_ext: String,
+ pub video_ext: String,
+ pub vbr: Option<f64>,
+ pub abr: Option<f64>,
+ pub format: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct HttpHeader {
+ #[serde(alias = "User-Agent")]
+ pub user_agent: String,
+ #[serde(alias = "Accept")]
+ pub accept: String,
+ #[serde(alias = "Accept-Language")]
+ pub accept_language: String,
+ #[serde(alias = "Sec-Fetch-Mode")]
+ pub sec_fetch_mode: String,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct Fragment {
+ pub url: String,
+ pub duration: f64,
+}
diff --git a/pkgs/sources/comments/src/main.rs b/pkgs/sources/comments/src/main.rs
new file mode 100644
index 00000000..6e4f72e9
--- /dev/null
+++ b/pkgs/sources/comments/src/main.rs
@@ -0,0 +1,322 @@
+use std::{
+ env,
+ fmt::Display,
+ fs::{self, File},
+ io::{BufReader, Write},
+ mem,
+ path::PathBuf,
+ process::{Command, Stdio},
+};
+
+use anyhow::Context;
+use chrono::{Local, TimeZone};
+use chrono_humanize::{Accuracy, HumanTime, Tense};
+use info_json::{Comment, InfoJson, Parent};
+use regex::Regex;
+
+mod info_json;
+
+fn get_runtime_path(component: &'static str) -> anyhow::Result<PathBuf> {
+ let out: PathBuf = format!(
+ "{}/{}",
+ env::var("XDG_RUNTIME_DIR").expect("This should always exist"),
+ component
+ )
+ .into();
+ fs::create_dir_all(out.parent().expect("Parent should exist"))?;
+ Ok(out)
+}
+
+const STATUS_PATH: &str = "ytcc/running";
+pub fn status_path() -> anyhow::Result<PathBuf> {
+ get_runtime_path(STATUS_PATH)
+}
+
+#[derive(Debug, Clone)]
+pub struct CommentExt {
+ pub value: Comment,
+ pub replies: Vec<CommentExt>,
+}
+
+#[derive(Debug, Default)]
+pub struct Comments {
+ vec: Vec<CommentExt>,
+}
+
+impl Comments {
+ pub fn new() -> Self {
+ Self::default()
+ }
+ pub fn push(&mut self, value: CommentExt) {
+ self.vec.push(value);
+ }
+ pub fn get_mut(&mut self, key: &str) -> Option<&mut CommentExt> {
+ self.vec.iter_mut().filter(|c| c.value.id.id == key).last()
+ }
+ pub fn insert(&mut self, key: &str, value: CommentExt) {
+ let parent = self
+ .vec
+ .iter_mut()
+ .filter(|c| c.value.id.id == key)
+ .last()
+ .expect("One of these should exist");
+ parent.push_reply(value);
+ }
+}
+impl CommentExt {
+ pub fn push_reply(&mut self, value: CommentExt) {
+ self.replies.push(value)
+ }
+ pub fn get_mut_reply(&mut self, key: &str) -> Option<&mut CommentExt> {
+ self.replies
+ .iter_mut()
+ .filter(|c| c.value.id.id == key)
+ .last()
+ }
+}
+
+impl From<Comment> for CommentExt {
+ fn from(value: Comment) -> Self {
+ Self {
+ replies: vec![],
+ value,
+ }
+ }
+}
+
+impl Display for Comments {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ macro_rules! c {
+ ($color:expr, $write:ident) => {
+ $write.write_str(concat!("\x1b[", $color, "m"))?
+ };
+ }
+
+ fn format(
+ comment: &CommentExt,
+ f: &mut std::fmt::Formatter<'_>,
+ ident_count: u32,
+ ) -> std::fmt::Result {
+ let ident = &(0..ident_count).map(|_| " ").collect::<String>();
+ let value = &comment.value;
+
+ f.write_str(ident)?;
+
+ if value.author_is_uploader {
+ c!("91;1", f);
+ } else {
+ c!("35", f);
+ }
+
+ f.write_str(&value.author)?;
+ c!("0", f);
+ if value.edited || value.is_favorited {
+ f.write_str("[")?;
+ if value.edited {
+ f.write_str("")?;
+ }
+ if value.edited && value.is_favorited {
+ f.write_str(" ")?;
+ }
+ if value.is_favorited {
+ f.write_str("")?;
+ }
+ f.write_str("]")?;
+ }
+
+ c!("36;1", f);
+ write!(
+ f,
+ " {}",
+ HumanTime::from(
+ Local
+ .timestamp_opt(value.timestamp, 0)
+ .single()
+ .expect("This should be valid")
+ )
+ .to_text_en(Accuracy::Rough, Tense::Past)
+ )?;
+ c!("0", f);
+
+ // c!("31;1", f);
+ // f.write_fmt(format_args!(" [{}]", comment.value.like_count))?;
+ // c!("0", f);
+
+ f.write_str(":\n")?;
+ f.write_str(ident)?;
+
+ f.write_str(&value.text.replace('\n', &format!("\n{}", ident)))?;
+ f.write_str("\n")?;
+
+ if !comment.replies.is_empty() {
+ let mut children = comment.replies.clone();
+ children.sort_by(|a, b| a.value.timestamp.cmp(&b.value.timestamp));
+
+ for child in children {
+ format(&child, f, ident_count + 4)?;
+ }
+ } else {
+ f.write_str("\n")?;
+ }
+
+ Ok(())
+ }
+
+ if !&self.vec.is_empty() {
+ let mut children = self.vec.clone();
+ children.sort_by(|a, b| b.value.like_count.cmp(&a.value.like_count));
+
+ for child in children {
+ format(&child, f, 0)?
+ }
+ }
+ Ok(())
+ }
+}
+
+fn main() -> anyhow::Result<()> {
+ cli_log::init_cli_log!();
+ let args: Option<String> = env::args().skip(1).last();
+ let mut info_json: InfoJson = {
+ let status_path = if let Some(arg) = args {
+ PathBuf::from(arg)
+ } else {
+ status_path().context("Failed to get status path")?
+ };
+
+ let reader =
+ BufReader::new(File::open(&status_path).with_context(|| {
+ format!("Failed to open status file at {}", status_path.display())
+ })?);
+
+ serde_json::from_reader(reader)?
+ };
+
+ let base_comments = mem::take(&mut info_json.comments);
+ drop(info_json);
+
+ let mut comments = Comments::new();
+ base_comments.into_iter().for_each(|c| {
+ if let Parent::Id(id) = &c.parent {
+ comments.insert(&(id.clone()), CommentExt::from(c));
+ } else {
+ comments.push(CommentExt::from(c));
+ }
+ });
+
+ comments.vec.iter_mut().for_each(|comment| {
+ let replies = mem::take(&mut comment.replies);
+ let mut output_replies: Vec<CommentExt> = vec![];
+
+ let re = Regex::new(r"\u{200b}?(@[^\t\s]+)\u{200b}?").unwrap();
+ for reply in replies {
+ if let Some(replyee_match) = re.captures(&reply.value.text){
+ let full_match = replyee_match.get(0).expect("This always exists");
+ let text = reply.
+ value.
+ text[0..full_match.start()]
+ .to_owned()
+ +
+ &reply
+ .value
+ .text[full_match.end()..];
+ let text: &str = text.trim().trim_matches('\u{200b}');
+
+ let replyee = replyee_match.get(1).expect("This should exist").as_str();
+
+
+ if let Some(parent) = output_replies
+ .iter_mut()
+ // .rev()
+ .flat_map(|com| &mut com.replies)
+ .flat_map(|com| &mut com.replies)
+ .flat_map(|com| &mut com.replies)
+ .filter(|com| com.value.author == replyee)
+ .last()
+ {
+ parent.replies.push(CommentExt::from(Comment {
+ text: text.to_owned(),
+ ..reply.value
+ }))
+ } else if let Some(parent) = output_replies
+ .iter_mut()
+ // .rev()
+ .flat_map(|com| &mut com.replies)
+ .flat_map(|com| &mut com.replies)
+ .filter(|com| com.value.author == replyee)
+ .last()
+ {
+ parent.replies.push(CommentExt::from(Comment {
+ text: text.to_owned(),
+ ..reply.value
+ }))
+ } else if let Some(parent) = output_replies
+ .iter_mut()
+ // .rev()
+ .flat_map(|com| &mut com.replies)
+ .filter(|com| com.value.author == replyee)
+ .last()
+ {
+ parent.replies.push(CommentExt::from(Comment {
+ text: text.to_owned(),
+ ..reply.value
+ }))
+ } else if let Some(parent) = output_replies.iter_mut()
+ // .rev()
+ .filter(|com| com.value.author == replyee)
+ .last()
+ {
+ parent.replies.push(CommentExt::from(Comment {
+ text: text.to_owned(),
+ ..reply.value
+ }))
+ } else {
+ eprintln!(
+ "Failed to find a parent for ('{}') both directly and via replies! The reply text was:\n'{}'\n",
+ replyee,
+ reply.value.text
+ );
+ output_replies.push(reply);
+ }
+ } else {
+ output_replies.push(reply);
+ }
+ }
+ comment.replies = output_replies;
+ });
+
+ let mut less = Command::new("less")
+ .args(["--raw-control-chars"])
+ .stdin(Stdio::piped())
+ .stderr(Stdio::inherit())
+ .spawn()
+ .context("Failed to run less")?;
+
+ let mut child = Command::new("fmt")
+ .args(["--uniform-spacing", "--split-only", "--width=90"])
+ .stdin(Stdio::piped())
+ .stderr(Stdio::inherit())
+ .stdout(less.stdin.take().expect("Should be open"))
+ .spawn()
+ .context("Failed to run fmt")?;
+
+ let mut stdin = child.stdin.take().context("Failed to open stdin")?;
+ std::thread::spawn(move || {
+ stdin
+ .write_all(comments.to_string().as_bytes())
+ .expect("Should be able to write to stdin of fmt");
+ });
+
+ let _ = less.wait().context("Failed to await less")?;
+
+ Ok(())
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn test_string_replacement() {
+ let s = "A \n\nB\n\nC".to_owned();
+ assert_eq!("A \n \n B\n \n C", s.replace('\n', "\n "))
+ }
+}
diff --git a/pkgs/sources/comments/update.sh b/pkgs/sources/comments/update.sh
new file mode 100755
index 00000000..e500bb23
--- /dev/null
+++ b/pkgs/sources/comments/update.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+nix flake update
+
+[ "$1" = "upgrade" ] && cargo upgrade
+cargo update
+
+# vim: ft=sh
diff --git a/pkgs/sources/default.nix b/pkgs/sources/default.nix
new file mode 100644
index 00000000..4668d735
--- /dev/null
+++ b/pkgs/sources/default.nix
@@ -0,0 +1,27 @@
+{
+ homeConfig,
+ nixosConfig,
+ sysLib,
+}: let
+ comments = import ./comments;
+ generate_firefox_extensions = import ./generate_moz_extension;
+ lf_make_map = import ./lf-make-map;
+ nvim_plugs = import ./plgs-pkgs;
+ scripts = import ./scripts {inherit sysLib homeConfig nixosConfig;};
+ snap-sync-forked = (import ./snap-sync-forked) {inherit sysLib;};
+ update_vim_plugins = import ./update_vim_plugins;
+ yt = import ./yt;
+ yts-grammar = import ./tree-sitter-yts;
+
+ overlays =
+ comments
+ ++ generate_firefox_extensions
+ ++ lf_make_map
+ ++ nvim_plugs
+ ++ scripts
+ ++ snap-sync-forked
+ ++ update_vim_plugins
+ ++ yt
+ ++ yts-grammar;
+in
+ overlays
diff --git a/pkgs/sources/generate_moz_extension/.envrc b/pkgs/sources/generate_moz_extension/.envrc
new file mode 100644
index 00000000..3550a30f
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/pkgs/sources/generate_moz_extension/.gitignore b/pkgs/sources/generate_moz_extension/.gitignore
new file mode 100644
index 00000000..f717ddd7
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/.gitignore
@@ -0,0 +1,3 @@
+/target
+/result
+.direnv
diff --git a/pkgs/sources/generate_moz_extension/Cargo.lock b/pkgs/sources/generate_moz_extension/Cargo.lock
new file mode 100644
index 00000000..c0a83aa8
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/Cargo.lock
@@ -0,0 +1,1275 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "anyhow"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "backtrace"
+version = "0.3.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "bytes"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
+
+[[package]]
+name = "cc"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
+
+[[package]]
+name = "futures-task"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
+
+[[package]]
+name = "futures-util"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generate_extensions"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "futures",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "tokio",
+]
+
+[[package]]
+name = "gimli"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+
+[[package]]
+name = "h2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "http"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
+
+[[package]]
+name = "hyper"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.154"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "memchr"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "object"
+version = "0.32.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "openssl"
+version = "0.10.64"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
+dependencies = [
+ "bitflags 2.5.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pin-project"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.12.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-tls",
+ "hyper-util",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pemfile",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "system-configuration",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "winreg",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags 2.5.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
+dependencies = [
+ "base64",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "schannel"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0"
+dependencies = [
+ "bitflags 2.5.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.201"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.201"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
+name = "socket2"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
+[[package]]
+name = "system-configuration"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "rustix",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+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.37.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
+[[package]]
+name = "tower-service"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
+
+[[package]]
+name = "tracing"
+version = "0.1.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+dependencies = [
+ "log",
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+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 = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[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.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "web-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.5",
+ "windows_aarch64_msvc 0.52.5",
+ "windows_i686_gnu 0.52.5",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.5",
+ "windows_x86_64_gnu 0.52.5",
+ "windows_x86_64_gnullvm 0.52.5",
+ "windows_x86_64_msvc 0.52.5",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+
+[[package]]
+name = "winreg"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.48.0",
+]
diff --git a/pkgs/sources/generate_moz_extension/Cargo.toml b/pkgs/sources/generate_moz_extension/Cargo.toml
new file mode 100644
index 00000000..e7d44db4
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "generate_extensions"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.83"
+futures = "0.3.30"
+reqwest = "0.12.4"
+serde = { version = "1.0.201", features = ["derive"] }
+serde_json = "1.0.117"
+tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
diff --git a/pkgs/sources/generate_moz_extension/default.nix b/pkgs/sources/generate_moz_extension/default.nix
new file mode 100644
index 00000000..be734eee
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/default.nix
@@ -0,0 +1,16 @@
+[
+ (
+ final: prev: {
+ generate_firefox_extensions = import ./generate_firefox_extensions.nix {
+ inherit
+ (prev)
+ rustPlatform
+ # Dependencies
+
+ openssl
+ pkg-config
+ ;
+ };
+ }
+ )
+]
diff --git a/pkgs/sources/generate_moz_extension/examples/generate_extensions.sh b/pkgs/sources/generate_moz_extension/examples/generate_extensions.sh
new file mode 100755
index 00000000..96802992
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/examples/generate_extensions.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+tmp=$(mktemp)
+cat <<EOF | awk '!/^\s*#/' >"$tmp"
+ darkreader:navbar
+ keepassxc-browser:navbar
+ vhack-libredirect:navbar
+ # torproject-snowflake:navbar
+ tridactyl-vim:menupanel
+ ublock-origin:menupanel
+EOF
+
+# The cat execution should be unquoted;
+# shellcheck disable=SC2046
+cargo run -- $(cat "$tmp")
+
+rm "$tmp"
diff --git a/pkgs/sources/generate_moz_extension/flake.lock b/pkgs/sources/generate_moz_extension/flake.lock
new file mode 100644
index 00000000..741a8ad1
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/flake.lock
@@ -0,0 +1,106 @@
+{
+ "nodes": {
+ "crane": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1714864355,
+ "narHash": "sha256-uXNW6bapWFfkYIkK1EagydSrFMqycOYEDSq75GmUpjk=",
+ "owner": "ipetkov",
+ "repo": "crane",
+ "rev": "442a7a6152f49b907e73206dc8e1f46a61e8e873",
+ "type": "github"
+ },
+ "original": {
+ "owner": "ipetkov",
+ "repo": "crane",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1715037484,
+ "narHash": "sha256-OUt8xQFmBU96Hmm4T9tOWTu4oCswCzoVl+pxSq/kiFc=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "ad7efee13e0d216bf29992311536fce1d3eefbef",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "crane": "crane",
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs",
+ "rust-overlay": "rust-overlay"
+ }
+ },
+ "rust-overlay": {
+ "inputs": {
+ "flake-utils": [
+ "flake-utils"
+ ],
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1715221036,
+ "narHash": "sha256-81EKOdlmT/4hZpImRlvMVPgmCcJYZjwlWbJese/XqUw=",
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "rev": "5c4bc8a0a70093a31a12509c5653c147f2310bd2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "type": "github"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/pkgs/sources/generate_moz_extension/flake.nix b/pkgs/sources/generate_moz_extension/flake.nix
new file mode 100644
index 00000000..5575f90b
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/flake.nix
@@ -0,0 +1,75 @@
+{
+ description = "A simple way to query the mozialla api for extension data";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+
+ crane = {
+ url = "github:ipetkov/crane";
+ inputs = {
+ nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ flake-utils.url = "github:numtide/flake-utils";
+
+ rust-overlay = {
+ url = "github:oxalica/rust-overlay";
+ inputs = {
+ nixpkgs.follows = "nixpkgs";
+ flake-utils.follows = "flake-utils";
+ };
+ };
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ crane,
+ flake-utils,
+ rust-overlay,
+ ...
+ }:
+ flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = import nixpkgs {
+ inherit system;
+ overlays = [(import rust-overlay)];
+ };
+
+ rust-stable = pkgs.rust-bin.stable.latest.default;
+ rust-minimal = pkgs.rust-bin.stable.latest.minimal;
+
+ craneLib = (crane.mkLib pkgs).overrideToolchain rust-minimal;
+
+ buildInputs = [
+ pkgs.openssl # needed for openssl
+ ];
+ nativeBuildInputs = [
+ pkgs.pkg-config # needed for openssl
+ ];
+
+ craneBuild = craneLib.buildPackage {
+ src = craneLib.cleanCargoSource ./.;
+ inherit buildInputs nativeBuildInputs;
+
+ doCheck = true;
+ };
+ in {
+ packages.default = craneBuild;
+ app.default = {
+ type = "app";
+ program = "${self.packages.${system}.default}/bin/generate_extensions";
+ };
+ devShells.default = pkgs.mkShell {
+ packages = with pkgs; [
+ cocogitto
+
+ rust-stable
+ cargo-edit
+ ];
+ inherit buildInputs nativeBuildInputs;
+ };
+ });
+}
+# vim: ts=2
+
diff --git a/pkgs/sources/generate_moz_extension/generate_firefox_extensions.nix b/pkgs/sources/generate_moz_extension/generate_firefox_extensions.nix
new file mode 100644
index 00000000..abd95c77
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/generate_firefox_extensions.nix
@@ -0,0 +1,20 @@
+{
+ rustPlatform,
+ openssl,
+ pkg-config,
+}:
+rustPlatform.buildRustPackage {
+ pname = "generate_firefox_extensions";
+ version = "0.1.0";
+
+ src = ./.;
+ cargoLock = {
+ lockFile = ./Cargo.lock;
+ };
+ buildInputs = [
+ openssl # needed for openssl-sys crate
+ ];
+ nativeBuildInputs = [
+ pkg-config # needed for openssl dependency
+ ];
+}
diff --git a/pkgs/sources/generate_moz_extension/res/generate_extensions.py b/pkgs/sources/generate_moz_extension/res/generate_extensions.py
new file mode 100644
index 00000000..ee8cc966
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/res/generate_extensions.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# source: https://github.com/etu/nixconfig/blob/ba47d577c8bfb4a1c06927c34ece34118f4a0460/modules/graphical/firefox/generate.py
+
+from concurrent.futures import ThreadPoolExecutor
+import json
+import requests
+
+EXTENSIONS = sorted(
+ [
+ "darkreader",
+ "firenvim",
+ "keepassxc-browser",
+ "simple-tab-groups",
+ ]
+)
+
+
+def index_ext(ext: str):
+ # print(f"Indexing {ext}...")
+
+ resp = requests.get(f"https://addons.mozilla.org/api/v5/addons/addon/{ext}/").json()
+ rel = resp["current_version"]
+
+ if not rel["file"]["hash"].startswith("sha256:"):
+ raise ValueError("Unhandled hash type")
+
+ return {
+ "pname": ext,
+ "version": rel["version"],
+ "addonId": resp["guid"],
+ "url": rel["file"]["url"],
+ "sha256": rel["file"]["hash"],
+ }
+
+
+if __name__ == "__main__":
+ # outfile = os.path.dirname(os.path.realpath(__file__)) + "/extensions.json"
+
+ with ThreadPoolExecutor() as e:
+ extensions = {ext: e.submit(index_ext, ext) for ext in EXTENSIONS}
+ extensions = {k: v.result() for k, v in extensions.items()}
+
+ # with open(outfile, "w") as f:
+ print(json.dumps(extensions, indent=2))
diff --git a/pkgs/sources/generate_moz_extension/res/reference.json b/pkgs/sources/generate_moz_extension/res/reference.json
new file mode 100644
index 00000000..f46ea8ec
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/res/reference.json
@@ -0,0 +1,30 @@
+{
+ "darkreader": {
+ "pname": "darkreader",
+ "version": "4.9.62",
+ "addonId": "addon@darkreader.org",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4053589/darkreader-4.9.62.xpi",
+ "sha256": "sha256:e537a2cee45ed7c26f79ecd3ed362620e3f00d24c158532a58e163a63a3d60cc"
+ },
+ "firenvim": {
+ "pname": "firenvim",
+ "version": "0.2.14",
+ "addonId": "firenvim@lacamb.re",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4026386/firenvim-0.2.14.xpi",
+ "sha256": "sha256:a8c495a59e30eaabbb3fcd188db9b5e28b40bffefe41a3f0fa22ecc58c80c2b6"
+ },
+ "keepassxc-browser": {
+ "pname": "keepassxc-browser",
+ "version": "1.8.4",
+ "addonId": "keepassxc-browser@keepassxc.org",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4045866/keepassxc_browser-1.8.4.xpi",
+ "sha256": "sha256:cc39aa058cb8915cfc88424e2e1cebe3ccfc3f95d7bddb2abd0c4905d2b17719"
+ },
+ "simple-tab-groups": {
+ "pname": "simple-tab-groups",
+ "version": "4.7.2.1",
+ "addonId": "simple-tab-groups@drive4ik",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/3873608/simple_tab_groups-4.7.2.1.xpi",
+ "sha256": "sha256:75077589098ca62c00b86cf9554c6120bf8dc04c5f916fe26f84915f5147b2a4"
+ }
+}
diff --git a/pkgs/sources/generate_moz_extension/res/test.json b/pkgs/sources/generate_moz_extension/res/test.json
new file mode 100644
index 00000000..daa1d19a
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/res/test.json
@@ -0,0 +1,30 @@
+{
+ "darkreader": {
+ "addon_id": "addon@darkreader.org",
+ "pname": "darkreader",
+ "sha256": "sha256:e537a2cee45ed7c26f79ecd3ed362620e3f00d24c158532a58e163a63a3d60cc",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4053589/darkreader-4.9.62.xpi",
+ "version": "4.9.62"
+ },
+ "firenvim": {
+ "addon_id": "firenvim@lacamb.re",
+ "pname": "firenvim",
+ "sha256": "sha256:a8c495a59e30eaabbb3fcd188db9b5e28b40bffefe41a3f0fa22ecc58c80c2b6",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4026386/firenvim-0.2.14.xpi",
+ "version": "0.2.14"
+ },
+ "keepassxc-browser": {
+ "addon_id": "keepassxc-browser@keepassxc.org",
+ "pname": "keepassxc-browser",
+ "sha256": "sha256:cc39aa058cb8915cfc88424e2e1cebe3ccfc3f95d7bddb2abd0c4905d2b17719",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/4045866/keepassxc_browser-1.8.4.xpi",
+ "version": "1.8.4"
+ },
+ "simple-tab-groups": {
+ "addon_id": "simple-tab-groups@drive4ik",
+ "pname": "simple-tab-groups",
+ "sha256": "sha256:75077589098ca62c00b86cf9554c6120bf8dc04c5f916fe26f84915f5147b2a4",
+ "url": "https://addons.mozilla.org/firefox/downloads/file/3873608/simple_tab_groups-4.7.2.1.xpi",
+ "version": "4.7.2.1"
+ }
+}
diff --git a/pkgs/sources/generate_moz_extension/src/main.rs b/pkgs/sources/generate_moz_extension/src/main.rs
new file mode 100644
index 00000000..bde986a3
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/src/main.rs
@@ -0,0 +1,138 @@
+use std::env::args;
+
+use anyhow::{bail, Context};
+use futures::StreamExt;
+use reqwest::Client;
+use serde_json::{json, Map, Value};
+
+pub mod types;
+
+macro_rules! get_json_value {
+ ($key:expr, $json_value:ident, $type:ident, $get:ident) => {
+ match $json_value.get($key) {
+ Some(resp) => {
+ let resp = resp.to_owned();
+ if resp.$type() {
+ resp.$get().expect(
+ "The should have been checked in the if guard, so unpacking here is fine",
+ ).to_owned()
+ } else {
+ bail!(
+ "Value {} => \n{}\n is not of type: {}",
+ $key,
+ resp,
+ stringify!($type)
+ );
+ }
+ }
+ None => {
+ bail!(
+ "There seems to be no '{}' in your json data (json value: '{}')\n Has the api changend?",
+ $key, serde_json::to_string_pretty(&$json_value).expect("Will always work")
+ );
+ }
+ }
+ };
+}
+
+use futures::stream::futures_unordered::FuturesUnordered;
+use types::{Extension, InputExtension};
+
+#[tokio::main]
+async fn main() -> anyhow::Result<()> {
+ let mut extensions: Vec<InputExtension> = vec![];
+ for input_extension in args()
+ .skip(1)
+ .map(|str| InputExtension::try_from(str))
+ .collect::<Vec<anyhow::Result<InputExtension>>>()
+ {
+ extensions.push(input_extension?);
+ }
+
+ let resulting_extensions = process_extensions(extensions).await?;
+
+ let mut output = Map::new();
+ for extension in resulting_extensions {
+ output.insert(extension.pname.clone(), json!(extension));
+ }
+
+ println!(
+ "{}",
+ serde_json::to_string_pretty(&serde_json::Value::Object(output)).expect(
+ "This is constructed from json, it should also be possible to serialize it again"
+ )
+ );
+ Ok(())
+}
+
+async fn process_extensions(extensions: Vec<InputExtension>) -> anyhow::Result<Vec<Extension>> {
+ let mut output = Vec::with_capacity(extensions.len());
+
+ let client = Client::new();
+ for extension in extensions
+ .iter()
+ .map(|ext| {
+ let local_client = &client;
+ index_extension(ext, local_client)
+ })
+ .collect::<FuturesUnordered<_>>()
+ .collect::<Vec<_>>()
+ .await
+ {
+ output.push(extension?);
+ }
+ Ok(output)
+}
+
+async fn index_extension(extension: &InputExtension, client: &Client) -> anyhow::Result<Extension> {
+ let response = client
+ .get(format!(
+ "https://addons.mozilla.org/api/v5/addons/addon/{}",
+ extension,
+ ))
+ .send()
+ .await
+ .context("Accessing the mozzila extenios api failed with error: {e}")?;
+
+ eprintln!("Indexing {} ({})...", extension, response.status());
+ let response: Value = serde_json::from_str(
+ &response
+ .text()
+ .await
+ .context("Turning the response to text fail with error: {e}")?,
+ )
+ .context("Deserializing the response failed! Error: {e}")?;
+
+ if let Some(detail) = response.get("detail") {
+ if detail == "Not found." {
+ bail!("Your extension ('{}') was not found!", extension);
+ }
+ };
+
+ let release = { get_json_value!("current_version", response, is_object, as_object) };
+
+ #[allow(non_snake_case)]
+ let addonId = { get_json_value!("guid", response, is_string, as_str) };
+
+ let version = { get_json_value!("version", release, is_string, as_str) };
+ let file = { get_json_value!("file", release, is_object, as_object) };
+
+ let url = { get_json_value!("url", file, is_string, as_str) };
+ let sha256 = {
+ let hash = get_json_value!("hash", file, is_string, as_str);
+ if hash.starts_with("sha256:") {
+ hash
+ } else {
+ bail!("This hash type is unhandled: {}", hash);
+ }
+ };
+
+ Ok(Extension {
+ pname: extension.moz_name.clone(),
+ default_area: extension.default_area,
+ version,
+ addonId,
+ url,
+ sha256,
+ })
+}
diff --git a/pkgs/sources/generate_moz_extension/src/types.rs b/pkgs/sources/generate_moz_extension/src/types.rs
new file mode 100644
index 00000000..b830fe0d
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/src/types.rs
@@ -0,0 +1,71 @@
+use std::fmt::Display;
+
+use anyhow::anyhow;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize, Deserialize)]
+#[allow(non_snake_case)]
+pub struct Extension {
+ pub pname: String,
+ pub default_area: DefaultArea,
+ pub version: String,
+ pub addonId: String,
+ pub url: String,
+ pub sha256: String,
+}
+
+#[derive(Debug, Clone)]
+pub struct InputExtension {
+ pub moz_name: String,
+ pub default_area: DefaultArea,
+}
+#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
+#[allow(non_camel_case_types)]
+pub enum DefaultArea {
+ navbar,
+ menupanel,
+}
+
+impl Display for DefaultArea {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ DefaultArea::navbar => f.write_str("navbar"),
+ DefaultArea::menupanel => f.write_str("menupanel"),
+ }
+ }
+}
+
+impl TryFrom<&str> for DefaultArea {
+ type Error = anyhow::Error;
+
+ fn try_from(value: &str) -> Result<Self, Self::Error> {
+ match value {
+ "navbar" => Ok(Self::navbar),
+ "menupanel" => Ok(Self::menupanel),
+ _ => Err(anyhow!(
+ "Your <default_area> needs to be one of 'navbar' or 'menupanel', but is: '{}'",
+ value
+ )),
+ }
+ }
+}
+
+impl Display for InputExtension {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(&self.moz_name)
+ }
+}
+impl TryFrom<String> for InputExtension {
+ type Error = anyhow::Error;
+
+ fn try_from(value: String) -> Result<Self, Self::Error> {
+ if let Some((moz_name, default_area)) = value.split_once(':') {
+ Ok(Self {
+ moz_name: moz_name.to_owned(),
+ default_area: default_area.try_into()?,
+ })
+ } else {
+ Err(anyhow!("Can't parse the input string as a InputExtension!\n Needs to be: '<moz_name>:<default_area>'"))
+ }
+ }
+}
diff --git a/pkgs/sources/generate_moz_extension/update.sh b/pkgs/sources/generate_moz_extension/update.sh
new file mode 100755
index 00000000..e500bb23
--- /dev/null
+++ b/pkgs/sources/generate_moz_extension/update.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+nix flake update
+
+[ "$1" = "upgrade" ] && cargo upgrade
+cargo update
+
+# vim: ft=sh
diff --git a/pkgs/sources/lf-make-map/.envrc b/pkgs/sources/lf-make-map/.envrc
new file mode 100644
index 00000000..c8c56659
--- /dev/null
+++ b/pkgs/sources/lf-make-map/.envrc
@@ -0,0 +1,11 @@
+use flake || use nix
+watch_file flake.nix
+
+PATH_add ./target/debug
+PATH_add ./target/release
+PATH_add ./scripts
+
+if on_git_branch; then
+ echo && git status --short --branch &&
+ echo && git fetch --verbose
+fi
diff --git a/pkgs/sources/lf-make-map/.gitignore b/pkgs/sources/lf-make-map/.gitignore
new file mode 100644
index 00000000..cb87f36f
--- /dev/null
+++ b/pkgs/sources/lf-make-map/.gitignore
@@ -0,0 +1,6 @@
+# build
+/target
+/result
+
+# dev env
+.direnv
diff --git a/pkgs/sources/lf-make-map/Cargo.lock b/pkgs/sources/lf-make-map/Cargo.lock
new file mode 100644
index 00000000..16af6e03
--- /dev/null
+++ b/pkgs/sources/lf-make-map/Cargo.lock
@@ -0,0 +1,505 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "cc"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "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 = "is-terminal"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lf-make-map"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "log",
+ "stderrlog",
+ "walkdir",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.154"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[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 = "stderrlog"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c910772f992ab17d32d6760e167d2353f4130ed50e796752689556af07dc6b"
+dependencies = [
+ "chrono",
+ "is-terminal",
+ "log",
+ "termcolor",
+ "thread_local",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[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 = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets",
+]
+
+[[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-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+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.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
diff --git a/pkgs/sources/lf-make-map/Cargo.toml b/pkgs/sources/lf-make-map/Cargo.toml
new file mode 100644
index 00000000..da9881fd
--- /dev/null
+++ b/pkgs/sources/lf-make-map/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "lf-make-map"
+description = "An automatic lf cd mapping generator"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.83"
+clap = { version = "4.5.4", features = ["derive", "env"] }
+log = "0.4.21"
+stderrlog = "0.6.0"
+walkdir = "2.5.0"
diff --git a/pkgs/sources/lf-make-map/README.md b/pkgs/sources/lf-make-map/README.md
new file mode 100644
index 00000000..0c57cede
--- /dev/null
+++ b/pkgs/sources/lf-make-map/README.md
@@ -0,0 +1,12 @@
+# Lf make map
+
+> An automatic lf cd mapping generator
+
+Some text about the project.
+
+## Licence
+
+This program 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.
diff --git a/pkgs/sources/lf-make-map/default.nix b/pkgs/sources/lf-make-map/default.nix
new file mode 100644
index 00000000..8ff4c624
--- /dev/null
+++ b/pkgs/sources/lf-make-map/default.nix
@@ -0,0 +1,12 @@
+[
+ (
+ final: prev: {
+ lf-make-map = import ./lf_make_map.nix {
+ inherit
+ (prev)
+ rustPlatform
+ ;
+ };
+ }
+ )
+]
diff --git a/pkgs/sources/lf-make-map/flake.lock b/pkgs/sources/lf-make-map/flake.lock
new file mode 100644
index 00000000..611392df
--- /dev/null
+++ b/pkgs/sources/lf-make-map/flake.lock
@@ -0,0 +1,147 @@
+{
+ "nodes": {
+ "crane": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1714864355,
+ "narHash": "sha256-uXNW6bapWFfkYIkK1EagydSrFMqycOYEDSq75GmUpjk=",
+ "owner": "ipetkov",
+ "repo": "crane",
+ "rev": "442a7a6152f49b907e73206dc8e1f46a61e8e873",
+ "type": "github"
+ },
+ "original": {
+ "owner": "ipetkov",
+ "repo": "crane",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1696426674,
+ "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": [
+ "systems"
+ ]
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1715037484,
+ "narHash": "sha256-OUt8xQFmBU96Hmm4T9tOWTu4oCswCzoVl+pxSq/kiFc=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "ad7efee13e0d216bf29992311536fce1d3eefbef",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "crane": "crane",
+ "flake-compat": "flake-compat",
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs",
+ "rust-overlay": "rust-overlay",
+ "systems": "systems",
+ "treefmt-nix": "treefmt-nix"
+ }
+ },
+ "rust-overlay": {
+ "inputs": {
+ "flake-utils": [
+ "flake-utils"
+ ],
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1715221036,
+ "narHash": "sha256-81EKOdlmT/4hZpImRlvMVPgmCcJYZjwlWbJese/XqUw=",
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "rev": "5c4bc8a0a70093a31a12509c5653c147f2310bd2",
+ "type": "github"
+ },
+ "original": {
+ "owner": "oxalica",
+ "repo": "rust-overlay",
+ "type": "github"
+ }
+ },
+ "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"
+ }
+ },
+ "treefmt-nix": {
+ "inputs": {
+ "nixpkgs": [
+ "nixpkgs"
+ ]
+ },
+ "locked": {
+ "lastModified": 1714058656,
+ "narHash": "sha256-Qv4RBm4LKuO4fNOfx9wl40W2rBbv5u5m+whxRYUMiaA=",
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "rev": "c6aaf729f34a36c445618580a9f95a48f5e4e03f",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "treefmt-nix",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/pkgs/sources/lf-make-map/flake.nix b/pkgs/sources/lf-make-map/flake.nix
new file mode 100644
index 00000000..dc8c24cc
--- /dev/null
+++ b/pkgs/sources/lf-make-map/flake.nix
@@ -0,0 +1,125 @@
+{
+ description = "An automatic lf cd mapping generator";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
+
+ treefmt-nix = {
+ url = "github:numtide/treefmt-nix";
+ inputs = {
+ nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ crane = {
+ url = "github:ipetkov/crane";
+ inputs = {
+ nixpkgs.follows = "nixpkgs";
+ };
+ };
+ rust-overlay = {
+ url = "github:oxalica/rust-overlay";
+ inputs = {
+ nixpkgs.follows = "nixpkgs";
+ flake-utils.follows = "flake-utils";
+ };
+ };
+
+ # inputs for following
+ systems = {
+ url = "github:nix-systems/x86_64-linux"; # only evaluate for this system
+ };
+ flake-compat = {
+ url = "github:edolstra/flake-compat";
+ flake = false;
+ };
+ flake-utils = {
+ url = "github:numtide/flake-utils";
+ inputs = {
+ systems.follows = "systems";
+ };
+ };
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ flake-utils,
+ treefmt-nix,
+ crane,
+ rust-overlay,
+ ...
+ }:
+ flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = import nixpkgs {
+ inherit system;
+ overlays = [(import rust-overlay)];
+ };
+
+ nightly = false;
+ rust_minimal =
+ if nightly
+ then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.minimal)
+ else pkgs.rust-bin.stable.latest.minimal;
+ rust_default =
+ if nightly
+ then pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default)
+ else pkgs.rust-bin.stable.latest.default;
+
+ cargo_toml = craneLib.cleanCargoToml {cargoToml = ./Cargo.toml;};
+ pname = cargo_toml.package.name;
+
+ craneLib = (crane.mkLib pkgs).overrideToolchain rust_minimal;
+ craneBuild = craneLib.buildPackage {
+ src = craneLib.cleanCargoSource ./.;
+
+ doCheck = true;
+ };
+
+ manual = pkgs.stdenv.mkDerivation {
+ name = "${pname}-manual";
+ inherit (cargo_toml.package) version;
+
+ src = ./docs;
+ nativeBuildInputs = with pkgs; [pandoc];
+
+ buildPhase = ''
+ mkdir --parents $out/docs;
+
+ pandoc "./${pname}.1.md" -s -t man > $out/docs/${pname}.1
+ '';
+
+ installPhase = ''
+ install -D $out/docs/${pname}.1 $out/share/man/man1/${pname};
+ '';
+ };
+
+ treefmtEval = import ./treefmt.nix {inherit treefmt-nix pkgs;};
+ in {
+ packages.default = pkgs.symlinkJoin {
+ inherit (cargo_toml.package) name;
+
+ paths = [manual craneBuild];
+ };
+
+ checks = {
+ inherit craneBuild;
+ formatting = treefmtEval.config.build.check self;
+ };
+
+ formatter = treefmtEval.config.build.wrapper;
+
+ devShells.default = pkgs.mkShell {
+ packages = with pkgs; [
+ cocogitto
+
+ rust_default
+ cargo-edit
+
+ licensure
+ ];
+ };
+ });
+}
+# vim: ts=2
+
diff --git a/pkgs/sources/lf-make-map/lf_make_map.nix b/pkgs/sources/lf-make-map/lf_make_map.nix
new file mode 100644
index 00000000..afb067b8
--- /dev/null
+++ b/pkgs/sources/lf-make-map/lf_make_map.nix
@@ -0,0 +1,10 @@
+{rustPlatform}:
+rustPlatform.buildRustPackage {
+ pname = "lf-make-map";
+ version = "0.1.0";
+
+ src = ./.;
+ cargoLock = {
+ lockFile = ./Cargo.lock;
+ };
+}
diff --git a/pkgs/sources/lf-make-map/src/cli.rs b/pkgs/sources/lf-make-map/src/cli.rs
new file mode 100644
index 00000000..a398e451
--- /dev/null
+++ b/pkgs/sources/lf-make-map/src/cli.rs
@@ -0,0 +1,49 @@
+use std::path::PathBuf;
+
+use clap::{ArgAction, Parser, Subcommand};
+
+/// An automatic lf cd mapping generator
+#[derive(Parser, Debug)]
+#[clap(author, version, about, long_about = None)]
+#[command(next_line_help = true)]
+pub struct Args {
+ /// The directory to treat as home
+ #[arg(long, short = 'n', env = "HOME")]
+ pub home_name: PathBuf,
+
+ /// The number of directories to generate mappings for, starting from each `relevant_directory`
+ #[arg(long, short, default_value = "2")]
+ pub depth: usize,
+
+ /// Increase message verbosity
+ #[arg(long="verbose", short = 'v', action = ArgAction::Count)]
+ pub verbosity: u8,
+
+ /// Silence all output
+ #[arg(long, short = 'q')]
+ pub quiet: bool,
+
+ #[command(subcommand)]
+ pub command: Command,
+}
+
+#[derive(Subcommand, Debug)]
+pub enum Command {
+ /// Visualize the generated mappings in a tree
+ Visualize {
+ #[command(flatten)]
+ options: CommandOptions,
+ },
+
+ /// Output the generated mappings in a format suitable for the lf config file
+ Generate {
+ #[command(flatten)]
+ options: CommandOptions,
+ },
+}
+
+#[derive(Debug, Parser)]
+pub struct CommandOptions {
+ /// The directories to generate mappings for
+ pub relevant_directories: Vec<PathBuf>,
+}
diff --git a/pkgs/sources/lf-make-map/src/main.rs b/pkgs/sources/lf-make-map/src/main.rs
new file mode 100644
index 00000000..aaf79b20
--- /dev/null
+++ b/pkgs/sources/lf-make-map/src/main.rs
@@ -0,0 +1,229 @@
+use std::path::{Path, PathBuf};
+
+use anyhow::{Context, Result};
+use clap::Parser;
+use cli::{Args, Command};
+use log::trace;
+use mapping::map_tree::MappingTree;
+use walkdir::{DirEntry, WalkDir};
+
+use crate::mapping::MapKey;
+
+mod cli;
+mod mapping;
+
+fn main() -> anyhow::Result<()> {
+ let args = Args::parse();
+
+ stderrlog::new()
+ .module(module_path!())
+ .quiet(args.quiet)
+ .show_module_names(false)
+ .color(stderrlog::ColorChoice::Auto)
+ .verbosity(args.verbosity as usize)
+ .timestamp(stderrlog::Timestamp::Off)
+ .init()?;
+
+ let mut mappings = MappingTree::new();
+
+ let relevant_directories = match &args.command {
+ Command::Visualize { options } => &options.relevant_directories,
+ Command::Generate { options } => &options.relevant_directories,
+ };
+
+ for dir in relevant_directories {
+ trace!("Processing '{}'..", dir.display());
+ let path = strip_path(&dir, &args.home_name)?;
+
+ mappings
+ .include(path_to_str(path)?)
+ .with_context(|| format!("Failed to include path: '{}'", path.display()))?;
+ }
+
+ let home = path_to_str(&args.home_name)?.to_owned();
+
+ let mut current_depth = 1;
+ while current_depth != args.depth {
+ for (key, value) in mappings.iter(false) {
+ trace!(
+ "Adding to child ('{}' -> '{}')",
+ MapKey::display(&key),
+ value
+ );
+
+ let mut local_mappings = MappingTree::new();
+ for dir in WalkDir::new(extend(&home, &value)?)
+ .min_depth(1)
+ .max_depth(1)
+ .into_iter()
+ .filter_entry(|e| is_dir(e) && !is_hidden(e))
+ {
+ let directory = dir
+ .with_context(|| format!("Failed to read dir ('{}')", home.clone() + &value))?;
+ let path_to_strip = &PathBuf::from(extend(&home, &value)?);
+ let path = strip_path(&directory.path(), &path_to_strip)?;
+ trace!(
+ "Including: '{}' (after stripping '{}' from '{}' -> '{}' + '/' + '{}')",
+ path.display(),
+ directory.path().display(),
+ path_to_strip.display(),
+ home,
+ value
+ );
+
+ let gen_key = MapKey::new_ones_from_path(path_to_str(path)?, 1);
+ local_mappings
+ .insert(
+ &gen_key,
+ path_to_str(strip_path(&directory.path(), &PathBuf::from(&home))?)?,
+ )
+ .with_context(|| format!("Failed to include path: '{}'", path.display()))?;
+ }
+
+ trace!("{}", local_mappings);
+
+ trace!(
+ "'{}' -> '{:#?}'",
+ MapKey::display(&key),
+ local_mappings.root_node()
+ );
+ mappings.interleave(&key, local_mappings.root_node().to_owned())?;
+ }
+ current_depth += 1;
+ }
+
+ match args.command {
+ Command::Visualize { .. } => println!("{}", mappings),
+ Command::Generate { .. } => println!("{}", mappings.to_lf_mappings(args.home_name)),
+ }
+
+ Ok(())
+}
+
+fn extend(base: &str, value: &str) -> Result<String> {
+ let base_path = PathBuf::from(base);
+ let value_path = PathBuf::from(value);
+
+ Ok(path_to_str(&base_path.join(&value_path))?.to_owned())
+}
+
+fn is_hidden(entry: &DirEntry) -> bool {
+ entry
+ .file_name()
+ .to_str()
+ .map(|s| s.starts_with("."))
+ .unwrap_or(false)
+}
+
+fn is_dir(entry: &DirEntry) -> bool {
+ entry.file_type().is_dir()
+}
+
+fn strip_path<'a>(path: &'a Path, to_strip: &Path) -> Result<&'a Path> {
+ path.strip_prefix(&to_strip).with_context(|| {
+ format!(
+ "'{}' is not under the specified home path ('{}')!",
+ path.display(),
+ to_strip.display()
+ )
+ })
+}
+
+fn path_to_str(path: &Path) -> Result<&str> {
+ path.to_str().with_context(|| {
+ format!(
+ "\
+Can't derive a keymapping from path: '{}' \
+because it can't be turned to a string
+",
+ path.display()
+ )
+ })
+}
+
+// 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/sources/lf-make-map/src/mapping/map_tree/display.rs b/pkgs/sources/lf-make-map/src/mapping/map_tree/display.rs
new file mode 100644
index 00000000..65302e1e
--- /dev/null
+++ b/pkgs/sources/lf-make-map/src/mapping/map_tree/display.rs
@@ -0,0 +1,91 @@
+use std::fmt::Display;
+
+use crate::mapping::{
+ map_tree::{Node, NodeValue},
+ MapKey,
+};
+
+use super::MappingTree;
+
+impl Display for MappingTree {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ fn write_node(
+ f: &mut std::fmt::Formatter<'_>,
+ node: &Node,
+ indention: String,
+ location: Vec<MapKey>,
+ is_last: bool,
+ is_root: bool,
+ ) -> std::fmt::Result {
+ let node_value = match &node.value {
+ NodeValue::Parent { children: _ } => "<Parent>".to_owned(),
+ NodeValue::Child { path, extandable } => {
+ path.to_owned() + if *extandable { " [exten.]" } else { " [stop]" }
+ }
+ };
+
+ let new_idention = indention.clone()
+ + if is_root {
+ ""
+ } else {
+ match is_last {
+ true => " ",
+ false => "│ ",
+ }
+ };
+
+ let bullet = match is_last {
+ true => String::from("└── "),
+ false => String::from("├── "),
+ };
+
+ if is_root {
+ write!(f, ": {}\n", node_value)?;
+ } else {
+ write!(
+ f,
+ "{}{}\x1b[1;33m{}\x1b[0m: {}\n",
+ indention,
+ bullet,
+ MapKey::display(&location),
+ node_value,
+ )?;
+ };
+
+ match &node.value {
+ NodeValue::Parent { children } => {
+ let mut children_vec: Vec<(&MapKey, &Node)> = children.iter().collect();
+ children_vec.sort_by(|(a, _), (b, _)| a.key.cmp(&b.key));
+
+ let mut counter = 1;
+ for (key, child) in &children_vec {
+ let mut new_location = location.clone();
+ new_location.push((*key).to_owned());
+
+ write_node(
+ f,
+ child,
+ new_idention.clone(),
+ new_location.clone(),
+ counter == children_vec.len(),
+ false,
+ )?;
+ counter += 1;
+ }
+ }
+ NodeValue::Child {
+ path: _,
+ extandable: _,
+ } => {
+ // Do nothing and stop the recursion
+ }
+ }
+
+ Ok(())
+ }
+
+ write_node(f, &self.root, String::new(), vec![], false, true)?;
+
+ Ok(())
+ }
+}
diff --git a/pkgs/sources/lf-make-map/src/mapping/map_tree/iterator.rs b/pkgs/sources/lf-make-map/src/mapping/map_tree/iterator.rs
new file mode 100644
index 00000000..4364bb2b
--- /dev/null
+++ b/pkgs/sources/lf-make-map/src/mapping/map_tree/iterator.rs
@@ -0,0 +1,53 @@
+use crate::mapping::MapKey;
+
+use super::{MappingTree, Node, NodeValue};
+
+pub struct MappingTreeIterator {
+ children: Vec<(Vec<MapKey>, String)>,
+}
+
+impl MappingTreeIterator {
+ pub fn new(tree: &MappingTree, ignore_extendable: bool) -> Self {
+ let children = extract_child(vec![], &tree.root, ignore_extendable);
+
+ Self { children }
+ }
+}
+
+fn extract_child(
+ current_key: Vec<MapKey>,
+ node: &Node,
+ ignore_extendable: bool,
+) -> Vec<(Vec<MapKey>, String)> {
+ match &node.value {
+ NodeValue::Parent { children } => children
+ .iter()
+ .map(|(key, value)| {
+ let mut new_key = current_key.clone();
+ new_key.push(key.to_owned());
+
+ extract_child(new_key, value, ignore_extendable)
+ })
+ .flatten()
+ .collect(),
+ NodeValue::Child { path, extandable } => {
+ if ignore_extendable {
+ vec![(current_key, path.to_string())]
+ } else {
+ if *extandable {
+ vec![(current_key, path.to_string())]
+ } else {
+ vec![]
+ }
+ }
+ }
+ }
+}
+
+impl Iterator for MappingTreeIterator {
+ type Item = (Vec<MapKey>, String);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ self.children.pop()
+ }
+}
diff --git a/pkgs/sources/lf-make-map/src/mapping/map_tree/lf_mapping.rs b/pkgs/sources/lf-make-map/src/mapping/map_tree/lf_mapping.rs
new file mode 100644
index 00000000..6d9c7a0d
--- /dev/null
+++ b/pkgs/sources/lf-make-map/src/mapping/map_tree/lf_mapping.rs
@@ -0,0 +1,19 @@
+use std::path::PathBuf;
+
+use crate::mapping::MapKey;
+
+use super::MappingTree;
+
+impl MappingTree {
+ pub fn to_lf_mappings(self, home_path: PathBuf) -> String {
+ self.iter(true)
+ .map(|(key, value)| {
+ format!(
+ "map g{} cd \"{}\"\n",
+ MapKey::display(&key),
+ home_path.join(&value).display()
+ )
+ })
+ .collect()
+ }
+}
diff --git a/pkgs/sources/lf-make-map/src/mapping/map_tree/mod.rs b/pkgs/sources/lf-make-map/src/mapping/map_tree/mod.rs
new file mode 100644
index 00000000..35e6d91d
--- /dev/null
+++ b/pkgs/sources/lf-make-map/src/mapping/map_tree/mod.rs
@@ -0,0 +1,402 @@
+use std::{collections::HashMap, mem};
+
+use anyhow::{bail, Result};
+use log::debug;
+
+use self::iterator::MappingTreeIterator;
+
+use super::MapKey;
+
+pub mod display;
+pub mod iterator;
+pub mod lf_mapping;
+
+/// A prefix tree
+#[derive(Debug)]
+pub struct MappingTree {
+ root: Node,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum NodeValue {
+ Parent { children: HashMap<MapKey, Node> },
+ Child { path: String, extandable: bool },
+}
+
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct Node {
+ value: NodeValue,
+}
+
+impl MappingTree {
+ pub fn new() -> Self {
+ Self {
+ root: Node::new_parent(),
+ }
+ }
+
+ pub fn root_node(&self) -> &Node {
+ &self.root
+ }
+
+ pub fn iter(&self, ignore_extendable: bool) -> MappingTreeIterator {
+ MappingTreeIterator::new(&self, ignore_extendable)
+ }
+
+ /// Returns the node at the key, otherwise None. The node can be changed
+ pub fn get_mut(&mut self, key: &[MapKey]) -> Option<&mut Node> {
+ let mut current_node = &mut self.root;
+ for ch in key.iter() {
+ if let NodeValue::Parent { children } = &mut current_node.value {
+ current_node = children.get_mut(&ch)?
+ } else {
+ return None;
+ }
+ }
+
+ Some(current_node)
+ }
+
+ /// Returns the node at the key, otherwise the last node that matched.
+ pub fn try_get(&self, key: &[MapKey]) -> (&Node, Vec<MapKey>) {
+ let mut current_node = &self.root;
+ let mut current_key = vec![];
+
+ for ch in key.iter() {
+ if let NodeValue::Parent { children } = &current_node.value {
+ current_node = if let Some(node) = children.get(&ch) {
+ let (key, _value) = children
+ .get_key_value(&ch)
+ .expect("This exists, we checked");
+ current_key.push(key.clone());
+
+ node
+ } else {
+ return (current_node, current_key);
+ };
+ } else {
+ return (current_node, current_key);
+ }
+ }
+
+ (current_node, current_key)
+ }
+
+ pub fn include(&mut self, path: &str) -> Result<()> {
+ let associated_key = MapKey::new_ones_from_path(path, 1);
+ self.insert(&associated_key, path)
+ }
+
+ pub fn insert(&mut self, key: &[MapKey], path: &str) -> Result<()> {
+ self.insert_node(key, Node::new_child(path.to_owned()))
+ }
+
+ pub fn interleave(&mut self, key: &[MapKey], node: Node) -> Result<()> {
+ let want_to_be_parent = self.get_mut(&key).expect("This value exists");
+ let (parent_value, _parent_children) = if let NodeValue::Parent { children } = node.value {
+ (
+ NodeValue::Parent {
+ children: children.clone(),
+ },
+ children,
+ )
+ } else {
+ unreachable!("This value will be a parent")
+ };
+
+ let child_value = mem::replace(&mut want_to_be_parent.value, parent_value);
+ assert!(matches!(
+ child_value,
+ NodeValue::Child {
+ path: _,
+ extandable: _
+ }
+ ));
+
+ let child_value = if let NodeValue::Child {
+ path,
+ extandable: _,
+ } = child_value
+ {
+ NodeValue::Child {
+ path,
+ extandable: false,
+ }
+ } else {
+ unreachable!("This is only a child value")
+ };
+
+ let child = Node { value: child_value };
+
+ let mut new_key = key.to_vec();
+ new_key.push(MapKey {
+ key: '.',
+ part_path: ".".to_owned(),
+ resolution: 1,
+ });
+ self.insert_node(&new_key, child)?;
+ Ok(())
+ }
+
+ pub fn insert_node(&mut self, key: &[MapKey], node: Node) -> Result<()> {
+ let (_node, found_key) = self.try_get(key).clone();
+
+ if found_key != key {
+ let needed_nodes_key = key
+ .strip_prefix(&found_key[..])
+ .expect("The node's location is a prefix");
+
+ let needed_nodes_length = needed_nodes_key.iter().count();
+
+ let mut current_node = self
+ .get_mut(&found_key[..])
+ .expect("This should always exists");
+ let mut current_location = found_key.clone();
+ let mut counter = 1;
+
+ for ch in needed_nodes_key.iter() {
+ current_location.push(ch.to_owned());
+
+ let next_node = if counter == needed_nodes_length {
+ node.clone()
+ } else {
+ Node::new_parent()
+ };
+
+ current_node = match &current_node.value {
+ NodeValue::Parent { children } => {
+ assert_eq!(children.get(&ch), None);
+
+ let children =
+ if let NodeValue::Parent { children } = &mut current_node.value {
+ children
+ } else {
+ unreachable!("This is a parent, we cheched")
+ };
+
+ children.insert(ch.to_owned(), next_node);
+ children.get_mut(&ch).expect("Was just inserted")
+ }
+ NodeValue::Child {
+ path,
+ extandable: _,
+ } => {
+ // A node that should be a parent was classified
+ // as child before:
+ //
+ // 1. Remove the child node and replace it with a parent one.
+ // 2. Add the child node to the parent node as child, but with a '.' as MapKey.
+ // 3. Add the original node also as child to the parent node.
+
+ let mut children = HashMap::new();
+ let move_child_node = Node::new_child(path.to_owned());
+
+ children.insert(
+ MapKey {
+ key: '.',
+ part_path: ".".to_owned(),
+ resolution: 1,
+ },
+ move_child_node,
+ );
+ children.insert(ch.to_owned(), next_node);
+
+ current_node.value = NodeValue::Parent { children };
+
+ let children =
+ if let NodeValue::Parent { children } = &mut current_node.value {
+ children
+ } else {
+ unreachable!("We just inserted the parent value.")
+ };
+
+ children.get_mut(&ch).expect("Was just inserted")
+ }
+ };
+
+ counter += 1;
+ }
+ } else {
+ fn reduce_string(a: &str) -> Option<char> {
+ let first_char = a.chars().take(1).last().expect("Should contain one char");
+
+ if a.chars().all(|ch| ch == first_char) {
+ return Some(first_char);
+ } else {
+ return None;
+ }
+ }
+ fn check_subset(a: &str, b: &str) -> bool {
+ if a.len() > b.len() {
+ let a_prefix: String = a.chars().take(b.len()).collect();
+ let a_suffix: String = a.chars().skip(b.len()).collect();
+
+ if a_prefix == b {
+ let clean_suffix = reduce_string(&a_suffix);
+ if let Some(ch) = clean_suffix {
+ ch == b.chars().last().expect("Will match")
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ } else if b.len() > a.len() {
+ let b_prefix: String = b.chars().take(a.len()).collect();
+ let b_suffix: String = b.chars().skip(a.len()).collect();
+
+ if b_prefix == a {
+ let clean_suffix = reduce_string(&b_suffix);
+ if let Some(ch) = clean_suffix {
+ ch == a.chars().last().expect("Will match")
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ } else {
+ a == b
+ }
+ }
+
+ // Another node was already inserted with the same key!
+ // So we simple increase the resolution of the other node and this node, until their
+ // keys are not the same anymore.
+ // This only includes the last segment of the `MapKey`
+ //
+ // 1. Change both keys, until they are not equal any more
+ // 2. Move the wrongly placed node to the new place.
+ // 3. Insert our node.
+ let mut foreign_key = vec![found_key.last().expect("This will exist").clone()];
+ let mut our_key = vec![key.last().expect("This will exist").clone()];
+
+ debug!(
+ "'{}' ('{}') and '{}' ('{}') are the same, try to find a better combination!",
+ MapKey::display(&our_key),
+ our_key[0].part_path,
+ MapKey::display(&foreign_key),
+ foreign_key[0].part_path,
+ );
+
+ // The 'a' and 'b' stuff is here, to ensure that both returning None will not match
+ // this condition.
+ if reduce_string(&foreign_key[0].part_path).unwrap_or('a')
+ == reduce_string(&our_key[0].part_path).unwrap_or('b')
+ {
+ bail!(
+ "\
+The foreign_key ('{}', path_part: '{}' -> '{}') and our_key ('{}', path_part: '{}' -> '{}') \
+have an identical path_part (when duplicated chars are removed)!
+I cannot extended them via incrementation.
+Please rename the paths to fix this.
+ ",
+ MapKey::display(&foreign_key),
+ &foreign_key[0].part_path,
+ reduce_string(&foreign_key[0].part_path).expect("Is some here"),
+ MapKey::display(&our_key),
+ &our_key[0].part_path,
+ reduce_string(&our_key[0].part_path).expect("Is some here"),
+ );
+ }
+
+ if check_subset(&foreign_key[0].part_path, &our_key[0].part_path) {
+ bail!(
+ "\
+The foreign_key ('{}', path_part: '{}') and our_key ('{}', path_part: '{}') \
+are subsets of one another!
+A discrimination through incrementation will not work!
+Please rename the paths to fix this.
+ ",
+ MapKey::display(&foreign_key),
+ &foreign_key[0].part_path,
+ MapKey::display(&our_key),
+ &our_key[0].part_path,
+ );
+ }
+
+ while our_key == foreign_key {
+ our_key = our_key[0].increment(our_key[our_key.len() - 1].resolution + 1);
+ foreign_key =
+ foreign_key[0].increment(foreign_key[foreign_key.len() - 1].resolution + 1);
+ debug!(
+ "Now its: '{}' ('{}') and '{}' ('{}')",
+ MapKey::display(&our_key),
+ our_key[0].part_path,
+ MapKey::display(&foreign_key),
+ foreign_key[0].part_path,
+ );
+ }
+
+ debug!(
+ "Found a better one: '{}' ('{}') and '{}' ('{}')",
+ MapKey::display(&our_key),
+ our_key[0].part_path,
+ MapKey::display(&foreign_key),
+ foreign_key[0].part_path,
+ );
+
+ let parent = self
+ .get_mut(&found_key[..&found_key.len() - 1])
+ .expect("This will exist");
+
+ if let NodeValue::Parent { children } = &mut parent.value {
+ if let NodeValue::Child {
+ path: _,
+ extandable: _,
+ } = children
+ .get(found_key.last().expect("Exists"))
+ .expect("This node also exists")
+ .value
+ {
+ let old = children
+ .remove(found_key.last().expect("This will exist"))
+ .expect("This will be there");
+
+ let full_foreign_key: Vec<_> = found_key
+ .clone()
+ .into_iter()
+ .rev()
+ .skip(1)
+ .rev()
+ .chain(foreign_key.clone().into_iter())
+ .collect();
+ self.insert_node(&full_foreign_key, old.clone())?;
+ }
+
+ let full_our_key: Vec<_> = key
+ .to_vec()
+ .into_iter()
+ .rev()
+ .skip(1)
+ .rev()
+ .chain(our_key.clone().into_iter())
+ .collect();
+
+ self.insert_node(&full_our_key, node.clone())?;
+ } else {
+ unreachable!("This node will be a parent");
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl Node {
+ pub fn new_child(path: String) -> Self {
+ Self {
+ value: NodeValue::Child {
+ path,
+ extandable: true,
+ },
+ }
+ }
+ pub fn new_parent() -> Self {
+ Self {
+ value: NodeValue::Parent {
+ children: HashMap::new(),
+ },
+ }
+ }
+}
diff --git a/pkgs/sources/lf-make-map/src/mapping/mod.rs b/pkgs/sources/lf-make-map/src/mapping/mod.rs
new file mode 100644
index 00000000..114fdca0
--- /dev/null
+++ b/pkgs/sources/lf-make-map/src/mapping/mod.rs
@@ -0,0 +1,156 @@
+use std::{
+ fmt::{Display, Write},
+ hash::Hash,
+};
+
+use log::debug;
+
+pub mod map_tree;
+
+#[derive(Clone, Debug, Eq)]
+pub struct MapKey {
+ pub key: char,
+
+ resolution: usize,
+
+ /// Part of the path, used to derive the key
+ part_path: String,
+}
+
+impl Hash for MapKey {
+ fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+ self.key.hash(state)
+ }
+}
+
+impl PartialEq for MapKey {
+ fn eq(&self, other: &Self) -> bool {
+ self.key == other.key
+ }
+}
+
+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);
+
+ key.chars()
+ .map(|ch| Self {
+ key: ch,
+ resolution,
+ part_path: part_path.to_owned(),
+ })
+ .collect()
+ }
+
+ pub fn new_ones_from_path(path: &str, number_of_chars: usize) -> Vec<Self> {
+ let key: Vec<MapKey> = path
+ .split('/')
+ .map(|part| Self::new_from_part_path(part, number_of_chars))
+ .flatten()
+ .collect();
+
+ debug!(
+ "Generated full MapKeys: '{}' -> '{}'",
+ path,
+ MapKey::display(&key)
+ );
+ key
+ }
+
+ pub fn increment(&self, target_resolution: usize) -> Vec<Self> {
+ let new_resolution = target_resolution;
+
+ // 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)
+ } else {
+ let mut generated_chars =
+ MapKey::part_path_to_key(&self.part_path, self.part_path.len());
+
+ generated_chars.extend(
+ (0..(new_resolution - self.part_path.len()))
+ .into_iter()
+ .map(|_| self.part_path.chars().last().expect("This will exists")),
+ );
+
+ generated_chars
+ };
+
+ let part_path = self.part_path.clone();
+ let output: Vec<Self> = added_chars
+ .chars()
+ .enumerate()
+ .map(|(res, ch)| MapKey {
+ key: ch,
+ resolution: res + 1,
+ part_path: part_path.clone(),
+ })
+ .collect();
+
+ // debug!("Finished increment: '{}' ('{}')", MapKey::display(&output), output[0].part_path);
+ output
+ }
+
+ pub fn display(values: &[Self]) -> String {
+ values.iter().map(|value| value.key.clone()).collect()
+ }
+ fn part_path_to_key(part: &str, number_of_chars: usize) -> String {
+ fn make(pat: char, part: &str, number_of_chars: usize) -> String {
+ let mut acc = String::new();
+
+ if !part.split(pat).all(|part| part.len() > 0) {
+ panic!(
+ "\
+Can't turn this path '{}' to a mapping.
+This should not happen, please report the bug!",
+ part
+ )
+ }
+
+ let mut last_working = None;
+ for i in 0..number_of_chars {
+ for str in part.split(pat) {
+ if acc.len() != number_of_chars {
+ acc.push(match str.chars().nth(i) {
+ Some(ch) => ch,
+ None => {
+ if let Some(last) = last_working {
+ str.chars().nth(last).expect("This should always exist")
+ } else {
+ last_working = Some(i - 1);
+ str.chars().nth(i - 1).expect("This should always exist")
+ }
+ }
+ })
+ }
+ }
+ }
+
+ acc
+ }
+
+ let value = if part.contains('_') && !part.starts_with('_') && !part.ends_with('_') {
+ make('_', part, number_of_chars)
+ } else if part.contains('-') && !part.starts_with('-') && !part.ends_with('-') {
+ make('-', part, number_of_chars)
+ } else {
+ part.chars().take(number_of_chars).collect::<String>()
+ };
+
+ assert_eq!(
+ value.len(),
+ number_of_chars,
+ "'{}' does not have expected length of: {}",
+ value,
+ number_of_chars
+ );
+ value
+ }
+}
+
+impl Display for MapKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_char(self.key)
+ }
+}
diff --git a/pkgs/sources/lf-make-map/update.sh b/pkgs/sources/lf-make-map/update.sh
new file mode 100755
index 00000000..a0a029f4
--- /dev/null
+++ b/pkgs/sources/lf-make-map/update.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+nix flake update
+
+[ "$1" = "upgrade" ] && cargo upgrade
+cargo update
diff --git a/pkgs/sources/plgs-pkgs/README.md b/pkgs/sources/plgs-pkgs/README.md
new file mode 100644
index 00000000..e8169951
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/README.md
@@ -0,0 +1,92 @@
+# Fork
+
+All files in this repository where forked form [here](https://github.com/NixNeovim/NixNeovimPlugins) on commit `5010b91eb03696574c3c293f072a090618227e87`.
+Below the original README. They were licensed under the MIT license.
+
+# All vim plugins, ready to go
+
+This repo auto generates nix packages for vim/neovim plugins.
+Packages are automatically updated twice per week using a GitHub Actions.
+Plugins are fetched from the `manifest.txt` and [awesome-neovim][0] repo.
+
+This is a fork of [this repo](https://github.com/m15a/nixpkgs-vim-extra-plugins); however, we fetch all additions from the original repo, so we will never have less plugins.
+Further, the original deletes plugins that are available in the nixpkgs. We, instead, try to assemble a list of all available plugins.
+Therefore, to access plugins you will never have to search in two places.
+
+This repo can be used as a stand-alone, by adding it to your inputs.
+However, we recommend to use [NixNeovim](https://github.com/NixNeovim/NixNeovim) modules instead, and use this only when you need a plugins, which does not have a module, yet.
+
+## Available plugins
+
+The [plugins.md](plugins.md) contains an auto-generated list of all available plugins.
+
+## Usage
+
+- We recommend using [NixNeovim](https://github.com/NixNeovim/NixNeovim), and only access the plugins directly when they do not have a module in NixNeovim.
+
+However, you can also use this repo without NixNeovim:
+To access the plugins, you need to add the overlay.
+The overlay adds extra Vim plugins to `pkgs.vimExtraPlugins`.
+First, add this repo to your inputs:
+
+```
+inputs.nixneovimplugins.url = github:jooooscha/nixpkgs-vim-extra-plugins
+```
+
+Next, apply the provided overlay:
+
+```
+nixpkgs.overlays = [
+ inputs.nixneovimplugins.overlays.default
+];
+```
+
+Finally, you can add the packages to your vim/neovim config. For example you can use [NixNeovim](https://github.com/NixNeovim/Nixneovim) or you can add the plugins directly:
+
+```
+ programs.neovim = {
+ plugins = [
+ pkgs.vimExtraPlugins.nvim-colorizer-lua
+ ];
+ }
+```
+
+More info on using neovim with nix can be found here: [NixOS Neovim](https://nixos.wiki/wiki/Neovim)
+
+## Contribution
+
+### How to add a new plugin
+
+#### 1. Add the plugin to manifest.txt:
+
+```
+# Examples
+
+haringsrob/nvim_context_vt
+sourcehut:henriquehbr/ataraxis.lua
+gitlab:yorickpeterse/nvim-pqf
+williamboman/mason.nvim:45b9a4da776d9fb017960b3ac7241161fb7bc578
+foo/bar::baz --> renamed to baz
+foo/bar:dev --> using dev branch
+```
+
+Supported are Github (default), SourceHut, and GitLab.
+
+#### 2. Create a Pull Request
+
+- Create a pull request with the changed manifest.txt (and blacklist.txt if neccessary).
+- A GitHub action will check your contribution and generate all neccessary nix code for your new plugin. It will also take care of sorting and cleaning the manifest.txt
+- After all checks have passed, I will merge your change.
+
+I am happy for any contribution. :)
+
+### How to remove a new plugin
+
+Copy the entry from manifest.txt to blacklist.txt and create a PR.
+The GitHub Actions will do the rest, including removing the entry from manifest.txt
+
+## Credits
+
+This is originally based on work by [m15a](https://github.com/m15a/nixpkgs-vim-extra-plugins)
+
+[0]: https://github.com/rockerBOO/awesome-neovim
diff --git a/pkgs/sources/plgs-pkgs/check.nix b/pkgs/sources/plgs-pkgs/check.nix
new file mode 100644
index 00000000..ad23e2c7
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/check.nix
@@ -0,0 +1,37 @@
+{
+ pkgs,
+ lib,
+ ...
+}: let
+ # checks if a plugin has a license
+ hasLicense = _: pkg: let
+ warn = x: lib.warn x x;
+
+ msg =
+ if builtins.hasAttr "license" pkg.meta
+ then "${pkg.name} has license"
+ else warn "${pkg.name} has no license";
+
+ msg' = lib.replaceStrings [" "] ["-"] msg;
+ in
+ pkgs.runCommandNoCC msg' {} "echo : > $out ";
+
+ # function to check License for all packages
+ check-missing-licenses = let
+ buildInputs =
+ lib.mapAttrsToList
+ hasLicense
+ pkgs.vimExtraPlugins;
+ in
+ pkgs.runCommandNoCC
+ "check-missing-licenses"
+ {inherit buildInputs;}
+ "echo : > $out";
+in {
+ checks =
+ pkgs.vimExtraPlugins
+ // {
+ inherit check-missing-licenses;
+ inherit (pkgs) update-vim-plugins;
+ };
+}
diff --git a/pkgs/sources/plgs-pkgs/default.nix b/pkgs/sources/plgs-pkgs/default.nix
new file mode 100644
index 00000000..0f7cd485
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/default.nix
@@ -0,0 +1,15 @@
+[
+ (final: prev:
+ prev.lib.composeManyExtensions [
+ (self: super: let
+ origin = import ./plugins {
+ inherit (super.vimUtils) buildVimPlugin;
+ inherit (super) lib fetchurl fetchgit;
+ };
+ in {
+ vimExtraPlugins = super.lib.makeExtensible (_: super.lib.recurseIntoAttrs origin);
+ })
+ ]
+ final
+ prev)
+]
diff --git a/pkgs/sources/plgs-pkgs/overrides.nix b/pkgs/sources/plgs-pkgs/overrides.nix
new file mode 100644
index 00000000..e03a78b1
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/overrides.nix
@@ -0,0 +1,34 @@
+final: prev: let
+ inherit (final) lib;
+
+ /*
+ * Mark broken packages here.
+ */
+ markBrokenPackages = self: super:
+ lib.mapAttrs (attrName: broken:
+ super.${attrName}.overrideAttrs (old: {
+ meta = old.meta // {inherit broken;};
+ }))
+ {
+ # <name> = true;
+ };
+
+ /*
+ * Add licenses if missing or incorrect in generated ./pkgs/vim-plugins.nix.
+ */
+ fixLicenses = self: super:
+ lib.mapAttrs (attrName: license:
+ super.${attrName}.overrideAttrs (old: {
+ meta = old.meta // {inherit license;};
+ })) (with lib.licenses; {
+ /*
+ * Example:
+ * plugin-name = [<licenses>]
+ */
+ });
+in {
+ vimExtraPlugins = prev.vimExtraPlugins.extend (lib.composeManyExtensions [
+ markBrokenPackages
+ fixLicenses
+ ]);
+}
diff --git a/pkgs/sources/plgs-pkgs/plugins/.plugins.json b/pkgs/sources/plgs-pkgs/plugins/.plugins.json
new file mode 100644
index 00000000..9331bc8f
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/plugins/.plugins.json
@@ -0,0 +1,7 @@
+{
+ "ThePrimeagen/harpoon:master": "{\"description\": \"\", \"homepage\": \"https://github.com/ThePrimeagen/harpoon\", \"license\": {\"py/reduce\": [{\"py/type\": \"update_vim_plugins.nix.License\"}, {\"py/tuple\": [\"mit\"]}]}, \"name\": \"harpoon\", \"owner\": \"ThePrimeagen\", \"py/object\": \"update_vim_plugins.plugin.GitHubPlugin\", \"source\": {\"py/object\": \"update_vim_plugins.nix.UrlSource\", \"sha256\": \"1w4hi9hbdjwdhb4vwa0x08a25vbcxqg1d5cskm2qvjy5fdlqils0\", \"url\": \"https://github.com/ThePrimeagen/harpoon/archive/ccae1b9bec717ae284906b0bf83d720e59d12b91.tar.gz\"}, \"source_line\": \"ThePrimeagen/harpoon:master\", \"version\": {\"__reduce__\": [{\"py/type\": \"datetime.date\"}, [\"B+cMGg==\"]], \"py/object\": \"datetime.date\"}}",
+ "akinsho/toggleterm.nvim": "{\"description\": \"A neovim lua plugin to help easily manage multiple terminal windows\", \"homepage\": \"https://github.com/akinsho/toggleterm.nvim\", \"license\": {\"py/reduce\": [{\"py/type\": \"update_vim_plugins.nix.License\"}, {\"py/tuple\": [\"gpl3Only\"]}]}, \"name\": \"toggleterm-nvim\", \"owner\": \"akinsho\", \"py/object\": \"update_vim_plugins.plugin.GitHubPlugin\", \"source\": {\"py/object\": \"update_vim_plugins.nix.UrlSource\", \"sha256\": \"0nx69q9597vy7lzvvh58fnjyin23ns6apmyp532sgf547bw7mld6\", \"url\": \"https://github.com/akinsho/toggleterm.nvim/archive/cbd041d91b90cd3c02df03fe6133208888f8e008.tar.gz\"}, \"source_line\": \"akinsho/toggleterm.nvim\", \"version\": {\"__reduce__\": [{\"py/type\": \"datetime.date\"}, [\"B+cMBg==\"]], \"py/object\": \"datetime.date\"}}",
+ "andrewferrier/debugprint.nvim": "{\"description\": \"Debugging in NeoVim the print() way!\", \"homepage\": \"https://github.com/andrewferrier/debugprint.nvim\", \"license\": {\"py/reduce\": [{\"py/type\": \"update_vim_plugins.nix.License\"}, {\"py/tuple\": [\"mit\"]}]}, \"name\": \"debugprint-nvim\", \"owner\": \"andrewferrier\", \"py/object\": \"update_vim_plugins.plugin.GitHubPlugin\", \"source\": {\"py/object\": \"update_vim_plugins.nix.UrlSource\", \"sha256\": \"06r1jhx7jd15q8wvnw0xqwk3bkx39pm4pbv70hf9ggd6zsnmsrmn\", \"url\": \"https://github.com/andrewferrier/debugprint.nvim/archive/54297dd0a4f318b279a1cb954e7714f3942df123.tar.gz\"}, \"source_line\": \"andrewferrier/debugprint.nvim\", \"version\": {\"__reduce__\": [{\"py/type\": \"datetime.date\"}, [\"B+gDHQ==\"]], \"py/object\": \"datetime.date\"}}",
+ "lmburns/lf.nvim": "{\"description\": \"Lf file manager for Neovim (in Lua)\", \"homepage\": \"https://github.com/lmburns/lf.nvim\", \"license\": {\"py/reduce\": [{\"py/type\": \"update_vim_plugins.nix.License\"}, {\"py/tuple\": [\"mit\"]}]}, \"name\": \"lf-nvim\", \"owner\": \"lmburns\", \"py/object\": \"update_vim_plugins.plugin.GitHubPlugin\", \"source\": {\"py/object\": \"update_vim_plugins.nix.UrlSource\", \"sha256\": \"1nwf90bnzqhlgs007gg6xpx0vf4r1d19586nld78ipi1ch7nz4px\", \"url\": \"https://github.com/lmburns/lf.nvim/archive/69ab1efcffee6928bf68ac9bd0c016464d9b2c8b.tar.gz\"}, \"source_line\": \"lmburns/lf.nvim\", \"version\": {\"__reduce__\": [{\"py/type\": \"datetime.date\"}, [\"B+cKAw==\"]], \"py/object\": \"datetime.date\"}}",
+ "nvim-telescope/telescope-bibtex.nvim": "{\"description\": \"A telescope.nvim extension to search and paste bibtex entries into your TeX files.\", \"homepage\": \"https://github.com/nvim-telescope/telescope-bibtex.nvim\", \"license\": {\"py/reduce\": [{\"py/type\": \"update_vim_plugins.nix.License\"}, {\"py/tuple\": [\"mit\"]}]}, \"name\": \"telescope-bibtex-nvim\", \"owner\": \"nvim-telescope\", \"py/object\": \"update_vim_plugins.plugin.GitHubPlugin\", \"source\": {\"py/object\": \"update_vim_plugins.nix.UrlSource\", \"sha256\": \"1sd6p8cvv3dckgrhc7grlyfcibjxhxbfyh0w7p5m4mdcazhy1kqs\", \"url\": \"https://github.com/nvim-telescope/telescope-bibtex.nvim/archive/289a6f86ebec06e8ae1590533b732b9981d84900.tar.gz\"}, \"source_line\": \"nvim-telescope/telescope-bibtex.nvim\", \"version\": {\"__reduce__\": [{\"py/type\": \"datetime.date\"}, [\"B+gDHA==\"]], \"py/object\": \"datetime.date\"}}"
+} \ No newline at end of file
diff --git a/pkgs/sources/plgs-pkgs/plugins/blacklist.txt b/pkgs/sources/plgs-pkgs/plugins/blacklist.txt
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/plugins/blacklist.txt
@@ -0,0 +1 @@
+
diff --git a/pkgs/sources/plgs-pkgs/plugins/default.nix b/pkgs/sources/plgs-pkgs/plugins/default.nix
new file mode 100644
index 00000000..df09e446
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/plugins/default.nix
@@ -0,0 +1,55 @@
+{
+ lib,
+ buildVimPlugin,
+ fetchurl,
+ fetchgit,
+}: {
+ /*
+ Generated from: ThePrimeagen/harpoon:master
+ */
+ harpoon = buildVimPlugin {
+ pname = "harpoon";
+ version = "2023-12-26";
+ src = fetchurl {
+ url = "https://github.com/ThePrimeagen/harpoon/archive/ccae1b9bec717ae284906b0bf83d720e59d12b91.tar.gz";
+ sha256 = "1w4hi9hbdjwdhb4vwa0x08a25vbcxqg1d5cskm2qvjy5fdlqils0";
+ };
+ meta = with lib; {
+ description = "";
+ homepage = "https://github.com/ThePrimeagen/harpoon";
+ license = with licenses; [mit];
+ };
+ };
+ /*
+ Generated from: lmburns/lf.nvim
+ */
+ lf-nvim = buildVimPlugin {
+ pname = "lf-nvim";
+ version = "2023-10-03";
+ src = fetchurl {
+ url = "https://github.com/lmburns/lf.nvim/archive/69ab1efcffee6928bf68ac9bd0c016464d9b2c8b.tar.gz";
+ sha256 = "1nwf90bnzqhlgs007gg6xpx0vf4r1d19586nld78ipi1ch7nz4px";
+ };
+ meta = with lib; {
+ description = "Lf file manager for Neovim (in Lua)";
+ homepage = "https://github.com/lmburns/lf.nvim";
+ license = with licenses; [mit];
+ };
+ };
+ /*
+ Generated from: nvim-telescope/telescope-bibtex.nvim
+ */
+ telescope-bibtex-nvim = buildVimPlugin {
+ pname = "telescope-bibtex-nvim";
+ version = "2024-03-28";
+ src = fetchurl {
+ url = "https://github.com/nvim-telescope/telescope-bibtex.nvim/archive/289a6f86ebec06e8ae1590533b732b9981d84900.tar.gz";
+ sha256 = "1sd6p8cvv3dckgrhc7grlyfcibjxhxbfyh0w7p5m4mdcazhy1kqs";
+ };
+ meta = with lib; {
+ description = "A telescope.nvim extension to search and paste bibtex entries into your TeX files.";
+ homepage = "https://github.com/nvim-telescope/telescope-bibtex.nvim";
+ license = with licenses; [mit];
+ };
+ };
+}
diff --git a/pkgs/sources/plgs-pkgs/plugins/manifest.txt b/pkgs/sources/plgs-pkgs/plugins/manifest.txt
new file mode 100644
index 00000000..615083c8
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/plugins/manifest.txt
@@ -0,0 +1,3 @@
+lmburns/lf.nvim
+nvim-telescope/telescope-bibtex.nvim
+ThePrimeagen/harpoon:master
diff --git a/pkgs/sources/plgs-pkgs/plugins/plugins.md b/pkgs/sources/plgs-pkgs/plugins/plugins.md
new file mode 100644
index 00000000..4f73f811
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/plugins/plugins.md
@@ -0,0 +1,7 @@
+- Plugin count: 3
+
+| Repo | Last Update | Nix package name | Last checked |
+|:---|:---|:---|:---|
+| [ThePrimeagen/harpoon:master](https://github.com/ThePrimeagen/harpoon) | 2023-12-26 | `harpoon` | 2024-05-09 |
+| [lmburns/lf.nvim](https://github.com/lmburns/lf.nvim) | 2023-10-03 | `lf-nvim` | 2024-05-09 |
+| [nvim-telescope/telescope-bibtex.nvim](https://github.com/nvim-telescope/telescope-bibtex.nvim) | 2024-03-28 | `telescope-bibtex-nvim` | 2024-05-09 |
diff --git a/pkgs/sources/plgs-pkgs/plugins/whitelist.txt b/pkgs/sources/plgs-pkgs/plugins/whitelist.txt
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/plugins/whitelist.txt
diff --git a/pkgs/sources/plgs-pkgs/update.sh b/pkgs/sources/plgs-pkgs/update.sh
new file mode 100755
index 00000000..6a0d3452
--- /dev/null
+++ b/pkgs/sources/plgs-pkgs/update.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env sh
+
+BASE_DIR="$(readlink -f "$(dirname "$0")/plugins")"
+
+# Fetch plugins
+cd "$BASE_DIR" || (echo "BUG: No '$BASE_DIR'" && exit 1)
+
+# Cleanup manifest
+sort -o "$BASE_DIR/manifest.txt" "$BASE_DIR/manifest.txt"
+sort -o "$BASE_DIR/blacklist.txt" "$BASE_DIR/blacklist.txt"
+## Remove all plugins, which are on the blacklist
+# The same file is read and written to
+# shellcheck disable=SC2005
+echo "$(comm -23 "$BASE_DIR/manifest.txt" "$BASE_DIR/blacklist.txt")" >"$BASE_DIR/manifest.txt"
+
+# Backup vim-plugins.nix
+mv "$BASE_DIR/default.nix" "$BASE_DIR/default.nix.bak"
+echo "{...} : {}" >"$BASE_DIR/default.nix"
+
+# Generate derivations for new plugins (this binary is provided by the dev-environment)
+update-vim-plugins cleanup "$BASE_DIR"
+
+# Restore vim-plugins.nix
+mv "$BASE_DIR/default.nix.bak" "$BASE_DIR/default.nix"
+
+# Update new plugins
+update-vim-plugins update "$BASE_DIR" --all
diff --git a/pkgs/sources/scripts/default.nix b/pkgs/sources/scripts/default.nix
new file mode 100644
index 00000000..09c8d411
--- /dev/null
+++ b/pkgs/sources/scripts/default.nix
@@ -0,0 +1,412 @@
+{
+ sysLib,
+ homeConfig,
+ nixosConfig,
+}: [
+ (
+ final: prev: let
+ inherit (prev) lib;
+
+ write_shell = {
+ name,
+ path,
+ dependencies ? [],
+ keepPath ? false,
+ completions ? false,
+ }:
+ sysLib.writeShellScript {
+ inherit name keepPath;
+ src = ./source/${path}/${name}.sh;
+ dependencies = dependencies ++ [prev.dash];
+ generateCompletions = completions;
+ };
+ write_python = {
+ name,
+ path,
+ dependencies_system ? [],
+ dependencies_python ? _: [],
+ keepPath ? false,
+ }: let
+ src = ./source/${path}/${name}.py;
+ dependencies =
+ [(prev.python3.withPackages dependencies_python)]
+ ++ dependencies_system;
+ path_setting =
+ if keepPath
+ then "--prefix PATH :"
+ else "--set PATH";
+ in
+ prev.runCommandLocal name {
+ nativeBuildInputs = [prev.makeWrapper] ++ dependencies;
+ }
+ ''
+ install -m755 ${src} -D "$out/bin/${name}"
+ patchShebangs "$out/bin/${name}"
+ wrapProgram "$out/bin/${name}" ${path_setting} ${prev.lib.makeBinPath dependencies};
+ '';
+
+ ## Begin of shell scripts
+ aumo-scr = write_shell {
+ name = "aumo";
+ path = "apps";
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ udisks
+ findutils
+ rofi
+ ;
+ };
+ };
+
+ battery-scr = write_shell {
+ name = "battery";
+ path = "wrappers";
+ dependencies = [];
+ };
+
+ brightness-scr = lib.mkIf nixosConfig.soispha.laptop.enable (write_shell {
+ name = "brightness";
+ path = "small_functions";
+ generateCompletions = true;
+ dependencies = [];
+ replacementStrings = {BACKLIGHT_NAME = nixosConfig.soispha.laptop.backlight;};
+ });
+
+ con2pdf-scr = sysLib.writeShellScript {
+ name = "con2pdf";
+ src = ./source/apps/con2pdf.sh;
+ dependencies = builtins.attrValues {inherit (prev) sane-backends imagemagick coreutils fd;};
+ generateCompletions = true;
+ replacementStrings = {
+ DEVICE_FUNCTION =
+ # This is here, because escaping the whole function, to use it in the shell script
+ # directly just isn't possible
+ prev.writeText "DEVICE_FUNCTION"
+ /*
+ bash
+ */
+ ''
+ scanimage -L | awk 'BEGIN { FS = "`" } { gsub(/'.*/, "", $2); print $2 }'
+ '';
+ };
+ };
+
+ description-scr = write_shell {
+ name = "description";
+ path = "specific/ytcc";
+ dependencies = builtins.attrValues {
+ inherit (prev) jq fmt less locale;
+ };
+ };
+
+ fupdate-scr = write_shell {
+ name = "fupdate";
+ path = "apps";
+ keepPath = true;
+ completions = true;
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ dash
+ nix
+ gnugrep
+ fd
+ coreutils
+ bat # used by batgrep
+ gnused # required by batgrep
+ git # needed to fetch through git
+ ;
+ inherit (prev.bat-extras) batgrep;
+ };
+ };
+
+ git-edit-index-scr = write_shell {
+ name = "git-edit-index";
+ path = "apps";
+ completions = true;
+ # This starts neovim, wich might want to shell out
+ keepPath = true;
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ git
+ gnused
+ # $EDITOR
+
+ ;
+ };
+ };
+
+ hibernate-scr = write_shell {
+ name = "hibernate";
+ path = "wrappers";
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ systemd
+ taskwarrior
+ ;
+ };
+ };
+
+ ll-scr = sysLib.writeShellScript {
+ name = "ll";
+ src = ./source/wrappers/ll.sh;
+ wrap = false;
+ };
+
+ # TODO: this need to be replaced with a wayland alternative
+ # llp-scr = write_shell {
+ # name = "llp";
+ # path = "wrappers";
+ # dependencies = builtins.attrValues {inherit (prev) lf ueberzug;};
+ # };
+
+ lock-scr = write_shell {
+ name = "lock";
+ path = "wrappers";
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ taskwarrior
+ swaylock
+ ;
+ };
+ };
+
+ lyrics-scr = write_shell {
+ name = "lyrics";
+ path = "wrappers";
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ exiftool
+ mpc-cli
+ jq
+ less
+ locale # dependency of less
+ ;
+ };
+ };
+
+ mpc-fav-scr = write_shell {
+ name = "mpc-fav";
+ path = "wrappers";
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ mpc-cli
+ ;
+ };
+ };
+
+ mpc-rm-scr = write_shell {
+ name = "mpc-rm";
+ path = "wrappers";
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ mpc-cli
+ trash-cli
+ ;
+ };
+ };
+
+ mpc-scr = write_shell {
+ name = "mpc";
+ path = "wrappers";
+ dependencies = [
+ mpc-fav-scr
+ mpc-rm-scr
+ prev.mpc-cli
+ ];
+ };
+
+ nato-scr = write_python {
+ name = "nato";
+ path = "small_functions";
+ dependencies_python = ps: [];
+ };
+
+ neorg-scr = sysLib.writeShellScriptMultiPart {
+ name = "neorg";
+ keepPath = true;
+ src = ./source/specific/neorg/sh;
+ baseName = "main.sh";
+ cmdPrefix = "functions";
+ cmdNames = [
+ "add.sh"
+ "context.sh"
+ "dmenu.sh"
+ "f_start.sh"
+ "f_stop.sh"
+ "list.sh"
+ "project.sh"
+ "review.sh"
+ "utils.sh"
+ "workspace.sh"
+ ];
+ dependencies = with prev; [
+ cocogitto
+ rofi
+ libnotify
+ ];
+ generateCompletions = true;
+ replacementStrings = {
+ DEFAULT_NEORG_PROJECT_DIR =
+ homeConfig.programs.nixvim.plugins.neorg.modules."core.dirman".config.workspaces.projects;
+ HOME_TASKRC = "${homeConfig.xdg.configHome}/task/home-manager-taskrc";
+ NEORG_REVIEW_PATH = "${homeConfig.xdg.dataHome}/neorg/review";
+ ALL_PROJECTS_NEWLINE = "${homeConfig.soispha.taskwarrior.projects.projects_newline}";
+ ALL_PROJECTS_COMMA = "${homeConfig.soispha.taskwarrior.projects.projects_comma}";
+ ALL_PROJECTS_PIPE = "${homeConfig.soispha.taskwarrior.projects.projects_pipe}";
+ ALL_WORKSPACES = "${lib.strings.concatStringsSep "|" (builtins.attrNames homeConfig.programs.nixvim.plugins.neorg.modules."core.dirman".config.workspaces)}";
+ ID_GENERATION_FUNCTION = "${sysLib.writeShellScript {
+ name = "neorg_id_function";
+ src = ./source/specific/neorg/neorg_id_function.sh;
+ dependencies = with prev; [
+ taskwarrior
+ gawk
+ findutils # xargs
+ ];
+ }}/bin/neorg_id_function";
+
+ # TODO: Replace the hard-coded path here with some reference <2023-10-20>
+ TASK_PROJECT_FILE = "/home/soispha/repos/nix/nixos-config/hm/soispha/conf/taskwarrior/projects/default.nix";
+ };
+ };
+
+ screenshot_persistent-scr = write_shell {
+ name = "screenshot_persistent";
+ path = "small_functions";
+ keepPath = true;
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ grim
+ slurp
+ alacritty
+ rofi
+ libnotify
+ lf # TODO: add llp
+ ;
+ };
+ };
+
+ screenshot_temporary-scr = write_shell {
+ name = "screenshot_temporary";
+ path = "small_functions";
+ dependencies = builtins.attrValues {inherit (prev) grim slurp wl-clipboard;};
+ };
+
+ show-scr = write_shell {
+ name = "show";
+ path = "wrappers";
+ keepPath = true; # I might want to use nvim in less (and shell escapes)
+ dependencies = builtins.attrValues {inherit (prev) less locale;};
+ };
+
+ sort_song-scr = write_shell {
+ name = "sort_song";
+ path = "wrappers";
+ dependencies = builtins.attrValues {inherit (prev) mediainfo jq gawk;};
+ };
+
+ spodi-scr = sysLib.writeShellScriptMultiPart {
+ name = "spodi";
+ keepPath = false;
+ src = ./source/specific/spodi;
+ baseName = "spodi.sh";
+ cmdPrefix = "sh";
+ cmdNames = [
+ "download.sh"
+ "update.sh"
+ ];
+ dependencies = with prev; [
+ gawk
+ expect
+ spotdl
+ fd
+ coreutils
+ ];
+ generateCompletions = true;
+ replacementStrings = {
+ XDG_CACHE_HOME = homeConfig.xdg.cacheHome;
+ XDG_MUSIC_DIR = homeConfig.xdg.userDirs.music;
+ };
+ };
+
+ update-sys-scr = write_shell {
+ name = "update-sys";
+ path = "small_functions";
+ completions = true;
+ dependencies = builtins.attrValues {
+ inherit
+ (prev)
+ git
+ nixos-rebuild
+ sudo
+ openssh
+ coreutils
+ mktemp
+ gnugrep
+ gnused
+ systemd
+ ;
+ };
+ };
+
+ virsh-del-scr = write_shell {
+ name = "virsh-del";
+ path = "wrappers";
+ dependencies = builtins.attrValues {inherit (prev) libvirt;};
+ };
+
+ yti-scr = write_shell {
+ name = "yti";
+ path = "wrappers";
+ dependencies = builtins.attrValues {inherit (prev) gawk expect yt-dlp;};
+ };
+ in {
+ scripts = {
+ # llp = llp-scr; # TODO: see above
+ aumo = aumo-scr;
+ battery = battery-scr;
+ brightness = brightness-scr;
+ con2pdf = con2pdf-scr;
+ description = description-scr;
+ fupdate = fupdate-scr;
+ git-edit-index = git-edit-index-scr;
+ hibernate = hibernate-scr;
+ ll = ll-scr;
+ lock = lock-scr;
+ lyrics = lyrics-scr;
+ mpc = mpc-scr;
+ mpc-fav = mpc-fav-scr;
+ mpc-rm = mpc-rm-scr;
+ nato = nato-scr;
+ neorg = neorg-scr;
+ screenshot_persistent = screenshot_persistent-scr;
+ screenshot_temporary = screenshot_temporary-scr;
+ show = show-scr;
+ sort_song = sort_song-scr;
+ spodi = spodi-scr;
+ update-sys = update-sys-scr;
+ virsh-del = virsh-del-scr;
+ yti = yti-scr;
+ };
+ }
+ )
+]
+
+
+ pkgs = import nixpkgs (import ./sys/nixpkgs {
+ inherit (nixpkgs) lib;
+ inherit system sysLib;
+
+ # FIXME: Don't unconditionally use tiamat here <2024-02-24>
+ homeConfig = self.nixosConfigurations.tiamat.config.home-manager.users.soispha;
+ nixosConfig = self.nixosConfigurations.tiamat.config;
+ overlays = [];
+ });
diff --git a/pkgs/sources/scripts/source/apps/aumo.sh b/pkgs/sources/scripts/source/apps/aumo.sh
new file mode 100755
index 00000000..84d39deb
--- /dev/null
+++ b/pkgs/sources/scripts/source/apps/aumo.sh
@@ -0,0 +1,28 @@
+#! /usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+unmounting() {
+ disk_name="$(find /dev/disk/by-label -type l -printf "%P|" | rofi -sep "|" -dmenu -p "Select disk to mount")"
+
+ udisksctl unmount --block-device "/dev/disk/by-label/$disk_name"
+}
+
+mounting() {
+ disk_name="$(find /dev/disk/by-label -type l -printf "%P|" | rofi -sep "|" -dmenu -p "Select disk to mount")"
+
+ udisksctl mount --block-device "/dev/disk/by-label/$disk_name"
+}
+
+case "$1" in
+"mount")
+ mounting
+ ;;
+"unmount" | "umount")
+ unmounting
+ ;;
+*)
+ die "Usage: $NAME mount|unmount"
+ ;;
+esac
diff --git a/pkgs/sources/scripts/source/apps/con2pdf.sh b/pkgs/sources/scripts/source/apps/con2pdf.sh
new file mode 100755
index 00000000..08bf8998
--- /dev/null
+++ b/pkgs/sources/scripts/source/apps/con2pdf.sh
@@ -0,0 +1,234 @@
+#! /usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# needed for help() and version
+# shellcheck disable=2034
+AUTHORS="Soispha"
+# shellcheck disable=2034
+YEARS="2023"
+# shellcheck disable=2034
+VERSION="1.0.0"
+
+# NAME is from the wrapper
+# shellcheck disable=SC2269
+NAME="$NAME"
+help() {
+ cat <<EOF
+Scan images and turn them into a pdf.
+
+Usage:
+ $NAME [OPTIONS] --name --device
+
+OPTIONS:
+ --out-dir | -o [FILE]
+ Path to place the generated pdf files (default: ./pdf).
+
+ --name | -n NAME
+ Name for the pdf files (e.g. <NAME>_1.pdf).
+
+ --num-pages | -p NUM
+ Number of pages to merge into one pdf (default: 1).
+
+ --device | -d DEVICE
+ Device used for scanning.
+
+ --method | -m METHOD
+ Method to use for scanning (default: ADF).
+
+ --help | -h
+ Display this help and exit.
+
+ --version | -v
+ Display version and copyright information and exit.
+ARGUMENTS:
+ FILE := [[fd . --max-depth 3]]
+ A name of a file to store, default is: ./pdf
+
+ NAME | * := [[fd . --max-depth 3]]
+ The basename of the generated files
+
+ NUM | *([0-9]) := 0 | 1 | 2 | 3 | 4
+ Possible numbers of pages, can be more than 4
+
+ DEVICE := [[$(cat %DEVICE_FUNCTION)]]
+ Possible scanner names
+
+ METHOD := ADF | Flatbed
+ The scanning method to use, not all scanners support both of
+ these. The default is ADF
+EOF
+}
+
+scan_adf() {
+ device="$1"
+ sides_per_page="$2"
+ method="ADF"
+ for i in $(seq "$sides_per_page"); do
+ do_until_success \
+ "scanimage --format=tiff --progress --source='$method' --device='$device' --batch=%d.tif --batch-increment='$sides_per_page' --batch-start='$i'" \
+ "warn 'Retrying scan, as we assume a network error!'"
+
+ if [ "$sides_per_page" -ne 1 ]; then
+ msg "Finished turn, please change side!"
+ readp "Press enter to continue" noop
+ fi
+ done
+}
+process_images_adf() {
+ tiff_temp_path="$1"
+ output_directory="$2"
+ name="$3"
+
+ counter=0
+ pdf_counter=0
+ image_cache="$(mktmp)"
+ while read -r scanned_image; do
+ dbg "$scanned_image (scanned_image) at $counter (counter)"
+ echo "$scanned_image" >>"$image_cache"
+ : $((counter += 1))
+ if [ "$counter" = "$number_of_pages" ]; then
+ dbg "$counter == $number_of_pages"
+ counter=0
+ convert_images "$image_cache" "${name}_$pdf_counter" "$output_directory"
+ : $((pdf_counter += 1))
+ printf "" >"$image_cache"
+ fi
+ done <"$(tmp_pipe fd . "$tiff_temp_path" "|" sort -V)"
+}
+
+scan_flatbed() {
+ device="$1"
+ number_of_pages"$2"
+ method="Flatbed"
+ for i in $(seq "$number_of_pages"); do
+ do_until_success \
+ "scanimage --format=tiff --progress --source='$method' --device='$device' --output-file=$i.tiff" \
+ "warn 'Retrying scan, as we assume a network error!'"
+ if [ "$number_of_pages" -ne 1 ]; then
+ msg "Finished turn, please change side!"
+ readp "Press enter to continue" noop
+ fi
+ done
+}
+process_images_flatbed() {
+ tiff_temp_path="$1"
+ output_directory="$2"
+ name="$3"
+
+ counter=0
+ image_cache="$(mktmp)"
+ while read -r scanned_image; do
+ echo "$scanned_image" >>"$image_cache"
+ : $((counter += 1))
+ if [ "$counter" = "$number_of_pages" ]; then
+ counter=0
+ convert_images "$image_cache" "$name" "$output_directory"
+ printf "" >"$image_cache"
+ fi
+ done <"$(tmp_pipe fd . "$tiff_temp_path" "|" sort -V)"
+}
+convert_images() {
+ image_cache="$1"
+ pdf_name="$2"
+ output_dir="$3"
+
+ set --
+ while read -r image; do
+ dbg "setting image: $image"
+ set -- "$@" "$image"
+ done <"$image_cache"
+
+ while [ -e "$output_dir/${pdf_name}.pdf" ]; do
+ pdf_name="${pdf_name}_$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 25)"
+ done
+ dbg "using pdf_name: $pdf_name"
+ convert "$@" -compress jpeg -quality 100 "$output_dir/${pdf_name}.pdf"
+}
+
+scan() {
+ number_of_pages="$1"
+ device="$2"
+ output_directory="$(readlink -f "$3")"
+ name="$4"
+ method="$5"
+
+ [ -z "$number_of_pages" ] && die "Parameter 'number_of_pages' is not set!"
+ [ -z "$device" ] && die "Parameter 'device' is not set!"
+ [ -z "$output_directory" ] && die "Parameter 'output_directory' is not set!"
+ [ -z "$name" ] && die "Parameter 'name' is not set!"
+ [ -z "$method" ] && die "Parameter 'method' is not set!"
+
+ tiff_temp_path="$(mktmp -d)"
+ cd "$tiff_temp_path" || die "Bug"
+
+ msg "Started scanning..."
+ if [ "$method" = "Flatbed" ]; then
+ scan_flatbed "$device" "$number_of_pages"
+ else
+ scan_adf "$device" "$number_of_pages"
+ fi
+
+ msg "Creating output directory..."
+ mkdir "$output_directory"
+ cd "$output_directory" || die "Bug"
+
+ msg "Converting images to pdfs..."
+ if [ "$method" = "Flatbed" ]; then
+ process_images_flatbed "$tiff_temp_path" "$output_directory" "$name"
+ else
+ process_images_adf "$tiff_temp_path" "$output_directory" "$name"
+ fi
+}
+
+for input in "$@"; do
+ case "$input" in
+ "--help" | "-h")
+ help
+ exit 0
+ ;;
+ "--version" | "-v")
+ version
+ exit 0
+ ;;
+ esac
+done
+
+number_of_pages="1"
+unset device
+output_directory="$(pwd)/pdf"
+unset name
+method="ADF"
+
+while [ "$#" -ne 0 ]; do
+ case "$1" in
+ "--help" | "-h") ;;
+ "--version" | "-v") ;;
+ "--out-dir" | "-o")
+ shift 1
+ output_directory="$1"
+ ;;
+ "--name" | "-n")
+ shift 1
+ name="$1"
+ ;;
+ "--num-pages" | "-p")
+ shift 1
+ number_of_pages="$1"
+ ;;
+ "--device" | "-d")
+ shift 1
+ device="$1"
+ ;;
+ "--method" | "-m")
+ shift 1
+ method="$1"
+ ;;
+ *)
+ die "Command line arg $1 does not exist. See --help for a list."
+ ;;
+ esac
+ shift 1
+done
+scan "$number_of_pages" "$device" "$output_directory" "$name" "$method"
diff --git a/pkgs/sources/scripts/source/apps/fupdate.1.md b/pkgs/sources/scripts/source/apps/fupdate.1.md
new file mode 100644
index 00000000..710e8fb7
--- /dev/null
+++ b/pkgs/sources/scripts/source/apps/fupdate.1.md
@@ -0,0 +1,70 @@
+% FUPDATE(1) fupdate 1.0.0
+% Soispha
+% May 2023
+
+# NAME
+
+fupdate - updates your flake, while checking for common mistakes
+
+# SYNOPSIS
+
+**fupdate** list of \[*flake*|*\<some word>*|*--help*|*-h*\]
+
+# DESCRIPTION
+
+Argument can be stacked, this makes it possible to specify multiple targets to be updated in succession. See the Examples section for further details.
+
+No argument or *flake*
+: **fupdate**, when executed without arguments or with *flake*, will update your *flake.lock*, check for duplicate flake inputs, i.e., an input has an input declared, which you have also declared as input, and will run a script called *update.sh*, if you allow it.
+The allowance for the script is asked, when you run **fupdate** and the found script is not yet allowed. Furthermore, the allowance is based on the concrete sha256 hash of the script, so any changes will require another allowance.
+
+**\<some word>** as argument
+: If the executable **update-\<some word>** is reachable thought the PATH variable, than this is run. Otherwise, the program will exit.
+
+# OPTIONS
+
+**--help**, **-h**
+: Displays a help message and exit.
+
+**--version**, **-v**
+: Displays the software version and exit.
+
+# EXAMPLES
+
+**fupdate** or **fupdate flake**
+: Updates your *flake.lock*. See the Description section for further details.
+
+**fupdate sys**
+: Run the executable **update-sys**, if it exists. See the Description section for further details.
+
+**fupdate flake sys docs**
+: First updates your flake, then, if the command succeeded, runs **update-sys**, afterweich **update-docs** is run.
+
+# FILES
+
+*update.sh*
+: This is supposed to be a shell script located in your flake base directory, i.e., the directory which contains both a *flake.nix* and a *flake.lock* file.
+
+*~/.local/share/flake-update/*
+: **fupdate** will store the hashes to the allowed *update.sh* files here.
+
+# BUGS
+
+Report bugs to <https://codeberg.org/soispha/flake_update/issues>.
+
+# COPYRIGHT
+
+Copyright (C) 2023 Soispha
+
+This program 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.
+
+This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
diff --git a/pkgs/sources/scripts/source/apps/fupdate.sh b/pkgs/sources/scripts/source/apps/fupdate.sh
new file mode 100755
index 00000000..4322610a
--- /dev/null
+++ b/pkgs/sources/scripts/source/apps/fupdate.sh
@@ -0,0 +1,197 @@
+#! /usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+UPDATE_SCRIPT_NAME="update.sh"
+CONFIG_DIRECTORY_PATH="$HOME/.local/share/flake-update"
+
+# Both are used in version()
+# shellcheck disable=SC2034
+AUTHORS="Soispha"
+# shellcheck disable=SC2034
+YEARS="2023"
+
+UPDATE_SCRIPT_NOT_WANTED=false
+
+# Searches upward for a `UPDATE_SCRIPT_NAME` script
+# Returns a path to the script if it exists, otherwise nothing is returned
+check_for_update_script() {
+ dirname="$(search_upward_files "$UPDATE_SCRIPT_NAME")"
+ if [ "$dirname" ]; then
+ printf "%s/%s" "$dirname" "$UPDATE_SCRIPT_NAME"
+ fi
+}
+
+# Checks if a given path to the update script is allowed.
+# Takes the path as input
+# Return 0, if allowed, 1 if not.
+check_for_allowed_update_script() {
+ update_script="$1"
+ config_path="${CONFIG_DIRECTORY_PATH}${update_script}"
+ update_script_hash="$(sha256sum "$update_script")"
+ if [ -f "$config_path" ]; then
+ if [ "$(cat "$config_path")" = "$update_script_hash" ]; then
+ dbg "Recorded hash matches"
+ return 0
+ else
+ dbg "Recorded hash \'$(cat "$config_path")\' does not match real hash \'$update_script_hash\', assuming not allowed"
+ return 1
+ fi
+ else
+ dbg "Path \'$config_path\' does not exist, assuming not allowed"
+ return 1
+ fi
+}
+
+# Asks the user if they want to allow a given script.
+# Takes the path as input
+ask_to_allow_update_script() {
+ update_script="$1"
+ config_path="${CONFIG_DIRECTORY_PATH}${update_script}"
+ update_script_hash="$(sha256sum "$update_script")"
+ println "\033[2J" # clear the screen
+ cat "$update_script"
+ readp "Do you want to allow this script?[N/y]: " allow
+ # shellcheck disable=SC2154
+ dbg "allow is: $allow"
+ case "$allow" in
+ [yY])
+ dbg "allowed script"
+ dbg "storing contents in: $config_path"
+ mkdir --parents "$(dirname "$config_path")"
+ print "$update_script_hash" >"$config_path"
+ ;;
+ *)
+ UPDATE_SCRIPT_NOT_ALLOWED=true
+ ;;
+ esac
+}
+
+# Runs the provided script and continues to update the nix flake
+# Takes the path to the script and the directory to the flake as arguments
+# If the path to the update script is empty, it will be ignored
+update() {
+ update_script="$1"
+ flake_base_dir="$2"
+ shift 2
+ dbg "Provided following args to update script: '$*'"
+
+ cd "$flake_base_dir" || die "Provided dir \'$flake_base_dir\' can not be accessed"
+ dbg "changed directory to: $flake_base_dir"
+
+ nix flake update
+
+ if ! [ "$update_script" = "" ] && ! [ "$UPDATE_SCRIPT_NOT_WANTED" = "true" ]; then
+ "$update_script" "$@"
+ fi
+
+ if grep '[^0-9]_[0-9]' flake.lock >/dev/null; then
+ batgrep '[^0-9]_[0-9]' flake.lock
+ die "Your flake.nix contains duplicate inputs!"
+ fi
+}
+
+help() {
+ cat <<EOF
+This is a Nix flake update manager.
+
+USAGE:
+ $NAME [--help | --version] [flake [--no-script] | <some other command>]
+
+OPTIONS:
+ --help | -h
+ Display this help and exit.
+
+ --version | -v
+ Display version and copyright information and exit.
+
+ --no-script
+ Avoid running the 'update.sh' script
+COMMANDS:
+ flake
+ update the flake project
+
+ <some other command>
+ runs a executable called "update-<some other command>", if it exists
+EOF
+}
+
+main() {
+ if ! [ "$UPDATE_SCRIPT_NOT_ALLOWED" = true ]; then
+ update_script="$(check_for_update_script)"
+ flake_base_dir="$(search_flake_base_dir)" # Assume, that the update script is in the base dir
+ dbg "update_script is: $update_script"
+ dbg "flake_base_dir is: $flake_base_dir"
+
+ if [ "$update_script" = "" ]; then
+ update "" "$flake_base_dir" "$@"
+ elif check_for_allowed_update_script "$update_script" && ! [ "$update_script" = "" ]; then
+ update "$update_script" "$flake_base_dir" "$@"
+ else
+ ask_to_allow_update_script "$update_script"
+ main "$@"
+ fi
+ fi
+}
+
+if [ "$#" -eq 0 ]; then
+ main
+ exit 0
+fi
+
+for input in "$@"; do
+ case "$input" in
+ "--help" | "-h")
+ help
+ exit 0
+ ;;
+ "--version" | "-v")
+ version
+ exit 0
+ ;;
+ "--no-script" | "-n")
+ UPDATE_SCRIPT_NOT_WANTED=true
+ ;;
+ "--")
+ end_of_cli_options=true
+
+ # Stop processing args after that marker.
+ break
+ ;;
+ esac
+ [ "$end_of_cli_options" = "true" ] && break
+done
+
+case "$1" in
+"flake")
+ shift 1
+
+ # Filter out fupdate specific flags
+ while [ "$1" != "--" ]; do
+ # FIXME: This check allows to add a flag multiple times, but this should probably
+ # not be allowed <2024-03-29>
+ case "$1" in
+ "--no-script" | "-n")
+ shift 1
+ ;;
+ *)
+ break
+ ;;
+ esac
+ done
+
+ [ "$1" = "--" ] && shift 1
+ main "$@"
+ ;;
+*)
+ command="$1"
+ shift 1
+ [ "$1" = "--" ] && shift 1
+ if which update-"$command" >/dev/null 2>&1; then
+ update-"$command" "$@"
+ else
+ die "command \"update-$command\" is not executable, or does not exist"
+ fi
+ ;;
+esac
diff --git a/pkgs/sources/scripts/source/apps/git-edit-index.sh b/pkgs/sources/scripts/source/apps/git-edit-index.sh
new file mode 100755
index 00000000..e73dc53c
--- /dev/null
+++ b/pkgs/sources/scripts/source/apps/git-edit-index.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# needed for help() and version
+# shellcheck disable=2034
+AUTHORS="Soispha"
+# shellcheck disable=2034
+YEARS="2024"
+# shellcheck disable=2034
+VERSION="1.0.0"
+
+# NAME is from the wrapper
+# shellcheck disable=SC2269
+NAME="$NAME"
+
+help() {
+ cat <<EOF
+Edit a file from the index. This script does not touch the unstaged variant of the file.
+
+USAGE:
+ $NAME [OPTIONS] [--] FILES..
+
+OPTIONS:
+ --
+ Stop parsing options and interpret everything as an file.
+
+ --help | -h
+ Display this help and exit.
+
+ --version | -v
+ Display version and copyright information and exit.
+ARGUMENTS:
+ FILES := [[ git diff --name-only --cached --diff-filter=AM ]]
+ The files to edit.
+
+EOF
+}
+
+GIT_DIR="$(git rev-parse --show-toplevel)"
+materialize_file() {
+ git diff --cached "$1" >"$GIT_DIR/.git/EDIT_INDEX_PATCH"
+
+ git add "$1"
+ git restore --staged "$1"
+ cat "$1" >"$GIT_DIR/.git/EDIT_INDEX_FILE"
+ git restore "$1"
+
+ git apply "$GIT_DIR/.git/EDIT_INDEX_PATCH"
+ "$EDITOR" "$1"
+
+ git add "$1"
+ mv "$GIT_DIR/.git/EDIT_INDEX_FILE" "$1"
+}
+
+edit() {
+ files_to_add="$(mktmp)"
+ realpath --relative-to=. "$@" >"$files_to_add"
+
+ index_files="$(mktmp)"
+ git diff --name-only --cached --diff-filter=AM >"$index_files"
+
+ while read -r file; do
+ if grep -q "$file" "$files_to_add"; then
+ sed -i "s|$file||" "$files_to_add"
+ materialize_file "$file"
+ fi
+ done <"$index_files"
+
+ files_to_check="$(mktmp)"
+ clean "$files_to_add" >"$files_to_check"
+ if [ "$(wc -l <"$files_to_check")" -gt 0 ]; then
+ warn "Could not edit every file:"
+ cat "$files_to_add"
+ fi
+}
+
+for arg in "$@"; do
+ case "$arg" in
+ "--help" | "-h")
+ help
+ exit 0
+ ;;
+ "--version" | "-v")
+ version
+ exit 0
+ ;;
+ "--")
+ end_of_cli_options=true
+ ;;
+ esac
+ [ "$end_of_cli_options" = "true" ] && break
+done
+
+edit "$@"
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/small_functions/brightness.sh b/pkgs/sources/scripts/source/small_functions/brightness.sh
new file mode 100755
index 00000000..a7272279
--- /dev/null
+++ b/pkgs/sources/scripts/source/small_functions/brightness.sh
@@ -0,0 +1,80 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+help() {
+ cat <<EOF
+This is a system brightness manager
+
+USAGE:
+ $NAME up [VALUE] | down [VALUE]
+
+OPTIONS:
+ --help | -h
+ Output this help and exit.
+
+ --version | -v
+ Output the version and exit.
+
+COMMANDS:
+ up [VALUE]
+ Increase the brightness by VALUE or 5%.
+
+ down [VALUE]
+ Decrease the brightness by VALUE or 5%.
+
+ARGUMENTS:
+ VALUE := [[seq 0 100]]
+ The amount to increase/decrease the brightness. In percentage
+EOF
+}
+
+BACKLIGHT="/sys/class/%BACKLIGHT_NAME"
+
+brightness() {
+ offset="$1"
+
+ max="$(cat $BACKLIGHT/max_brightness)"
+ cur="$(cat $BACKLIGHT/brightness)"
+ percentage="$(echo | awk --assign=cur="$cur" --assign=max="$max" '{printf cur / max}')"
+
+ new="$(echo | awk --assign=per="$percentage" --assign=offset="$offset" '{printf per + (offset / 10)}')"
+
+ output="$(echo | awk --assign=new="$new" --assign=max="$max" '{printf max * new}')"
+
+ msg "echo \"$output\" > $BACKLIGHT/brightness"
+}
+
+for arg in "$@"; do
+ case "$arg" in
+ "--help" | "-h")
+ help
+ exit 0
+ ;;
+ "--version" | "-v")
+ version
+ exit 0
+ ;;
+ esac
+done
+
+case "$1" in
+"up")
+ shift 1
+ value="5"
+ [ -n "$1" ] && value="$1"
+ brightness "+$value"
+ ;;
+"down")
+ shift 1
+ value="-5"
+ [ -n "$1" ] && value="$1"
+ brightness "-$value"
+ ;;
+*)
+ die "The command '$1' does not exist! See '--help' for a list"
+ ;;
+esac
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/small_functions/nato.py b/pkgs/sources/scripts/source/small_functions/nato.py
new file mode 100755
index 00000000..e9d15f56
--- /dev/null
+++ b/pkgs/sources/scripts/source/small_functions/nato.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+# originally from here: https://cgit.pacien.net/desktop-utilities/
+
+import sys
+
+alphabet = {
+ "nato": {
+ "A": "Alfa", # No idea why this is not just 'Alpha' ..
+ "B": "Bravo",
+ "C": "Charlie",
+ "D": "Delta",
+ "E": "Echo",
+ "F": "Foxtrot",
+ "G": "Golf",
+ "H": "Hotel",
+ "I": "India",
+ "J": "Juliett",
+ "K": "Kilo",
+ "L": "Lima",
+ "M": "Mike",
+ "N": "November",
+ "O": "Oscar",
+ "P": "Papa",
+ "Q": "Quebec",
+ "R": "Romeo",
+ "S": "Sierra",
+ "T": "Tango",
+ "U": "Uniform",
+ "V": "Victor",
+ "W": "Whiskey",
+ "X": "X-ray",
+ "Y": "Yankee",
+ "Z": "Zulu",
+ "0": "Nadazero",
+ "1": "Unaone",
+ "2": "Bissotwo",
+ "3": "Terrathree",
+ "4": "Kartefour",
+ "5": "Pantafive",
+ "6": "Soxisix",
+ "7": "Setteseven",
+ "8": "Oktoeight",
+ "9": "Novenine",
+ ",": "Comma",
+ "/": "Forward slash",
+ ".": "Stop/Decimal",
+ },
+ "german": {
+ "A": "Aachen",
+ "Ä": "Umlaut Aachen",
+ "B": "Berlin",
+ "C": "Chemnitz",
+ "D": "Düsseldorf",
+ "E": "Essen",
+ "F": "Frankfurt",
+ "G": "Goslar",
+ "H": "Hamburg",
+ "I": "Ingelheim",
+ "J": "Jena",
+ "K": "Köln",
+ "L": "Leipzig",
+ "M": "München",
+ "N": "Nürnberg",
+ "O": "Offenbach",
+ "Ö": "Umlaut Offenbach",
+ "P": "Potsdam",
+ "Q": "Quickborn",
+ "R": "Rostock",
+ "S": "Salzwedel",
+ "ẞ": "Eszett",
+ "T": "Tübingen",
+ "U": "Unna",
+ "Ü": "Umlaut Unna",
+ "V": "Völklingen",
+ "W": "Wuppertal",
+ "X": "Xanten",
+ "Y": "Ypsilon",
+ "Z": "Zwickau",
+ },
+}
+
+
+def str_to_telephony(phrase, language):
+ language_alphabet = alphabet[language]
+
+ return [
+ language_alphabet[c] if c in language_alphabet else c for c in phrase.upper()
+ ]
+
+
+language = sys.argv[1]
+if language not in ["nato", "german"]:
+ print(
+ f"Langugae '{language}' is not a valid language, only 'nato' and 'german' are!",
+ file=sys.stderr,
+ )
+ exit(1)
+
+print(
+ "\n".join(
+ str_to_telephony(
+ " ".join(sys.argv[2:]),
+ language,
+ )
+ )
+)
diff --git a/pkgs/sources/scripts/source/small_functions/screenshot_persistent.sh b/pkgs/sources/scripts/source/small_functions/screenshot_persistent.sh
new file mode 100755
index 00000000..4308b8d2
--- /dev/null
+++ b/pkgs/sources/scripts/source/small_functions/screenshot_persistent.sh
@@ -0,0 +1,22 @@
+#! /usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# only generate a path (this could lead to a time-of-check/time-of-use bug)
+tmp="$(mktmp --dry-run)"
+
+if grim -g "$(slurp)" "$tmp"; then
+ name="$(rofi -dmenu -p "Name of screenshot: " -l 0)"
+ screen_shot_path="$HOME/media/pictures/screenshots/$name.png"
+ while [ -f "$screen_shot_path" ]; do
+ notify-send "Warning" 'Screenshot name already in use!'
+ name="$(rofi -dmenu -p "New name of screenshot: " -l 0)"
+ screen_shot_path="$HOME/media/pictures/screenshots/$name.png"
+ done
+
+ mv "$tmp" "$screen_shot_path"
+ alacritty -e lf -command ":{{ set sortby atime; set reverse!; }}"
+fi
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/small_functions/screenshot_temporary.sh b/pkgs/sources/scripts/source/small_functions/screenshot_temporary.sh
new file mode 100755
index 00000000..8968ca79
--- /dev/null
+++ b/pkgs/sources/scripts/source/small_functions/screenshot_temporary.sh
@@ -0,0 +1,8 @@
+#! /usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+grim -g "$(slurp)" | wl-copy
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/small_functions/update-sys.sh b/pkgs/sources/scripts/source/small_functions/update-sys.sh
new file mode 100755
index 00000000..d28247f6
--- /dev/null
+++ b/pkgs/sources/scripts/source/small_functions/update-sys.sh
@@ -0,0 +1,85 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+help() {
+ cat <<EOF
+This is a NixOS System flake update manager.
+
+USAGE:
+ $NAME [--branch <branchname>] [--help]
+
+OPTIONS:
+ --branch | -b BRANCHNAME
+ select a branch to update from.
+
+ --mode | -m MODE
+ select a mode to update with
+
+ --help | -h
+ output this help.
+ARGUMENTS:
+ BRANCHNAME := [[ git branch --list --format '%(refname:short)' ]]
+ The name of the branch to deploy the config from
+
+ MODE := switch|boot|test|build|dry-build|dry-activate|edit|repl|build-vm|build-vm-with-bootloader
+ See the 'nixos-rebuild' manpage for more information about these modes.
+EOF
+ exit "$1"
+}
+default_branch=$(mktmp)
+BRANCH=""
+
+while [ "$#" -gt 0 ]; do
+ case "$1" in
+ "--help" | "-h")
+ help 0
+ ;;
+ "--branch" | "-b")
+ if [ -n "$2" ]; then
+ BRANCH="$2"
+ else
+ error "$1 requires an argument"
+ help 1
+ fi
+ shift 2
+ ;;
+ "--mode" | "-m")
+ if [ -n "$2" ]; then
+ MODE="$2"
+ else
+ error "$1 requires an argument"
+ help 1
+ fi
+ shift 2
+ ;;
+ *)
+ error "the option $1 does not exist!"
+ help 1
+ ;;
+ esac
+done
+
+cd /etc/nixos || die "No /etc/nixos"
+msg "Starting system update..."
+git remote update origin --prune >/dev/null 2>&1
+if ! [ "$BRANCH" = "" ]; then
+ git switch "$BRANCH" >/dev/null 2>&1 && msg2 "Switched to branch '$BRANCH'"
+fi
+msg2 "Updating git repository..."
+git pull --rebase
+
+git remote show origin | grep 'HEAD' | cut -d':' -f2 | sed -e 's/^ *//g' -e 's/ *$//g' >"$default_branch" &
+
+msg2 "Updating system..."
+if [ -n "$MODE" ]; then
+ nixos-rebuild "$MODE"
+else
+ nixos-rebuild switch
+fi
+
+git switch "$(cat "$default_branch")" >/dev/null 2>&1 && msg2 "Switched to branch '$(cat "$default_branch")'"
+msg "Finished Update!"
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/specific/neorg/neorg_id_function.sh b/pkgs/sources/scripts/source/specific/neorg/neorg_id_function.sh
new file mode 100755
index 00000000..865ecacf
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/neorg_id_function.sh
@@ -0,0 +1,16 @@
+#! /usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+context="$(task _get rc.context)"
+if [ "$context" ]; then
+ filter="project:$context"
+else
+ filter="0-10000"
+fi
+tasks="$(task "$filter" _ids)"
+
+if [ "$tasks" ]; then
+ echo "$tasks" | xargs task _zshids | awk -F: -v q="'" '{gsub(/'\''/, q "\\" q q ); print $1 ":" q $2 q}'
+fi
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/add.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/add.sh
new file mode 100755
index 00000000..5a830a10
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/add.sh
@@ -0,0 +1,23 @@
+#!/usr/bin/env dash
+
+add0open_taskwarrior_project_file() {
+ task_project_file="%TASK_PROJECT_FILE"
+
+ cd "$(dirname $task_project_file)" || die "BUG: task_project_file ('$task_project_file') can't be accessed"
+
+ git_dir="$(search_flake_base_dir)"
+ [ "$git_dir" ] || die "(BUG): No git directory?"
+ cd "$git_dir" || die "Unreachable, this MUST exists"
+
+ nvim "$task_project_file"
+ git add "$task_project_file"
+
+ base_task_project_file_path="$(awk "{ gsub(\"$git_dir/\", \"\", \$0); print }" "$(ptmp "$task_project_file")")"
+ git add $task_project_file
+
+ # Check that only the project file has been added (and that our file is actually
+ # modified)
+ if git status --porcelain=v2 | awk -v path="$base_task_project_file_path" 'BEGIN { hit = 0 } { if ($2 ~ /A./ || $2 ~ /M./) { if ($NF ~ path) { hit = 1 } else { hit = 0; exit 1 } } } END { if (hit == 1) { exit 0 } else { exit 1 } }'; then
+ git commit --verbose --message="chore($(dirname "$base_task_project_file_path")): Update"
+ fi
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/context.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/context.sh
new file mode 100755
index 00000000..7095847d
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/context.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env dash
+
+context0open_current_task_context() {
+ current_context="$(utils0get_current_context)"
+
+ if [ "$current_context" ]; then
+ context_path="$(utils0get_current_context_path "$current_context")"
+
+ extended_neorg_project_dir="$(utils0get_neorg_project_dir)"
+ cd "$extended_neorg_project_dir" || die "(BUG?): Can not access the project dir: $extended_neorg_project_dir"
+
+ nvim "$extended_neorg_project_dir/$context_path"
+
+ git add .
+ git commit --message="chore($(dirname "$context_path")): Update" --no-gpg-sign
+ else
+ warn "No context active"
+ fi
+}
+
+context0open_current_task_context_at_task_id() {
+ task_id="$1"
+ current_context="$(utils0get_current_context)"
+
+ if [ "$current_context" ]; then
+ context_path="$(utils0get_current_context_path "$current_context")"
+ extended_neorg_project_dir="$(utils0get_neorg_project_dir)"
+ task_uuid="$(task "$task_id" uuids)"
+
+ cd "$extended_neorg_project_dir" || die "(BUG?): Can not access the project dir: $extended_neorg_project_dir"
+
+ if ! grep -q "% $task_uuid" "$extended_neorg_project_dir/$context_path"; then
+ echo "* TITLE (% $task_uuid)" >>"$extended_neorg_project_dir/$context_path"
+ fi
+
+ nvim "$extended_neorg_project_dir/$context_path" -c "/% $task_uuid"
+
+ git add .
+ git commit --message="chore($(dirname "$context_path")): Update" --no-gpg-sign
+ else
+ warn "No context active"
+ fi
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/dmenu.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/dmenu.sh
new file mode 100755
index 00000000..5a138982
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/dmenu.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env dash
+
+dmenu0open_context_in_browser() {
+ project="$(echo "%ALL_PROJECTS_PIPE" | rofi -sep "|" -dmenu)"
+
+ if [ "$project" ]; then
+ [ -d "%NEORG_REVIEW_PATH" ] || mkdir --parents "%NEORG_REVIEW_PATH"
+ [ -f "%NEORG_REVIEW_PATH/$project.lock" ] || touch "%NEORG_REVIEW_PATH/$project.lock"
+ project0open_project_in_browser "$project"
+ else
+ notify-send "(neorg/dmenu) No project selected"
+ exit 1
+ fi
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/f_start.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/f_start.sh
new file mode 100755
index 00000000..2423dd44
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/f_start.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env dash
+
+fstart0start_new_task() {
+ task_id="$1"
+ fstop0stop_current_task
+ task start "$task_id"
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/f_stop.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/f_stop.sh
new file mode 100755
index 00000000..e4ff0b94
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/f_stop.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env dash
+
+fstop0stop_current_task() {
+ # we ensured that only one task may be active
+ active="$(task +ACTIVE _ids)"
+ [ "$active" ] && task stop "$active"
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/list.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/list.sh
new file mode 100755
index 00000000..10659457
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/list.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env dash
+
+list0list_all_contexts_newline() {
+ print "%ALL_PROJECTS_NEWLINE"
+}
+list0list_all_contexts_comma() {
+ print "%ALL_PROJECTS_COMMA"
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/project.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/project.sh
new file mode 100755
index 00000000..64591850
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/project.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env dash
+
+project0open_current_context_in_browser() {
+ current_context="$(utils0get_current_context)"
+ [ "$current_context" ] || die "No current context to use"
+ project0open_context_in_browser "$(utils0context2project "$current_context")"
+}
+
+project0open_project_in_browser() {
+ project="$1"
+ [ "$project" ] || die "BUG: No context supplied to project0open_context_in_browser"
+
+ old_context="$(utils0get_current_context)"
+ # We have ensured that only one task may be active
+ old_started_task="$(task +ACTIVE _ids)"
+
+ tracking="$(mktmp)"
+ task "project:$project" _ids | xargs --no-run-if-empty task _zshids >"$tracking"
+ task context "$(utils0project2context "$project")"
+
+ while read -r description; do
+ desc="$(echo "$description" | awk -F: '{print $2}')"
+ if [ "$desc" = "tracking" ]; then
+ task_id="$(echo "$description" | awk -F: '{print $1}')"
+ notify-send "(Neorg)" "Starting task $project -> $desc"
+ task start "$task_id"
+ break
+ fi
+ done <"$tracking"
+
+ firefox -P "$project"
+
+ task stop "$task_id"
+ [ "$old_started_task" ] && task start "$old_started_task"
+
+ if [ "$old_context" ]; then
+ task context "$old_context"
+ else
+ task context none
+ fi
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/review.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/review.sh
new file mode 100755
index 00000000..a0a9ab8d
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/review.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env dash
+
+review0start() {
+ for project in $(list0list_all_contexts_newline); do
+ if [ -f "%NEORG_REVIEW_PATH/$project.lock" ]; then
+ msg "Reviewing '$project'"
+ notify-send "Neorg" "Reviewing '$project'"
+ firefox -P "$project"
+ rm "%NEORG_REVIEW_PATH/$project.lock"
+ fi
+ done
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/utils.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/utils.sh
new file mode 100755
index 00000000..c3843e8e
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/utils.sh
@@ -0,0 +1,40 @@
+#!/usr/bin/env dash
+
+# Runs it's first argument and then the second, regardless if the first failed or
+# succeeded
+utils0chain() {
+ eval "$1"
+ eval "$2"
+}
+
+utils0get_current_context() {
+ current_context="$(task _get rc.context)"
+ printf "%s\n" "$current_context"
+}
+
+utils0get_current_context_path() {
+ current_context="$1"
+ context_path="$(task _get rc.context."$current_context".rc.neorg_path 2>/dev/null)"
+ if ! [ "$context_path" ]; then
+ context_path="$(grep "context.$current_context.rc.neorg_path" "%HOME_TASKRC" | awk 'BEGIN {FS="="} {print $2}')"
+ [ "$context_path" ] || die "All contexts should have a 'neorg_path' set!"
+ fi
+ printf "%s\n" "$context_path"
+}
+
+utils0get_neorg_project_dir() {
+ # Perform shell expansion of Tilde
+ neorg_project_dir="$(sed "s|^~|$HOME|" "$(ptmp "%DEFAULT_NEORG_PROJECT_DIR")")"
+ printf "%s\n" "$neorg_project_dir"
+}
+
+utils0project2context() {
+ project="$1"
+ context="$(sed 's|\.|_|g' "$(ptmp "$project")")"
+ printf "%s\n" "$context"
+}
+utils0context2project() {
+ context="$1"
+ project="$(sed 's|_|\.|g' "$(ptmp "$context")")"
+ printf "%s\n" "$project"
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/functions/workspace.sh b/pkgs/sources/scripts/source/specific/neorg/sh/functions/workspace.sh
new file mode 100755
index 00000000..d5eb2fca
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/functions/workspace.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env dash
+
+workspace0open_neorg_workspace() {
+ workspace="$1"
+ nvim -c "NeorgStart" -s "$(ptmp ":Neorg workspace $workspace\n")"
+}
+workspace0open_neorg_workspace_prompt() {
+ nvim -c "NeorgStart" -s "$(ptmp ":Neorg workspace ")"
+}
diff --git a/pkgs/sources/scripts/source/specific/neorg/sh/main.sh b/pkgs/sources/scripts/source/specific/neorg/sh/main.sh
new file mode 100755
index 00000000..559351b9
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/neorg/sh/main.sh
@@ -0,0 +1,164 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# load dependencies
+. ./functions/add.sh
+. ./functions/context.sh
+. ./functions/dmenu.sh
+. ./functions/f_start.sh
+. ./functions/f_stop.sh
+. ./functions/list.sh
+. ./functions/project.sh
+. ./functions/utils.sh
+. ./functions/workspace.sh
+. ./functions/review.sh
+
+# these are used in version()
+# shellcheck disable=2034
+AUTHORS="Soispha"
+# shellcheck disable=2034
+YEARS="2023"
+
+NAME="neorg"
+
+help() {
+ cat <<EOF
+This is the core interface to the system-integrated task management
+
+USAGE:
+ $NAME [OPTIONS] [COMMAND]
+
+OPTIONS:
+ --help | -h
+ Display this help and exit.
+
+ --version | -v
+ Display version and copyright information and exit.
+COMMANDS:
+ task [ID]
+ Open the neorg context associated with the current context and
+ the uuid of the task with id ID. Without ID, it'll open the
+ current context's norg file.
+ If no context is set, drops you to the selection prompt
+
+ dmenu
+ Select a project in dmenu mode. This will give you all projects
+ and exectute the selected one as in 'neorg projects <selected>'
+
+ workspace [WS]
+ The neorg workspace (WS) to open at startup, an empty value drops
+ you at a prompt to enter the workspace yourself.
+
+ project [P]
+ Opens the webbrowser with either the context (P) or
+ the current active context as argument if no context is supplied
+
+ list
+ Lists all available contexts
+
+ add
+ Allows you to quickly add projects
+
+ fstart ID
+ Starts the task (ID) but only after it stooped
+ the previous active task, if it existed.
+
+ fstop
+ Stops the current active task
+
+ review
+ Review all firefox tabs
+ARGUMENTS:
+ ID | *([0-9]) := [[%ID_GENERATION_FUNCTION]]
+ The function displays all possible IDs of the eligable tasks.
+
+ WS := %ALL_WORKSPACES
+ All possible workspaces
+
+ P := %ALL_PROJECTS_PIPE
+ The possible project
+
+EOF
+}
+
+for arg in "$@"; do
+ case "$arg" in
+ "--help" | "-h")
+ help
+ exit 0
+ ;;
+ "--version" | "-v")
+ version
+ exit 0
+ ;;
+ esac
+done
+
+while [ "$#" -ne 0 ]; do
+ case "$1" in
+ "t"*) # task
+ shift 1
+ task_id="$1"
+ [ "$task_id" ] || utils0chain context0open_current_task_context "exit 0"
+ context0open_current_task_context_at_task_id "$task_id"
+ exit 0
+ ;;
+ "w"*) # workspace
+ shift 1
+ workspace_to_open="$1"
+ # TODO: Exit with 1 on error, instead of the 0 <2023-10-20>
+ [ "$workspace_to_open" ] || utils0chain workspace0open_neorg_workspace_prompt "exit 0"
+ workspace0open_neorg_workspace "$workspace_to_open"
+ exit 0
+ ;;
+ "p"*) # project
+ shift 1
+ project_to_open="$1"
+ # TODO: Exit with 1 on error, instead of the 0 <2023-10-20>
+ [ "$project_to_open" ] || utils0chain project0open_current_context_in_browser "exit 0"
+ if ! grep -q "$project_to_open" "$(ptmp "%ALL_PROJECTS_NEWLINE")"; then
+ die "Your project ('$project_to_open') is not in the list of available projects:
+%ALL_PROJECTS_COMMA"
+ fi
+ project0open_project_in_browser "$project_to_open"
+ exit 0
+ ;;
+ "l"*) # list
+ list0list_all_contexts_newline
+ exit 0
+ ;;
+ "a"*) # add-project
+ add0open_taskwarrior_project_file
+ exit 0
+ ;;
+ "d"*) # dmenu
+ dmenu0open_context_in_browser
+ exit 0
+ ;;
+ "fsta"*) # fstart
+ shift 1
+ task_id="$1"
+ [ "$task_id" ] || die "No task id provided to fstart"
+ fstart0start_new_task "$task_id"
+ exit 0
+ ;;
+ "fsto"*) # fstop
+ fstop0stop_current_task
+ exit 0
+ ;;
+ "r"*) # review
+ shift 1
+ review0start
+ exit 0
+ ;;
+ *)
+ die "Command '$1' does not exist! Please look at:\n $NAME --help"
+ exit 0
+ ;;
+ esac
+done
+
+context0open_current_task_context
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/specific/spodi/sh/download.sh b/pkgs/sources/scripts/source/specific/spodi/sh/download.sh
new file mode 100755
index 00000000..fe9746c8
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/spodi/sh/download.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env dash
+
+download_to_down() {
+ DOWNLOAD_DIRECTORY="%XDG_MUSIC_DIR/down/spotify"
+
+ already_downloaded_files="$(mktmp)"
+ fd . "$DOWNLOAD_DIRECTORY" --exclude spotdl.log --exclude spotdl-errors.log >"$already_downloaded_files"
+ if [ -z "$NO_CHECK" ] && [ "$(wc -l <"$already_downloaded_files")" -ne 0 ]; then
+ die "something is already downloaded"
+ fi
+ # [ -e "$DOWNLOAD_DIRECTORY/spotdl.log" ] && rm "$DOWNLOAD_DIRECTORY/spotdl.log"
+
+ download "$1" "$DOWNLOAD_DIRECTORY"
+}
+
+download() {
+ download_url="$1"
+ output_path="$2"
+
+ config="$(mktmp)"
+ cat <<EOF | clean >"$config"
+# Main options
+--audio slider-kz bandcamp youtube-music piped youtube soundcloud
+--lyrics genius musixmatch azlyrics synced
+
+# FFmpeg options
+--ffmpeg ffmpeg
+--threads 16
+--bitrate 256k
+
+# Spotify options
+--cache-path %XDG_CACHE_HOME/spotdl/.spotipy
+
+# Output options
+--preload
+--format opus
+--output {artists}_-_{title}
+--print-errors
+--save-errors $output_path/spotdl-errors.log
+# TODO: Reactive whence spotdl support for these has improved <2023-12-19>
+# --generate-lrc
+--overwrite skip
+
+# Misc options
+--log-level INFO
+EOF
+
+ cd "$output_path" || die "BUG: no $output_path"
+ touch "$output_path/spotdl-errors.log"
+
+ # The sub shell needs to be unquoted, as the arguments may not be treated as one.
+ # shellcheck disable=2046
+ unbuffer spotdl $(cat "$config") download "$download_url" | tee "$output_path/spotdl.log"
+
+ [ -d ~/.spotdl ] && rm -r ~/.spotdl
+}
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/specific/spodi/sh/update.sh b/pkgs/sources/scripts/source/specific/spodi/sh/update.sh
new file mode 100755
index 00000000..a289cf58
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/spodi/sh/update.sh
@@ -0,0 +1,52 @@
+#!/usr/bin/env dash
+
+update() {
+ UPDATE_DIRECTORY="%XDG_MUSIC_DIR/artists"
+ UPDATE_CONFIG_FILE="%XDG_MUSIC_DIR/artists/update.conf"
+
+ if ! [ -f "$UPDATE_CONFIG_FILE" ]; then
+ error="$(
+ cat <<EOF
+Please provide an update config file at: '$UPDATE_CONFIG_FILE'.
+
+The 'update.conf' file should follow this pattern:
+<path_to_artist>/<artist_name>|<spotify_url>
+
+All comments and empty lines are ignored
+EOF
+ )"
+ die "$error"
+ fi
+
+ config_file="$(mktmp)"
+ clean "$UPDATE_CONFIG_FILE" >"$config_file"
+
+ while IFS="|" read -r artist url; do
+ full_artist="$UPDATE_DIRECTORY/$artist"
+ [ -d "$full_artist" ] || mkdir --parents "$full_artist"
+ [ -d "$full_artist/update" ] || mkdir --parents "$full_artist/update"
+ [ -d "$full_artist/all" ] || mkdir --parents "$full_artist/all"
+ [ -d "$full_artist/filtered" ] || mkdir --parents "$full_artist/filtered"
+
+ while read -r file; do
+ ln --symbolic --relative "$file" "$full_artist/update/$(basename "$file")"
+ done <"$(tmp fd --type file --extension opus . "$full_artist/all")"
+
+ msg2 "Updating $artist with url: '$url'"
+ download "$url" "$full_artist/update"
+
+ while read -r file; do
+ mv "$file" "$full_artist/all"
+ ln --symbolic --relative "$full_artist/all/$(basename "$file")" "$full_artist/filtered/$(basename "$file")"
+ done <"$(tmp fd --type file --extension opus . "$full_artist/update")"
+
+ while read -r file; do
+ rm "$file"
+ done <"$(tmp fd --type symlink --extension opus . "$full_artist/update")"
+
+ cp "$full_artist/update/spotdl.log" "$full_artist/all/spotdl.$(date +%Y_%m_%d).log"
+ cp "$full_artist/update/spotdl-errors.log" "$full_artist/all/spotdl-errors.$(date +%Y_%m_%d).log"
+ done <"$config_file"
+}
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/specific/spodi/spodi.sh b/pkgs/sources/scripts/source/specific/spodi/spodi.sh
new file mode 100755
index 00000000..475fd48a
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/spodi/spodi.sh
@@ -0,0 +1,71 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# these are used in version()
+# shellcheck disable=2034
+AUTHORS="Soispha"
+# shellcheck disable=2034
+YEARS="2023"
+
+# load dependencies
+. ./sh/update.sh
+. ./sh/download.sh
+
+help() {
+ cat <<EOF
+This is a small wrapper around downloading things from spotify
+
+USAGE:
+ $NAME [OPTIONS] COMMAND
+
+OPTIONS:
+ --help | -h
+ Display this help and exit.
+
+ --version | -v
+ Display version and copyright information and exit.
+COMMANDS:
+ update
+ Read the artist.conf file and download all newly released things
+
+ download URL
+ Download a specific url to the DOWNLOAD_DIRECTORY
+EOF
+}
+
+for arg in "$@"; do
+ case "$arg" in
+ "--help" | "-h")
+ help
+ exit 0
+ ;;
+ "--version" | "-v")
+ version
+ exit 0
+ ;;
+ esac
+done
+
+case "$1" in
+"update")
+ shift 1
+ update
+ exit 0
+ ;;
+"download")
+ shift 1
+ download_url="$1"
+ [ -z "$download_url" ] && die "You need to provide a download url"
+ download_to_down "$download_url"
+ exit 0
+ ;;
+*)
+ die "Command '$1' is not know"
+ help
+ exit 1
+ ;;
+esac
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/specific/ytcc/description.sh b/pkgs/sources/scripts/source/specific/ytcc/description.sh
new file mode 100755
index 00000000..ae9107b9
--- /dev/null
+++ b/pkgs/sources/scripts/source/specific/ytcc/description.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+jq --raw-output '.description' "$XDG_RUNTIME_DIR/ytcc/running" | fmt -u -s | less
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/battery.sh b/pkgs/sources/scripts/source/wrappers/battery.sh
new file mode 100755
index 00000000..e650ba5d
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/battery.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+capacity="$(cat /sys/class/power_supply/BAT0/capacity)"
+status="$(cat /sys/class/power_supply/BAT0/status)"
+
+printf "%s%% (%s)\n" "$capacity" "$status"
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/hibernate.sh b/pkgs/sources/scripts/source/wrappers/hibernate.sh
new file mode 100755
index 00000000..30868fd1
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/hibernate.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+context="$(task _get rc.context)"
+[ "$context" ] && task context none
+
+# We have ensured that only one task is active
+active="$(task +ACTIVE _ids)"
+[ "$active" ] && task stop "$active"
+
+systemctl hibernate "$@"
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/ll.sh b/pkgs/sources/scripts/source/wrappers/ll.sh
new file mode 100755
index 00000000..f689ba44
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/ll.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+last_directory="$(mktemp)"
+
+command lf -last-dir-path="$last_directory" "$@"
+
+dir="$(cat "$last_directory")"
+cd "$dir" || die "$dir does not exist!"
+rm "$last_directory"
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/lock.sh b/pkgs/sources/scripts/source/wrappers/lock.sh
new file mode 100755
index 00000000..3101ef9a
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/lock.sh
@@ -0,0 +1,18 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+context="$(task _get rc.context)"
+[ "$context" ] && task context none
+
+# We have ensured that only one task is active
+active="$(task +ACTIVE _ids)"
+[ "$active" ] && task stop "$active"
+
+swaylock
+
+[ "$active" ] && task start "$active"
+
+[ "$context" ] && task context "$context"
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/lyrics.sh b/pkgs/sources/scripts/source/wrappers/lyrics.sh
new file mode 100755
index 00000000..02a147c8
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/lyrics.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+(
+ cd "$XDG_MUSIC_DIR" || die "No music dir!"
+ exiftool "$(mpc --format '%file%' current)" -json | jq '.[0].Lyrics' -r | less
+)
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/mpc-fav.sh b/pkgs/sources/scripts/source/wrappers/mpc-fav.sh
new file mode 100755
index 00000000..795a4875
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/mpc-fav.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+FAV_DIR="$XDG_MUSIC_DIR/playlists/favourites"
+
+cd "$XDG_MUSIC_DIR" || die "No music dir!"
+
+[ -d "$FAV_DIR" ] || mkdir --parents "$FAV_DIR"
+
+ln -sr "$(mpc --format '%file%' current)" "$FAV_DIR/" || die "Link failed!"
+
+mpc update
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/mpc-rm.sh b/pkgs/sources/scripts/source/wrappers/mpc-rm.sh
new file mode 100755
index 00000000..94e0634b
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/mpc-rm.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+cd "$XDG_MUSIC_DIR" || die "No music dir!"
+trash-put "$(mpc --format '%file%' current)"
+mpc del 0
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/mpc.sh b/pkgs/sources/scripts/source/wrappers/mpc.sh
new file mode 100755
index 00000000..5aae5cdb
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/mpc.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+case "$1" in
+"rm")
+ shift 1
+ mpc-rm "$@"
+ ;;
+"fav")
+ shift 1
+ mpc-fav "$@"
+ ;;
+*)
+ mpc "$@"
+ ;;
+esac
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/show.sh b/pkgs/sources/scripts/source/wrappers/show.sh
new file mode 100755
index 00000000..ae2bdb13
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/show.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+# Maybe add `--quit-if-one-screen`
+less --redraw-on-quit "$@"
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/sort_song.sh b/pkgs/sources/scripts/source/wrappers/sort_song.sh
new file mode 100755
index 00000000..e2978507
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/sort_song.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+case "$("$1" | tr '[:upper:]' '[:lower:]')" in
+"lyrics")
+ filter="LYRICS"
+ directory="lyrics"
+ ;;
+"instrumental")
+ filter="INSTRUMENTAL"
+ directory="instrumental"
+ ;;
+*)
+ die "Expected 'instrumental|lyrics' but got '$1'"
+ ;;
+esac
+
+process() {
+ mediainfo --Output=JSON "$1" | jq '.media.track | map(.Lyrics) | join("")'
+}
+
+mkdir "../$directory"
+
+fd . --extension=opus | while read -r file; do
+ if [ "$(process "$file")" = '""' ] || [ "$(process "$file")" = '"Instrumental"' ] || [ "$(process "$file")" = '"instrumental"' ]; then
+ echo "INSTRUMENTAL::$file"
+ else
+ echo "LYRICS::$file"
+ fi
+done | grep "$filter" | awk 'BEGIN {FS="::"}{print $2}' | while read -r file; do ln -s "../all/$file" "../$directory/$file"; done
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/virsh-del.sh b/pkgs/sources/scripts/source/wrappers/virsh-del.sh
new file mode 100755
index 00000000..c3de5484
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/virsh-del.sh
@@ -0,0 +1,10 @@
+#! /usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+virsh destroy "$1"
+virsh undefine "$1" --nvram
+virsh vol-delete --pool default "$1".qcow2
+
+# vim: ft=sh
diff --git a/pkgs/sources/scripts/source/wrappers/yti.sh b/pkgs/sources/scripts/source/wrappers/yti.sh
new file mode 100755
index 00000000..a69ffa74
--- /dev/null
+++ b/pkgs/sources/scripts/source/wrappers/yti.sh
@@ -0,0 +1,33 @@
+#! /usr/bin/env dash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+DOWN_DIR=/home/soispha/media/music/down/youtube
+
+tmp=$(mktmp)
+config=$(mktmp)
+
+for e in "$DOWN_DIR"/*.opus; do echo "$e" >>"$tmp"; done
+[ "$(wc -l "$tmp" | awk '{print $1}')" -gt 2 ] && die "something is already downloaded"
+
+cat <<EO >"$config"
+--paths home:"$DOWN_DIR"
+#--output %(fulltitle)
+--restrict-filenames
+--no-overwrites
+--no-write-info-json
+--clean-info-json
+--prefer-free-formats
+#--format mp3
+--extract-audio
+--audio-quality 0
+--audio-format best
+EO
+
+rm "$DOWN_DIR/yt-dlp.log"
+cd "$DOWN_DIR" || die "BUG: no $DOWN_DIR"
+
+unbuffer yt-dlp --config-location "$config" "$1" | tee "$DOWN_DIR/yt-dlp.log"
+
+# vim: ft=sh
diff --git a/pkgs/sources/snap-sync-forked/default.nix b/pkgs/sources/snap-sync-forked/default.nix
new file mode 100644
index 00000000..5b086a5a
--- /dev/null
+++ b/pkgs/sources/snap-sync-forked/default.nix
@@ -0,0 +1,24 @@
+{sysLib}: [
+ (final: prev: {
+ snap-sync-forked = sysLib.writeShellScript {
+ name = "snap-sync-forked";
+ src = ./snap-sync-forked.sh;
+ dependencies = with prev; [
+ bash
+ btrfs-progs
+ coreutils
+ gawk
+ gnugrep
+ snapper
+ util-linux
+
+ # optional:
+ libnotify
+ openssh
+ pv
+ rsync
+ sudo
+ ];
+ };
+ })
+]
diff --git a/pkgs/sources/snap-sync-forked/snap-sync-forked.sh b/pkgs/sources/snap-sync-forked/snap-sync-forked.sh
new file mode 100755
index 00000000..3d9c1ac9
--- /dev/null
+++ b/pkgs/sources/snap-sync-forked/snap-sync-forked.sh
@@ -0,0 +1,534 @@
+#!/usr/bin/env bash
+
+# shellcheck source=/dev/null
+SHELL_LIBRARY_VERSION="2.1.2" . %SHELL_LIBRARY_PATH
+
+#
+# snap-sync
+# https://github.com/wesbarnett/snap-sync
+# Copyright (C) 2016-2021 Wes Barnett
+
+# This program 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 2 of the License, or
+# (at your option) any later version.
+
+# This program 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 this program; if not, write to the Free Software Foundation, Inc.,
+
+# -------------------------------------------------------------------------
+
+# Takes snapshots of each snapper configuration. It then sends the snapshot to
+# a location on an external drive. After the initial transfer, it does
+# incremental snapshots on later calls. It's important not to delete the
+# snapshot created on your system since that will be used to determine the
+# difference for the next incremental snapshot.
+
+set -o errtrace
+
+version="0.7"
+name="snap-sync"
+
+printf "\nsnap-sync version %s, Copyright (C) 2016-2021 Wes Barnett\n" "$version"
+printf "snap-sync comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See the license for more information. \n\n"
+
+# The following line is modified by the Makefile or
+# find_snapper_config script
+SNAPPER_CONFIG=/etc/sysconfig/snapper
+
+donotify=0
+if ! command -v notify-send &>/dev/null; then
+ donotify=1
+fi
+
+doprogress=0
+if ! command -v pv &>/dev/null; then
+ doprogress=1
+fi
+
+error() {
+ printf "==> ERROR: %s\n" "$@"
+ notify_error 'Error' 'Check journal for more information.'
+} >&2
+
+die() {
+ error "$@"
+ exit 1
+}
+
+traperror() {
+ printf "Exited due to error on line %s.\n" "$1"
+ printf "exit status: %s\n" "$2"
+ printf "command: %s\n" "$3"
+ printf "bash line: %s\n" "$4"
+ printf "function name: %s\n" "$5"
+ exit 1
+}
+
+trapkill() {
+ die "Exited due to user intervention."
+}
+
+trap 'traperror ${LINENO} $? "$BASH_COMMAND" $BASH_LINENO "${FUNCNAME[@]}"' ERR
+trap trapkill SIGTERM SIGINT
+
+usage() {
+ cat <<EOF
+$name $version
+Usage: $name [options]
+
+Options:
+ -c, --config <config> snapper configuration to backup
+ -d, --description <desc> snapper description
+ -h, --help print this message
+ -n, --noconfirm do not ask for confirmation
+ -k, --keepold keep old incremental snapshots instead of deleting them
+ after backup is performed
+ -p, --port <port> remote port; used with '--remote'.
+ -q, --quiet do not send notifications; instead print them.
+ -r, --remote <address> ip address of a remote machine to backup to
+ --sudo use sudo on the remote machine
+ -s, --subvolid <subvlid> subvolume id of the mounted BTRFS subvolume to back up to
+ -u, --UUID <UUID> UUID of the mounted BTRFS subvolume to back up to
+
+See 'man snap-sync' for more details.
+EOF
+}
+
+ssh=""
+sudo=0
+while [[ $# -gt 0 ]]; do
+ key="$1"
+ case $key in
+ -d | --description)
+ description="$2"
+ shift 2
+ ;;
+ -c | --config)
+ selected_configs="$2"
+ shift 2
+ ;;
+ -u | --UUID)
+ uuid_cmdline="$2"
+ shift 2
+ ;;
+ -s | --subvolid)
+ subvolid_cmdline="$2"
+ shift 2
+ ;;
+ -k | --keepold)
+ keep="yes"
+ shift
+ ;;
+ -n | --noconfirm)
+ noconfirm="yes"
+ shift
+ ;;
+ -h | --help)
+ usage
+ exit 1
+ ;;
+ -q | --quiet)
+ donotify=1
+ shift
+ ;;
+ -r | --remote)
+ remote=$2
+ shift 2
+ ;;
+ -p | --port)
+ port=$2
+ shift 2
+ ;;
+ --sudo)
+ sudo=1
+ shift
+ ;;
+ *)
+ die "Unknown option: '$key'. Run '$name -h' for valid options."
+ ;;
+ esac
+done
+
+notify() {
+ for u in $(users | tr ' ' '\n' | sort -u); do
+ sudo -u "$u" DISPLAY=:0 \
+ DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(sudo -u "$u" id -u)/bus" \
+ notify-send -a $name "$1" "$2" --icon="dialog-$3"
+ done
+}
+
+notify_info() {
+ if [[ $donotify -eq 0 ]]; then
+ notify "$1" "$2" "information"
+ else
+ printf '%s\n' "$1: $2"
+ fi
+}
+
+notify_error() {
+ if [[ $donotify -eq 0 ]]; then
+ notify "$1" "$2" "error"
+ else
+ printf '%s\n' "$1: $2"
+ fi
+}
+
+[[ $EUID -ne 0 ]] && die "Script must be run as root. See '$name -h' for a description of options"
+! [[ -f $SNAPPER_CONFIG ]] && die "$SNAPPER_CONFIG does not exist."
+
+description=${description:-"latest incremental backup"}
+uuid_cmdline=${uuid_cmdline:-"none"}
+subvolid_cmdline=${subvolid_cmdline:-"5"}
+noconfirm=${noconfirm:-"no"}
+
+if [[ -z $remote ]]; then
+ if ! command -v rsync &>/dev/null; then
+ die "--remote specified but rsync command not found"
+ fi
+fi
+
+if [[ $uuid_cmdline != "none" ]]; then
+ if [[ -z $remote ]]; then
+ notify_info "Backup started" "Starting backups to $uuid_cmdline subvolid=$subvolid_cmdline..."
+ else
+ notify_info "Backup started" "Starting backups to $uuid_cmdline subvolid $subvolid_cmdline at $remote..."
+ fi
+else
+ if [[ -z $remote ]]; then
+ notify_info "Backup started" "Starting backups. Use command line menu to select disk."
+ else
+ notify_info "Backup started" "Starting backups. Use command line menu to select disk on $remote."
+ fi
+fi
+
+if [[ -n $remote ]]; then
+ ssh="ssh $remote"
+ if [[ -n $port ]]; then
+ ssh="$ssh -p $port"
+ fi
+ if [[ $sudo -eq 1 ]]; then
+ ssh="$ssh sudo"
+ fi
+fi
+
+if [[ "$($ssh findmnt -n -v --target / -o FSTYPE)" == "btrfs" ]]; then
+ EXCLUDE_UUID=$($ssh findmnt -n -v -t btrfs --target / -o UUID)
+ TARGETS=$($ssh findmnt -n -v -t btrfs -o UUID,TARGET --list | grep -v "$EXCLUDE_UUID" | awk '{print $2}')
+ UUIDS=$($ssh findmnt -n -v -t btrfs -o UUID,TARGET --list | grep -v "$EXCLUDE_UUID" | awk '{print $1}')
+else
+ TARGETS=$($ssh findmnt -n -v -t btrfs -o TARGET --list)
+ UUIDS=$($ssh findmnt -n -v -t btrfs -o UUID --list)
+fi
+
+declare -a TARGETS_ARRAY
+declare -a UUIDS_ARRAY
+declare -a SUBVOLIDS_ARRAY
+
+i=0
+for x in $TARGETS; do
+ SUBVOLIDS_ARRAY[i]=$($ssh btrfs subvolume show "$x" | awk '/Subvolume ID:/ { print $3 }')
+ TARGETS_ARRAY[i]=$x
+ i=$((i + 1))
+done
+
+i=0
+disk=-1
+disk_count=0
+for x in $UUIDS; do
+ UUIDS_ARRAY[i]=$x
+ if [[ $x == "$uuid_cmdline" && ${SUBVOLIDS_ARRAY[$((i))]} == "$subvolid_cmdline" ]]; then
+ disk=$i
+ disk_count=$((disk_count + 1))
+ fi
+ i=$((i + 1))
+done
+
+if [[ ${#UUIDS_ARRAY[$@]} -eq 0 ]]; then
+ die "No external btrfs subvolumes found to backup to. Run '$name -h' for more options."
+fi
+
+if [[ $disk_count -gt 1 ]]; then
+ printf "Multiple mount points were found with UUID %s and subvolid %s.\n" "$uuid_cmdline" "$subvolid_cmdline"
+ disk="-1"
+fi
+
+if [[ $disk == -1 ]]; then
+ if [[ $disk_count == 0 && $uuid_cmdline != "none" ]]; then
+ error "A device with UUID $uuid_cmdline and subvolid $subvolid_cmdline was not found to be mounted, or it is not a BTRFS device."
+ fi
+ if [[ -z $ssh ]]; then
+ printf "Select a mounted BTRFS device on your local machine to backup to.\nFor more options, exit and run '%s -h'.\n" "$name"
+ else
+ printf "Select a mounted BTRFS device on %s to backup to.\nFor more options, exit and run '%s -h'.\n" "$remote" "$name"
+ fi
+ while [[ $disk -lt 0 || $disk -gt $i ]]; do
+ for x in "${!TARGETS_ARRAY[@]}"; do
+ printf "%4s) %s (uuid=%s, subvolid=%s)\n" "$((x + 1))" "${TARGETS_ARRAY[$x]}" "${UUIDS_ARRAY[$x]}" "${SUBVOLIDS_ARRAY[$x]}"
+ done
+ printf "%4s) Exit\n" "0"
+ read -e -r -p "Enter a number: " disk
+ if ! [[ $disk == ?(-)+([0-9]) ]] || [[ $disk -lt 0 || $disk -gt $i ]]; then
+ printf "\nNo disk selected. Select a disk to continue.\n"
+ disk=-1
+ fi
+ done
+ if [[ $disk == 0 ]]; then
+ exit 0
+ fi
+ disk=$((disk - 1))
+fi
+
+selected_subvolid="${SUBVOLIDS_ARRAY[$((disk))]}"
+selected_uuid="${UUIDS_ARRAY[$((disk))]}"
+selected_mnt="${TARGETS_ARRAY[$((disk))]}"
+printf "\nYou selected the disk with uuid=%s, subvolid=%s.\n" "$selected_uuid" "$selected_subvolid"
+if [[ -z $ssh ]]; then
+ printf "The disk is mounted at '%s'.\n" "$selected_mnt"
+else
+ printf "The disk is mounted at '%s:%s'.\n" "$remote" "$selected_mnt"
+fi
+
+# shellcheck source=/dev/null
+source "$SNAPPER_CONFIG"
+
+if [[ -z $selected_configs ]]; then
+ printf "\nInteractively cycling through all snapper configurations...\n"
+fi
+selected_configs=${selected_configs:-$SNAPPER_CONFIGS}
+
+declare -a BACKUPDIRS_ARRAY
+declare -a MYBACKUPDIR_ARRAY
+declare -a OLD_NUM_ARRAY
+declare -a OLD_SNAP_ARRAY
+declare -a NEW_NUM_ARRAY
+declare -a NEW_SNAP_ARRAY
+declare -a NEW_INFO_ARRAY
+declare -a BACKUPLOC_ARRAY
+declare -a CONT_BACKUP_ARRAY
+
+# Initial configuration of where backup directories are
+i=0
+for x in $selected_configs; do
+
+ if [[ "$(snapper -c "$x" list --disable-used-space -t single | awk '/'"subvolid=$selected_subvolid, uuid=$selected_uuid"'/ {cnt++} END {print cnt}')" -gt 1 ]]; then
+ error "More than one snapper entry found with UUID $selected_uuid subvolid $selected_subvolid for configuration $x. Skipping configuration $x."
+ continue
+ fi
+
+ if [[ "$(snapper -c "$x" list --disable-used-space -t single | awk '/'$name' backup in progress/ {cnt++} END {print cnt}')" -gt 0 ]]; then
+ printf "\nNOTE: Previous failed %s backup snapshots found for '%s'.\n" "$name" "$x"
+ if [[ $noconfirm == "yes" ]]; then
+ printf "'noconfirm' option passed. Failed backups will not be deleted.\n"
+ else
+ read -e -r -p "Delete failed backup snapshot(s)? (These local snapshots from failed backups are not used.) [y/N]? " delete_failed
+ while [[ -n $delete_failed && $delete_failed != [Yy]"es" &&
+ $delete_failed != [Yy] && $delete_failed != [Nn]"o" &&
+ $delete_failed != [Nn] ]]; do
+ read -e -r -p "Delete failed backup snapshot(s)? (These local snapshots from failed backups are not used.) [y/N] " delete_failed
+ if [[ -n $delete_failed && $delete_failed != [Yy]"es" &&
+ $delete_failed != [Yy] && $delete_failed != [Nn]"o" &&
+ $delete_failed != [Nn] ]]; then
+ printf "Select 'y' or 'N'.\n"
+ fi
+ done
+ if [[ $delete_failed == [Yy]"es" || $delete_failed == [Yy] ]]; then
+ # explicit split list of snapshots (on whitespace) into multiple arguments
+ # shellcheck disable=SC2046
+ snapper -c "$x" delete $(snapper -c "$x" list --disable-used-space | awk '/'$name' backup in progress/ {print $1}')
+ fi
+ fi
+ fi
+
+ SNAP_SYNC_EXCLUDE=no
+
+ if [[ -f "/etc/snapper/configs/$x" ]]; then
+ # shellcheck source=/dev/null
+ source "/etc/snapper/configs/$x"
+ else
+ die "Selected snapper configuration $x does not exist."
+ fi
+
+ if [[ $SNAP_SYNC_EXCLUDE == "yes" ]]; then
+ continue
+ fi
+
+ printf "\n"
+
+ old_num=$(snapper -c "$x" list --disable-used-space -t single | awk '/'"subvolid=$selected_subvolid, uuid=$selected_uuid"'/ {print $1}')
+ old_snap=$SUBVOLUME/.snapshots/$old_num/snapshot
+
+ OLD_NUM_ARRAY[i]=$old_num
+ OLD_SNAP_ARRAY[i]=$old_snap
+
+ if [[ -z $old_num ]]; then
+ printf "No backups have been performed for '%s' on this disk.\n" "$x"
+ read -e -r -p "Enter name of subvolume to store backups, relative to $selected_mnt (to be created if not existing): " mybackupdir
+ printf "This will be the initial backup for snapper configuration '%s' to this disk. This could take awhile.\n" "$x"
+ BACKUPDIR="$selected_mnt/$mybackupdir"
+ $ssh test -d "$BACKUPDIR" || $ssh btrfs subvolume create "$BACKUPDIR"
+ else
+ mybackupdir=$(snapper -c "$x" list --disable-used-space -t single | awk -F"|" '/'"subvolid=$selected_subvolid, uuid=$selected_uuid"'/ {print $5}' | awk -F "," '/backupdir/ {print $1}' | awk -F"=" '{print $2}')
+ BACKUPDIR="$selected_mnt/$mybackupdir"
+ $ssh test -d "$BACKUPDIR" || die "%s is not a directory on %s.\n" "$BACKUPDIR" "$selected_uuid"
+ fi
+ BACKUPDIRS_ARRAY[i]="$BACKUPDIR"
+ MYBACKUPDIR_ARRAY[i]="$mybackupdir"
+
+ printf "Creating new local snapshot for '%s' configuration...\n" "$x"
+ new_num=$(snapper -c "$x" create --print-number -d "$name backup in progress")
+ new_snap=$SUBVOLUME/.snapshots/$new_num/snapshot
+ new_info=$SUBVOLUME/.snapshots/$new_num/info.xml
+ sync
+ backup_location=$BACKUPDIR/$x/$new_num/
+ if [[ -z $ssh ]]; then
+ printf "Will backup %s to %s\n" "$new_snap" "$backup_location/snapshot"
+ else
+ printf "Will backup %s to %s\n" "$new_snap" "$remote":"$backup_location/snapshot"
+ fi
+
+ if ($ssh test -d "$backup_location/snapshot"); then
+ printf "WARNING: Backup directory '%s' already exists. This configuration will be skipped!\n" "$backup_location/snapshot"
+ printf "Move or delete destination directory and try backup again.\n"
+ fi
+
+ NEW_NUM_ARRAY[i]="$new_num"
+ NEW_SNAP_ARRAY[i]="$new_snap"
+ NEW_INFO_ARRAY[i]="$new_info"
+ BACKUPLOC_ARRAY[i]="$backup_location"
+
+ cont_backup="K"
+ CONT_BACKUP_ARRAY[i]="yes"
+ if [[ $noconfirm == "yes" ]]; then
+ cont_backup="yes"
+ else
+ while [[ -n $cont_backup && $cont_backup != [Yy]"es" &&
+ $cont_backup != [Yy] && $cont_backup != [Nn]"o" &&
+ $cont_backup != [Nn] ]]; do
+ read -e -r -p "Proceed with backup of '$x' configuration [Y/n]? " cont_backup
+ if [[ -n $cont_backup && $cont_backup != [Yy]"es" &&
+ $cont_backup != [Yy] && $cont_backup != [Nn]"o" &&
+ $cont_backup != [Nn] ]]; then
+ printf "Select 'Y' or 'n'.\n"
+ fi
+ done
+ fi
+
+ if [[ $cont_backup != [Yy]"es" && $cont_backup != [Yy] && -n $cont_backup ]]; then
+ CONT_BACKUP_ARRAY[i]="no"
+ printf "Not backing up '%s' configuration.\n" "$x"
+ snapper -c "$x" delete "$new_num"
+ fi
+
+ i=$((i + 1))
+
+done
+
+# Actual backing up
+printf "\nPerforming backups...\n"
+i=-1
+for x in $selected_configs; do
+
+ i=$((i + 1))
+
+ SNAP_SYNC_EXCLUDE=no
+
+ if [[ -f "/etc/snapper/configs/$x" ]]; then
+ # shellcheck source=/dev/null
+ source "/etc/snapper/configs/$x"
+ else
+ die "Selected snapper configuration $x does not exist."
+ fi
+
+ cont_backup=${CONT_BACKUP_ARRAY[$i]}
+ if [[ $cont_backup == "no" || $SNAP_SYNC_EXCLUDE == "yes" ]]; then
+ notify_info "Backup in progress" "NOTE: Skipping $x configuration."
+ continue
+ fi
+
+ notify_info "Backup in progress" "Backing up $x configuration."
+
+ printf "\n"
+
+ old_num="${OLD_NUM_ARRAY[$i]}"
+ old_snap="${OLD_SNAP_ARRAY[$i]}"
+ BACKUPDIR="${BACKUPDIRS_ARRAY[$i]}"
+ mybackupdir="${MYBACKUPDIR_ARRAY[$i]}"
+ new_num="${NEW_NUM_ARRAY[$i]}"
+ new_snap="${NEW_SNAP_ARRAY[$i]}"
+ new_info="${NEW_INFO_ARRAY[$i]}"
+ backup_location="${BACKUPLOC_ARRAY[$i]}"
+
+ if ($ssh test -d "$backup_location/snapshot"); then
+ printf "ERROR: Backup directory '%s' already exists. Skipping backup of this configuration!\n" "$backup_location/snapshot"
+ continue
+ fi
+
+ $ssh mkdir -p "$backup_location"
+
+ if [[ -z $old_num ]]; then
+ printf "Sending first snapshot for '%s' configuration...\n" "$x"
+ if [[ $doprogress -eq 0 ]]; then
+ btrfs send "$new_snap" | pv | $ssh btrfs receive "$backup_location" &>/dev/null
+ else
+ btrfs send "$new_snap" | $ssh btrfs receive "$backup_location" &>/dev/null
+ fi
+ else
+
+ printf "Sending incremental snapshot for '%s' configuration...\n" "$x"
+ # Sends the difference between the new snapshot and old snapshot to the
+ # backup location. Using the -c flag instead of -p tells it that there
+ # is an identical subvolume to the old snapshot at the receiving
+ # location where it can get its data. This helps speed up the transfer.
+
+ if [[ $doprogress -eq 0 ]]; then
+ btrfs send -c "$old_snap" "$new_snap" | pv | $ssh btrfs receive "$backup_location"
+ else
+ btrfs send -c "$old_snap" "$new_snap" | $ssh btrfs receive "$backup_location"
+ fi
+
+ if [[ $keep == "yes" ]]; then
+ printf "Modifying data for old local snapshot for '%s' configuration...\n" "$x"
+ snapper -v -c "$x" modify -d "old snap-sync snapshot (you may remove)" -u "backupdir=,subvolid=,uuid=" -c "number" "$old_num"
+ else
+ printf "Deleting old snapshot for %s...\n" "$x"
+ snapper -c "$x" delete "$old_num"
+ fi
+
+ fi
+
+ if [[ -z $remote ]]; then
+ cp "$new_info" "$backup_location"
+ else
+ if [[ -z $port ]]; then
+ rsync -avzq "$new_info" "$remote":"$backup_location"
+ else
+ rsync -avzqe "ssh -p $port" "$new_info" "$remote":"$backup_location"
+ fi
+ fi
+
+ # It's important not to change this userdata in the snapshots, since that's how
+ # we find the previous one.
+
+ userdata="backupdir=$mybackupdir, subvolid=$selected_subvolid, uuid=$selected_uuid"
+
+ # Tag new snapshot as the latest
+ printf "Tagging local snapshot as latest backup for '%s' configuration...\n" "$x"
+ snapper -v -c "$x" modify -d "$description" -u "$userdata" "$new_num"
+
+ printf "Backup complete for '%s' configuration.\n" "$x"
+
+done
+
+printf "\nDone!\n"
+
+if [[ $uuid_cmdline != "none" ]]; then
+ notify_info "Finished" "Backups to $uuid_cmdline complete!"
+else
+ notify_info "Finished" "Backups complete!"
+fi
diff --git a/pkgs/sources/tree-sitter-yts/.editorconfig b/pkgs/sources/tree-sitter-yts/.editorconfig
new file mode 100644
index 00000000..919c78fa
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/.editorconfig
@@ -0,0 +1,21 @@
+root = true
+
+[*]
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = false
+
+# for testing purposes, the corpus may have trailing whitespace
+# and may have mixed EOL.
+# Still want a final newline though, as that makes no semantic difference.
+[corpus/*]
+trim_trailing_whitespace = false
+end_of_line = unset
+
+[**.{js,json,cc,css}]
+indent_style = space
+indent_size = 2
+
+# tree-sitter generate emits json with no trailing newline
+[src/node-types.json]
+insert_final_newline = false
diff --git a/pkgs/sources/tree-sitter-yts/.envrc b/pkgs/sources/tree-sitter-yts/.envrc
new file mode 100644
index 00000000..3550a30f
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/pkgs/sources/tree-sitter-yts/.gitignore b/pkgs/sources/tree-sitter-yts/.gitignore
new file mode 100644
index 00000000..c4e2e389
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/.gitignore
@@ -0,0 +1,3 @@
+/.direnv
+/result
+/node_modules
diff --git a/pkgs/sources/tree-sitter-yts/Cargo.toml b/pkgs/sources/tree-sitter-yts/Cargo.toml
new file mode 100644
index 00000000..5287c420
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "tree-sitter-yts"
+description = "yts grammar for the tree-sitter parsing library"
+version = "0.0.1"
+keywords = ["incremental", "parsing", "yts"]
+categories = ["parsing", "text-editors"]
+repository = "https://github.com/tree-sitter/tree-sitter-yts"
+edition = "2018"
+license = "MIT"
+
+build = "bindings/rust/build.rs"
+include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*"]
+
+[lib]
+path = "bindings/rust/lib.rs"
+
+[dependencies]
+tree-sitter = "~0.20.10"
+
+[build-dependencies]
+cc = "1.0"
diff --git a/pkgs/sources/tree-sitter-yts/binding.gyp b/pkgs/sources/tree-sitter-yts/binding.gyp
new file mode 100644
index 00000000..b05038b4
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/binding.gyp
@@ -0,0 +1,19 @@
+{
+ "targets": [
+ {
+ "target_name": "tree_sitter_yts_binding",
+ "include_dirs": [
+ "<!(node -e \"require('nan')\")",
+ "src"
+ ],
+ "sources": [
+ "bindings/node/binding.cc",
+ "src/parser.c",
+ # If your language uses an external scanner, add it here.
+ ],
+ "cflags_c": [
+ "-std=c99",
+ ]
+ }
+ ]
+}
diff --git a/pkgs/sources/tree-sitter-yts/bindings/node/binding.cc b/pkgs/sources/tree-sitter-yts/bindings/node/binding.cc
new file mode 100644
index 00000000..a042be54
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/bindings/node/binding.cc
@@ -0,0 +1,33 @@
+#include "nan.h"
+#include "tree_sitter/parser.h"
+#include <node.h>
+
+using namespace v8;
+
+extern "C" TSLanguage *tree_sitter_yts ();
+
+namespace
+{
+
+NAN_METHOD (New) {}
+
+void
+Init (Local<Object> exports, Local<Object> module)
+{
+ Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate> (New);
+ tpl->SetClassName (Nan::New ("Language").ToLocalChecked ());
+ tpl->InstanceTemplate ()->SetInternalFieldCount (1);
+
+ Local<Function> constructor = Nan::GetFunction (tpl).ToLocalChecked ();
+ Local<Object> instance
+ = constructor->NewInstance (Nan::GetCurrentContext ()).ToLocalChecked ();
+ Nan::SetInternalFieldPointer (instance, 0, tree_sitter_yts ());
+
+ Nan::Set (instance, Nan::New ("name").ToLocalChecked (),
+ Nan::New ("yts").ToLocalChecked ());
+ Nan::Set (module, Nan::New ("exports").ToLocalChecked (), instance);
+}
+
+NODE_MODULE (tree_sitter_yts_binding, Init)
+
+} // namespace
diff --git a/pkgs/sources/tree-sitter-yts/bindings/node/index.js b/pkgs/sources/tree-sitter-yts/bindings/node/index.js
new file mode 100644
index 00000000..32179742
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/bindings/node/index.js
@@ -0,0 +1,19 @@
+try {
+ module.exports = require("../../build/Release/tree_sitter_yts_binding");
+} catch (error1) {
+ if (error1.code !== "MODULE_NOT_FOUND") {
+ throw error1;
+ }
+ try {
+ module.exports = require("../../build/Debug/tree_sitter_yts_binding");
+ } catch (error2) {
+ if (error2.code !== "MODULE_NOT_FOUND") {
+ throw error2;
+ }
+ throw error1;
+ }
+}
+
+try {
+ module.exports.nodeTypeInfo = require("../../src/node-types.json");
+} catch (_) {}
diff --git a/pkgs/sources/tree-sitter-yts/bindings/rust/build.rs b/pkgs/sources/tree-sitter-yts/bindings/rust/build.rs
new file mode 100644
index 00000000..c6061f09
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/bindings/rust/build.rs
@@ -0,0 +1,40 @@
+fn main() {
+ let src_dir = std::path::Path::new("src");
+
+ let mut c_config = cc::Build::new();
+ c_config.include(&src_dir);
+ c_config
+ .flag_if_supported("-Wno-unused-parameter")
+ .flag_if_supported("-Wno-unused-but-set-variable")
+ .flag_if_supported("-Wno-trigraphs");
+ let parser_path = src_dir.join("parser.c");
+ c_config.file(&parser_path);
+
+ // If your language uses an external scanner written in C,
+ // then include this block of code:
+
+ /*
+ let scanner_path = src_dir.join("scanner.c");
+ c_config.file(&scanner_path);
+ println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
+ */
+
+ c_config.compile("parser");
+ println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
+
+ // If your language uses an external scanner written in C++,
+ // then include this block of code:
+
+ /*
+ let mut cpp_config = cc::Build::new();
+ cpp_config.cpp(true);
+ cpp_config.include(&src_dir);
+ cpp_config
+ .flag_if_supported("-Wno-unused-parameter")
+ .flag_if_supported("-Wno-unused-but-set-variable");
+ let scanner_path = src_dir.join("scanner.cc");
+ cpp_config.file(&scanner_path);
+ cpp_config.compile("scanner");
+ println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
+ */
+}
diff --git a/pkgs/sources/tree-sitter-yts/bindings/rust/lib.rs b/pkgs/sources/tree-sitter-yts/bindings/rust/lib.rs
new file mode 100644
index 00000000..f1868b2d
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/bindings/rust/lib.rs
@@ -0,0 +1,52 @@
+//! This crate provides yts language support for the [tree-sitter][] parsing library.
+//!
+//! Typically, you will use the [language][language func] function to add this language to a
+//! tree-sitter [Parser][], and then use the parser to parse some code:
+//!
+//! ```
+//! let code = "";
+//! let mut parser = tree_sitter::Parser::new();
+//! parser.set_language(tree_sitter_yts::language()).expect("Error loading yts grammar");
+//! let tree = parser.parse(code, None).unwrap();
+//! ```
+//!
+//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
+//! [language func]: fn.language.html
+//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
+//! [tree-sitter]: https://tree-sitter.github.io/
+
+use tree_sitter::Language;
+
+extern "C" {
+ fn tree_sitter_yts() -> Language;
+}
+
+/// Get the tree-sitter [Language][] for this grammar.
+///
+/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
+pub fn language() -> Language {
+ unsafe { tree_sitter_yts() }
+}
+
+/// The content of the [`node-types.json`][] file for this grammar.
+///
+/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
+pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json");
+
+// Uncomment these to include any queries that this grammar contains
+
+// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm");
+// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm");
+// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
+// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_can_load_grammar() {
+ let mut parser = tree_sitter::Parser::new();
+ parser
+ .set_language(super::language())
+ .expect("Error loading yts language");
+ }
+}
diff --git a/pkgs/sources/tree-sitter-yts/corpus/comments.txt b/pkgs/sources/tree-sitter-yts/corpus/comments.txt
new file mode 100644
index 00000000..0070baf8
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/corpus/comments.txt
@@ -0,0 +1,51 @@
+================================================================================
+Parse multiple lines
+================================================================================
+
+pick 6221 "Name" "2024-01-17" "A" "[0m 0s]" "url"
+pick 6181 "Name2" "2024-01-16" "A2" "[0m 0s]" "url"
+
+# This is a comment
+# it contains information
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote)))
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote)))
+ (comment)
+ (comment))
diff --git a/pkgs/sources/tree-sitter-yts/corpus/comments_correct.txt b/pkgs/sources/tree-sitter-yts/corpus/comments_correct.txt
new file mode 100644
index 00000000..40cdab7d
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/corpus/comments_correct.txt
@@ -0,0 +1,27 @@
+================================================================================
+Disregard comments in title
+================================================================================
+
+pick 6094 "#100 Name" "2024-01-12" "A" "[133m 29s]" "url"
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote))))
diff --git a/pkgs/sources/tree-sitter-yts/corpus/duration.txt b/pkgs/sources/tree-sitter-yts/corpus/duration.txt
new file mode 100644
index 00000000..59476b98
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/corpus/duration.txt
@@ -0,0 +1,84 @@
+================================================================================
+Parse multiple lines with different durations
+================================================================================
+
+pick 6221 "Name" "2024-01-17" "A" "[1h 0m]" "url"
+pick 6181 "Name2" "2024-01-16" "A2" "[20m 02s]" "url2"
+pick 6184 "Name3" "2024-01-16" "A3" "[20h 0m]" "url3"
+pick 6206 "Name4" "2024-01-16" "A4" "[No Duration]" "url4"
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote)))
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote)))
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote)))
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote))))
diff --git a/pkgs/sources/tree-sitter-yts/corpus/url.txt b/pkgs/sources/tree-sitter-yts/corpus/url.txt
new file mode 100644
index 00000000..1ae3d106
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/corpus/url.txt
@@ -0,0 +1,84 @@
+================================================================================
+Parse multiple lines with url
+================================================================================
+
+pick 6221 "Name" "2024-01-17" "A" "[0h 0m]" "url"
+pick 6181 "Name2" "2024-01-16" "A2" "[0h 0m]" "url2"
+pick 6184 "Name3" "2024-01-16" "A3" "[0h 0m]" "url3"
+pick 6206 "Name4" "2024-01-16" "A4" "[0h 0m]" "url4"
+
+--------------------------------------------------------------------------------
+
+(source_file
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote)))
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote)))
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote)))
+ (line
+ (command)
+ (id)
+ (title
+ (quote)
+ (quote))
+ (date
+ (quote)
+ (quote))
+ (author
+ (quote)
+ (quote))
+ (duration
+ (quote)
+ (quote))
+ (url
+ (quote)
+ (quote))))
diff --git a/pkgs/sources/tree-sitter-yts/default.nix b/pkgs/sources/tree-sitter-yts/default.nix
new file mode 100644
index 00000000..7e15481c
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/default.nix
@@ -0,0 +1,11 @@
+[
+ (
+ final: prev: {
+ yts-grammar = (prev.callPackage ./package.nix {}) {
+ language = "yts";
+ version = "1.0";
+ src = ./.;
+ };
+ }
+ )
+]
diff --git a/pkgs/sources/tree-sitter-yts/flake.lock b/pkgs/sources/tree-sitter-yts/flake.lock
new file mode 100644
index 00000000..bff9f1fa
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/flake.lock
@@ -0,0 +1,97 @@
+{
+ "nodes": {
+ "crane": {
+ "inputs": {
+ "nixpkgs": ["nixpkgs"]
+ },
+ "locked": {
+ "lastModified": 1704819371,
+ "narHash": "sha256-oFUfPWrWGQTZaCM3byxwYwrMLwshDxVGOrMH5cVP/X8=",
+ "owner": "ipetkov",
+ "repo": "crane",
+ "rev": "5c234301a1277e4cc759c23a2a7a00a06ddd7111",
+ "type": "github"
+ },
+ "original": {
+ "owner": "ipetkov",
+ "repo": "crane",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1701680307,
+ "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1705133751,
+ "narHash": "sha256-rCIsyE80jgiOU78gCWN3A0wE0tR2GI5nH6MlS+HaaSQ=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "9b19f5e77dd906cb52dade0b7bd280339d2a1f3d",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "npmlock2nix": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1673447413,
+ "narHash": "sha256-sJM82Sj8yfQYs9axEmGZ9Evzdv/kDcI9sddqJ45frrU=",
+ "owner": "nix-community",
+ "repo": "npmlock2nix",
+ "rev": "9197bbf397d76059a76310523d45df10d2e4ca81",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-community",
+ "repo": "npmlock2nix",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "crane": "crane",
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs",
+ "npmlock2nix": "npmlock2nix"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/pkgs/sources/tree-sitter-yts/flake.nix b/pkgs/sources/tree-sitter-yts/flake.nix
new file mode 100644
index 00000000..1b6f8ab0
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/flake.nix
@@ -0,0 +1,82 @@
+{
+ description = "tree-sitter-yts";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+ flake-utils.url = "github:numtide/flake-utils";
+
+ npmlock2nix = {
+ url = "github:nix-community/npmlock2nix";
+ flake = false;
+ };
+
+ crane = {
+ url = "github:ipetkov/crane";
+ inputs.nixpkgs.follows = "nixpkgs";
+ };
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ flake-utils,
+ npmlock2nix,
+ crane,
+ }: (flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = nixpkgs.legacyPackages.${system};
+ inherit (pkgs) lib;
+
+ npmlock2nix' = pkgs.callPackage npmlock2nix {};
+ craneLib = crane.lib.${system};
+ in {
+ build = self.packages.${system}.tree-sitter-nix;
+
+ rust-bindings = craneLib.buildPackage {
+ src = self;
+ };
+
+ # Requires xcode
+ node-bindings = npmlock2nix'.v2.build {
+ src = self;
+ inherit (self.devShells.${system}.default) nativeBuildInputs;
+ inherit (pkgs) nodejs;
+
+ buildCommands = [
+ "${pkgs.nodePackages.node-gyp}/bin/node-gyp configure"
+ "npm run build"
+ ];
+
+ installPhase = ''
+ touch $out
+ '';
+ };
+
+ packages.tree-sitter-yts = (pkgs.callPackage ./grammar.nix {}) {
+ language = "yts";
+ version = "1.0";
+ src = self;
+ };
+
+ packages.default = self.packages.${system}.tree-sitter-yts;
+ devShells.default = pkgs.mkShell {
+ packages = [
+ pkgs.nodejs
+ pkgs.python3
+
+ pkgs.tree-sitter
+ pkgs.editorconfig-checker
+
+ pkgs.rustc
+ pkgs.cargo
+
+ # Formatters
+ pkgs.treefmt
+ pkgs.nixpkgs-fmt
+ pkgs.nodePackages.prettier
+ pkgs.rustfmt
+ pkgs.clang-tools
+ ];
+ };
+ }));
+}
diff --git a/pkgs/sources/tree-sitter-yts/grammar.js b/pkgs/sources/tree-sitter-yts/grammar.js
new file mode 100644
index 00000000..655f6dea
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/grammar.js
@@ -0,0 +1,26 @@
+module.exports = grammar({
+ name: "yts",
+
+ rules: {
+ source_file: ($) => repeat(choice($.line, $.comment)),
+ line: ($) =>
+ seq($.command, $.id, $.title, $.date, $.author, $.duration, $.url, "\n"),
+
+ command: ($) => choice("pick", "p", "watch", "w", "drop", "d", "url", "u"),
+ id: ($) => /[0-9]+/,
+ title: ($) => seq($._q, /[^"]+/, $._q),
+ date: ($) => seq($._q, /\d{4}-\d{2}-\d{2}/, $._q),
+ author: ($) => seq($._q, /[^"]+/, $._q),
+ duration: ($) =>
+ seq(
+ $._q,
+ seq("[", choice("No Duration", /\d+m \d+s/, /\d+h \d+m/), "]"),
+ $._q,
+ ),
+ url: ($) => seq($._q, /[^"]+/, $._q),
+ comment: ($) => /#.*/,
+ _q: ($) => $.quote,
+ quote: ($) => /"/,
+ },
+ extras: ($) => [/\s/, /\\\r?\n/],
+});
diff --git a/pkgs/sources/tree-sitter-yts/highlight.yts b/pkgs/sources/tree-sitter-yts/highlight.yts
new file mode 100644
index 00000000..319ee95c
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/highlight.yts
@@ -0,0 +1,4 @@
+pick 6221 "Name" "2024-01-17" "A" "0:00" "url"
+pick 6181 "Name2" "2024-01-16" "A2" "0:00" "url2"
+pick 6184 "Name3" "2024-01-16" "A3" "0:00" "url3"
+pick 6206 "Name4" "2024-01-16" "A4" "299:36" "url4"
diff --git a/pkgs/sources/tree-sitter-yts/package.json b/pkgs/sources/tree-sitter-yts/package.json
new file mode 100644
index 00000000..2511ccb7
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "tree-sitter-yts",
+ "version": "0.0.1",
+ "description": "yts grammar for tree-sitter",
+ "main": "bindings/node",
+ "keywords": [
+ "parsing",
+ "incremental"
+ ],
+ "dependencies": {
+ "nan": "^2.12.1"
+ },
+ "devDependencies": {
+ "tree-sitter-cli": "^0.20.8"
+ },
+ "scripts": {
+ "test": "tree-sitter test"
+ },
+ "tree-sitter": [
+ {
+ "scope": "source.yts",
+ "file-types": [
+ "yts"
+ ],
+ "highlights": [
+ "queries/highlights.scm"
+ ],
+ "injection-regex": "^(yts)$"
+ }
+ ]
+}
diff --git a/pkgs/sources/tree-sitter-yts/package.nix b/pkgs/sources/tree-sitter-yts/package.nix
new file mode 100644
index 00000000..fe9a7326
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/package.nix
@@ -0,0 +1,63 @@
+# taken from nixpgks: pkgs/development/tools/parsing/tree-sitter/grammar.nix
+{
+ stdenv,
+ nodejs,
+ tree-sitter,
+ lib,
+}:
+# Build a parser grammar and put the resulting shared object in `$out/parser`
+{
+ # language name
+ language,
+ version,
+ src,
+ location ? null,
+ generate ? false,
+ ...
+} @ args:
+stdenv.mkDerivation ({
+ pname = "${language}-grammar";
+
+ inherit src version;
+
+ nativeBuildInputs = lib.optionals generate [nodejs tree-sitter];
+
+ CFLAGS = ["-Isrc" "-O2"];
+ CXXFLAGS = ["-Isrc" "-O2"];
+
+ stripDebugList = ["parser"];
+
+ configurePhase =
+ lib.optionalString (location != null) ''
+ cd ${location}
+ ''
+ + lib.optionalString generate ''
+ tree-sitter generate
+ '';
+
+ # When both scanner.{c,cc} exist, we should not link both since they may be the same but in
+ # different languages. Just randomly prefer C++ if that happens.
+ buildPhase = ''
+ runHook preBuild
+ if [[ -e src/scanner.cc ]]; then
+ $CXX -fPIC -c src/scanner.cc -o scanner.o $CXXFLAGS
+ elif [[ -e src/scanner.c ]]; then
+ $CC -fPIC -c src/scanner.c -o scanner.o $CFLAGS
+ fi
+ $CC -fPIC -c src/parser.c -o parser.o $CFLAGS
+ rm -rf parser
+ $CXX -shared -o parser *.o
+ runHook postBuild
+ '';
+
+ installPhase = ''
+ runHook preInstall
+ mkdir $out
+ mv parser $out/
+ if [[ -d queries ]]; then
+ cp -r queries $out
+ fi
+ runHook postInstall
+ '';
+ }
+ // removeAttrs args ["language" "location" "generate"])
diff --git a/pkgs/sources/tree-sitter-yts/queries/highlights.scm b/pkgs/sources/tree-sitter-yts/queries/highlights.scm
new file mode 100644
index 00000000..674cbf18
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/queries/highlights.scm
@@ -0,0 +1,11 @@
+(command) @keyword
+(id) @constant
+(title) @text.title
+(date) @number
+(author) @operator
+(duration) @property
+((url) @conceal (#set! conceal ""))
+
+
+((quote) @conceal (#set! conceal ""))
+(comment) @comment @spell
diff --git a/pkgs/sources/tree-sitter-yts/src/grammar.json b/pkgs/sources/tree-sitter-yts/src/grammar.json
new file mode 100644
index 00000000..a35a5464
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/src/grammar.json
@@ -0,0 +1,238 @@
+{
+ "name": "yts",
+ "rules": {
+ "source_file": {
+ "type": "REPEAT",
+ "content": {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "line"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "comment"
+ }
+ ]
+ }
+ },
+ "line": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "command"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "id"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "title"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "date"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "author"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "duration"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "url"
+ },
+ {
+ "type": "STRING",
+ "value": "\n"
+ }
+ ]
+ },
+ "command": {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "pick"
+ },
+ {
+ "type": "STRING",
+ "value": "p"
+ },
+ {
+ "type": "STRING",
+ "value": "watch"
+ },
+ {
+ "type": "STRING",
+ "value": "w"
+ },
+ {
+ "type": "STRING",
+ "value": "drop"
+ },
+ {
+ "type": "STRING",
+ "value": "d"
+ },
+ {
+ "type": "STRING",
+ "value": "url"
+ },
+ {
+ "type": "STRING",
+ "value": "u"
+ }
+ ]
+ },
+ "id": {
+ "type": "PATTERN",
+ "value": "[0-9]+"
+ },
+ "title": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ },
+ {
+ "type": "PATTERN",
+ "value": "[^\"]+"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ }
+ ]
+ },
+ "date": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ },
+ {
+ "type": "PATTERN",
+ "value": "\\d{4}-\\d{2}-\\d{2}"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ }
+ ]
+ },
+ "author": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ },
+ {
+ "type": "PATTERN",
+ "value": "[^\"]+"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ }
+ ]
+ },
+ "duration": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ },
+ {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "["
+ },
+ {
+ "type": "CHOICE",
+ "members": [
+ {
+ "type": "STRING",
+ "value": "No Duration"
+ },
+ {
+ "type": "PATTERN",
+ "value": "\\d+m \\d+s"
+ },
+ {
+ "type": "PATTERN",
+ "value": "\\d+h \\d+m"
+ }
+ ]
+ },
+ {
+ "type": "STRING",
+ "value": "]"
+ }
+ ]
+ },
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ }
+ ]
+ },
+ "url": {
+ "type": "SEQ",
+ "members": [
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ },
+ {
+ "type": "PATTERN",
+ "value": "[^\"]+"
+ },
+ {
+ "type": "SYMBOL",
+ "name": "_q"
+ }
+ ]
+ },
+ "comment": {
+ "type": "PATTERN",
+ "value": "#.*"
+ },
+ "_q": {
+ "type": "SYMBOL",
+ "name": "quote"
+ },
+ "quote": {
+ "type": "PATTERN",
+ "value": "\""
+ }
+ },
+ "extras": [
+ {
+ "type": "PATTERN",
+ "value": "\\s"
+ },
+ {
+ "type": "PATTERN",
+ "value": "\\\\\\r?\\n"
+ }
+ ],
+ "conflicts": [],
+ "precedences": [],
+ "externals": [],
+ "inline": [],
+ "supertypes": []
+}
+
diff --git a/pkgs/sources/tree-sitter-yts/src/node-types.json b/pkgs/sources/tree-sitter-yts/src/node-types.json
new file mode 100644
index 00000000..1a63a552
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/src/node-types.json
@@ -0,0 +1,200 @@
+[
+ {
+ "type": "author",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": true,
+ "types": [
+ {
+ "type": "quote",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "command",
+ "named": true,
+ "fields": {}
+ },
+ {
+ "type": "date",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": true,
+ "types": [
+ {
+ "type": "quote",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "duration",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": true,
+ "types": [
+ {
+ "type": "quote",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "line",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": true,
+ "types": [
+ {
+ "type": "author",
+ "named": true
+ },
+ {
+ "type": "command",
+ "named": true
+ },
+ {
+ "type": "date",
+ "named": true
+ },
+ {
+ "type": "duration",
+ "named": true
+ },
+ {
+ "type": "id",
+ "named": true
+ },
+ {
+ "type": "title",
+ "named": true
+ },
+ {
+ "type": "url",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "source_file",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": false,
+ "types": [
+ {
+ "type": "comment",
+ "named": true
+ },
+ {
+ "type": "line",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "title",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": true,
+ "types": [
+ {
+ "type": "quote",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "url",
+ "named": true,
+ "fields": {},
+ "children": {
+ "multiple": true,
+ "required": true,
+ "types": [
+ {
+ "type": "quote",
+ "named": true
+ }
+ ]
+ }
+ },
+ {
+ "type": "\n",
+ "named": false
+ },
+ {
+ "type": "No Duration",
+ "named": false
+ },
+ {
+ "type": "[",
+ "named": false
+ },
+ {
+ "type": "]",
+ "named": false
+ },
+ {
+ "type": "comment",
+ "named": true
+ },
+ {
+ "type": "d",
+ "named": false
+ },
+ {
+ "type": "drop",
+ "named": false
+ },
+ {
+ "type": "id",
+ "named": true
+ },
+ {
+ "type": "p",
+ "named": false
+ },
+ {
+ "type": "pick",
+ "named": false
+ },
+ {
+ "type": "quote",
+ "named": true
+ },
+ {
+ "type": "u",
+ "named": false
+ },
+ {
+ "type": "url",
+ "named": false
+ },
+ {
+ "type": "w",
+ "named": false
+ },
+ {
+ "type": "watch",
+ "named": false
+ }
+] \ No newline at end of file
diff --git a/pkgs/sources/tree-sitter-yts/src/parser.c b/pkgs/sources/tree-sitter-yts/src/parser.c
new file mode 100644
index 00000000..ded5c342
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/src/parser.c
@@ -0,0 +1,1108 @@
+#include <tree_sitter/parser.h>
+
+#if defined(__GNUC__) || defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
+#endif
+
+#define LANGUAGE_VERSION 14
+#define STATE_COUNT 31
+#define LARGE_STATE_COUNT 2
+#define SYMBOL_COUNT 30
+#define ALIAS_COUNT 0
+#define TOKEN_COUNT 20
+#define EXTERNAL_TOKEN_COUNT 0
+#define FIELD_COUNT 0
+#define MAX_ALIAS_SEQUENCE_LENGTH 8
+#define PRODUCTION_ID_COUNT 1
+
+enum
+{
+ anon_sym_LF = 1,
+ anon_sym_pick = 2,
+ anon_sym_p = 3,
+ anon_sym_watch = 4,
+ anon_sym_w = 5,
+ anon_sym_drop = 6,
+ anon_sym_d = 7,
+ anon_sym_url = 8,
+ anon_sym_u = 9,
+ sym_id = 10,
+ aux_sym_title_token1 = 11,
+ aux_sym_date_token1 = 12,
+ anon_sym_LBRACK = 13,
+ anon_sym_NoDuration = 14,
+ aux_sym_duration_token1 = 15,
+ aux_sym_duration_token2 = 16,
+ anon_sym_RBRACK = 17,
+ sym_comment = 18,
+ sym_quote = 19,
+ sym_source_file = 20,
+ sym_line = 21,
+ sym_command = 22,
+ sym_title = 23,
+ sym_date = 24,
+ sym_author = 25,
+ sym_duration = 26,
+ sym_url = 27,
+ sym__q = 28,
+ aux_sym_source_file_repeat1 = 29,
+};
+
+static const char *const ts_symbol_names[] = {
+ [ts_builtin_sym_end] = "end",
+ [anon_sym_LF] = "\n",
+ [anon_sym_pick] = "pick",
+ [anon_sym_p] = "p",
+ [anon_sym_watch] = "watch",
+ [anon_sym_w] = "w",
+ [anon_sym_drop] = "drop",
+ [anon_sym_d] = "d",
+ [anon_sym_url] = "url",
+ [anon_sym_u] = "u",
+ [sym_id] = "id",
+ [aux_sym_title_token1] = "title_token1",
+ [aux_sym_date_token1] = "date_token1",
+ [anon_sym_LBRACK] = "[",
+ [anon_sym_NoDuration] = "No Duration",
+ [aux_sym_duration_token1] = "duration_token1",
+ [aux_sym_duration_token2] = "duration_token2",
+ [anon_sym_RBRACK] = "]",
+ [sym_comment] = "comment",
+ [sym_quote] = "quote",
+ [sym_source_file] = "source_file",
+ [sym_line] = "line",
+ [sym_command] = "command",
+ [sym_title] = "title",
+ [sym_date] = "date",
+ [sym_author] = "author",
+ [sym_duration] = "duration",
+ [sym_url] = "url",
+ [sym__q] = "_q",
+ [aux_sym_source_file_repeat1] = "source_file_repeat1",
+};
+
+static const TSSymbol ts_symbol_map[] = {
+ [ts_builtin_sym_end] = ts_builtin_sym_end,
+ [anon_sym_LF] = anon_sym_LF,
+ [anon_sym_pick] = anon_sym_pick,
+ [anon_sym_p] = anon_sym_p,
+ [anon_sym_watch] = anon_sym_watch,
+ [anon_sym_w] = anon_sym_w,
+ [anon_sym_drop] = anon_sym_drop,
+ [anon_sym_d] = anon_sym_d,
+ [anon_sym_url] = anon_sym_url,
+ [anon_sym_u] = anon_sym_u,
+ [sym_id] = sym_id,
+ [aux_sym_title_token1] = aux_sym_title_token1,
+ [aux_sym_date_token1] = aux_sym_date_token1,
+ [anon_sym_LBRACK] = anon_sym_LBRACK,
+ [anon_sym_NoDuration] = anon_sym_NoDuration,
+ [aux_sym_duration_token1] = aux_sym_duration_token1,
+ [aux_sym_duration_token2] = aux_sym_duration_token2,
+ [anon_sym_RBRACK] = anon_sym_RBRACK,
+ [sym_comment] = sym_comment,
+ [sym_quote] = sym_quote,
+ [sym_source_file] = sym_source_file,
+ [sym_line] = sym_line,
+ [sym_command] = sym_command,
+ [sym_title] = sym_title,
+ [sym_date] = sym_date,
+ [sym_author] = sym_author,
+ [sym_duration] = sym_duration,
+ [sym_url] = sym_url,
+ [sym__q] = sym__q,
+ [aux_sym_source_file_repeat1] = aux_sym_source_file_repeat1,
+};
+
+static const TSSymbolMetadata ts_symbol_metadata[] = {
+ [ts_builtin_sym_end] = {
+ .visible = false,
+ .named = true,
+ },
+ [anon_sym_LF] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_pick] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_p] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_watch] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_w] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_drop] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_d] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_url] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_u] = {
+ .visible = true,
+ .named = false,
+ },
+ [sym_id] = {
+ .visible = true,
+ .named = true,
+ },
+ [aux_sym_title_token1] = {
+ .visible = false,
+ .named = false,
+ },
+ [aux_sym_date_token1] = {
+ .visible = false,
+ .named = false,
+ },
+ [anon_sym_LBRACK] = {
+ .visible = true,
+ .named = false,
+ },
+ [anon_sym_NoDuration] = {
+ .visible = true,
+ .named = false,
+ },
+ [aux_sym_duration_token1] = {
+ .visible = false,
+ .named = false,
+ },
+ [aux_sym_duration_token2] = {
+ .visible = false,
+ .named = false,
+ },
+ [anon_sym_RBRACK] = {
+ .visible = true,
+ .named = false,
+ },
+ [sym_comment] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_quote] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_source_file] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_line] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_command] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_title] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_date] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_author] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_duration] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym_url] = {
+ .visible = true,
+ .named = true,
+ },
+ [sym__q] = {
+ .visible = false,
+ .named = true,
+ },
+ [aux_sym_source_file_repeat1] = {
+ .visible = false,
+ .named = false,
+ },
+};
+
+static const TSSymbol ts_alias_sequences[PRODUCTION_ID_COUNT]
+ [MAX_ALIAS_SEQUENCE_LENGTH]
+ = {
+ [0] = { 0 },
+ };
+
+static const uint16_t ts_non_terminal_alias_map[] = {
+ 0,
+};
+
+static const TSStateId ts_primary_state_ids[STATE_COUNT] = {
+ [0] = 0, [1] = 1, [2] = 2, [3] = 3, [4] = 4, [5] = 5, [6] = 6,
+ [7] = 7, [8] = 8, [9] = 9, [10] = 10, [11] = 11, [12] = 12, [13] = 13,
+ [14] = 14, [15] = 15, [16] = 16, [17] = 17, [18] = 18, [19] = 19, [20] = 20,
+ [21] = 21, [22] = 22, [23] = 23, [24] = 24, [25] = 25, [26] = 26, [27] = 27,
+ [28] = 28, [29] = 29, [30] = 30,
+};
+
+static bool
+ts_lex (TSLexer *lexer, TSStateId state)
+{
+ START_LEXER ();
+ eof = lexer->eof (lexer);
+ switch (state)
+ {
+ case 0:
+ if (eof)
+ ADVANCE (47);
+ if (lookahead == '"')
+ ADVANCE (74);
+ if (lookahead == '#')
+ ADVANCE (73);
+ if (lookahead == 'N')
+ ADVANCE (30);
+ if (lookahead == '[')
+ ADVANCE (68);
+ if (lookahead == '\\')
+ SKIP (46)
+ if (lookahead == ']')
+ ADVANCE (72);
+ if (lookahead == 'd')
+ ADVANCE (54);
+ if (lookahead == 'p')
+ ADVANCE (50);
+ if (lookahead == 'u')
+ ADVANCE (56);
+ if (lookahead == 'w')
+ ADVANCE (52);
+ if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r'
+ || lookahead == ' ')
+ SKIP (0)
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (60);
+ END_STATE ();
+ case 1:
+ if (lookahead == '\n')
+ SKIP (14)
+ END_STATE ();
+ case 2:
+ if (lookahead == '\n')
+ SKIP (14)
+ if (lookahead == '\r')
+ SKIP (1)
+ END_STATE ();
+ case 3:
+ if (lookahead == '\n')
+ SKIP (16)
+ END_STATE ();
+ case 4:
+ if (lookahead == '\n')
+ SKIP (16)
+ if (lookahead == '\r')
+ SKIP (3)
+ END_STATE ();
+ case 5:
+ if (lookahead == '\n')
+ SKIP (7)
+ END_STATE ();
+ case 6:
+ if (lookahead == '\n')
+ SKIP (7)
+ if (lookahead == '\r')
+ SKIP (5)
+ END_STATE ();
+ case 7:
+ if (lookahead == '\n')
+ ADVANCE (48);
+ if (lookahead == '\\')
+ SKIP (6)
+ if (lookahead == '\t' || lookahead == '\r' || lookahead == ' ')
+ SKIP (7)
+ END_STATE ();
+ case 8:
+ if (lookahead == ' ')
+ ADVANCE (13);
+ END_STATE ();
+ case 9:
+ if (lookahead == ' ')
+ ADVANCE (39);
+ END_STATE ();
+ case 10:
+ if (lookahead == ' ')
+ ADVANCE (40);
+ END_STATE ();
+ case 11:
+ if (lookahead == '-')
+ ADVANCE (43);
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (24);
+ END_STATE ();
+ case 12:
+ if (lookahead == '-')
+ ADVANCE (44);
+ END_STATE ();
+ case 13:
+ if (lookahead == 'D')
+ ADVANCE (38);
+ END_STATE ();
+ case 14:
+ if (lookahead == 'N')
+ ADVANCE (30);
+ if (lookahead == '\\')
+ SKIP (2)
+ if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r'
+ || lookahead == ' ')
+ SKIP (14)
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (23);
+ END_STATE ();
+ case 15:
+ if (lookahead == '\\')
+ ADVANCE (63);
+ if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r'
+ || lookahead == ' ')
+ ADVANCE (65);
+ if (lookahead != 0 && lookahead != '"')
+ ADVANCE (66);
+ END_STATE ();
+ case 16:
+ if (lookahead == '\\')
+ SKIP (4)
+ if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r'
+ || lookahead == ' ')
+ SKIP (16)
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (62);
+ END_STATE ();
+ case 17:
+ if (lookahead == 'a')
+ ADVANCE (36);
+ END_STATE ();
+ case 18:
+ if (lookahead == 'c')
+ ADVANCE (26);
+ END_STATE ();
+ case 19:
+ if (lookahead == 'c')
+ ADVANCE (20);
+ END_STATE ();
+ case 20:
+ if (lookahead == 'h')
+ ADVANCE (51);
+ END_STATE ();
+ case 21:
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (11);
+ END_STATE ();
+ case 22:
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (21);
+ END_STATE ();
+ case 23:
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (22);
+ END_STATE ();
+ case 24:
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (24);
+ END_STATE ();
+ case 25:
+ if (lookahead == 'i')
+ ADVANCE (32);
+ END_STATE ();
+ case 26:
+ if (lookahead == 'k')
+ ADVANCE (49);
+ END_STATE ();
+ case 27:
+ if (lookahead == 'l')
+ ADVANCE (55);
+ END_STATE ();
+ case 28:
+ if (lookahead == 'm')
+ ADVANCE (71);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (28);
+ END_STATE ();
+ case 29:
+ if (lookahead == 'n')
+ ADVANCE (69);
+ END_STATE ();
+ case 30:
+ if (lookahead == 'o')
+ ADVANCE (8);
+ END_STATE ();
+ case 31:
+ if (lookahead == 'o')
+ ADVANCE (33);
+ END_STATE ();
+ case 32:
+ if (lookahead == 'o')
+ ADVANCE (29);
+ END_STATE ();
+ case 33:
+ if (lookahead == 'p')
+ ADVANCE (53);
+ END_STATE ();
+ case 34:
+ if (lookahead == 'r')
+ ADVANCE (17);
+ END_STATE ();
+ case 35:
+ if (lookahead == 's')
+ ADVANCE (70);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (35);
+ END_STATE ();
+ case 36:
+ if (lookahead == 't')
+ ADVANCE (25);
+ END_STATE ();
+ case 37:
+ if (lookahead == 't')
+ ADVANCE (19);
+ END_STATE ();
+ case 38:
+ if (lookahead == 'u')
+ ADVANCE (34);
+ END_STATE ();
+ case 39:
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (28);
+ END_STATE ();
+ case 40:
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (35);
+ END_STATE ();
+ case 41:
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (12);
+ END_STATE ();
+ case 42:
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (67);
+ END_STATE ();
+ case 43:
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (41);
+ END_STATE ();
+ case 44:
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (42);
+ END_STATE ();
+ case 45:
+ if (eof)
+ ADVANCE (47);
+ if (lookahead == '\n')
+ SKIP (0)
+ END_STATE ();
+ case 46:
+ if (eof)
+ ADVANCE (47);
+ if (lookahead == '\n')
+ SKIP (0)
+ if (lookahead == '\r')
+ SKIP (45)
+ END_STATE ();
+ case 47:
+ ACCEPT_TOKEN (ts_builtin_sym_end);
+ END_STATE ();
+ case 48:
+ ACCEPT_TOKEN (anon_sym_LF);
+ if (lookahead == '\n')
+ ADVANCE (48);
+ END_STATE ();
+ case 49:
+ ACCEPT_TOKEN (anon_sym_pick);
+ END_STATE ();
+ case 50:
+ ACCEPT_TOKEN (anon_sym_p);
+ if (lookahead == 'i')
+ ADVANCE (18);
+ END_STATE ();
+ case 51:
+ ACCEPT_TOKEN (anon_sym_watch);
+ END_STATE ();
+ case 52:
+ ACCEPT_TOKEN (anon_sym_w);
+ if (lookahead == 'a')
+ ADVANCE (37);
+ END_STATE ();
+ case 53:
+ ACCEPT_TOKEN (anon_sym_drop);
+ END_STATE ();
+ case 54:
+ ACCEPT_TOKEN (anon_sym_d);
+ if (lookahead == 'r')
+ ADVANCE (31);
+ END_STATE ();
+ case 55:
+ ACCEPT_TOKEN (anon_sym_url);
+ END_STATE ();
+ case 56:
+ ACCEPT_TOKEN (anon_sym_u);
+ if (lookahead == 'r')
+ ADVANCE (27);
+ END_STATE ();
+ case 57:
+ ACCEPT_TOKEN (sym_id);
+ if (lookahead == '-')
+ ADVANCE (43);
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (61);
+ END_STATE ();
+ case 58:
+ ACCEPT_TOKEN (sym_id);
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (57);
+ END_STATE ();
+ case 59:
+ ACCEPT_TOKEN (sym_id);
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (58);
+ END_STATE ();
+ case 60:
+ ACCEPT_TOKEN (sym_id);
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (59);
+ END_STATE ();
+ case 61:
+ ACCEPT_TOKEN (sym_id);
+ if (lookahead == 'h')
+ ADVANCE (9);
+ if (lookahead == 'm')
+ ADVANCE (10);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (61);
+ END_STATE ();
+ case 62:
+ ACCEPT_TOKEN (sym_id);
+ if (('0' <= lookahead && lookahead <= '9'))
+ ADVANCE (62);
+ END_STATE ();
+ case 63:
+ ACCEPT_TOKEN (aux_sym_title_token1);
+ if (lookahead == '\n')
+ ADVANCE (65);
+ if (lookahead == '\r')
+ ADVANCE (64);
+ if (lookahead != 0 && lookahead != '"')
+ ADVANCE (66);
+ END_STATE ();
+ case 64:
+ ACCEPT_TOKEN (aux_sym_title_token1);
+ if (lookahead == '\n')
+ ADVANCE (65);
+ if (lookahead != 0 && lookahead != '"')
+ ADVANCE (66);
+ END_STATE ();
+ case 65:
+ ACCEPT_TOKEN (aux_sym_title_token1);
+ if (lookahead == '\\')
+ ADVANCE (63);
+ if (lookahead == '\t' || lookahead == '\n' || lookahead == '\r'
+ || lookahead == ' ')
+ ADVANCE (65);
+ if (lookahead != 0 && lookahead != '"')
+ ADVANCE (66);
+ END_STATE ();
+ case 66:
+ ACCEPT_TOKEN (aux_sym_title_token1);
+ if (lookahead != 0 && lookahead != '"')
+ ADVANCE (66);
+ END_STATE ();
+ case 67:
+ ACCEPT_TOKEN (aux_sym_date_token1);
+ END_STATE ();
+ case 68:
+ ACCEPT_TOKEN (anon_sym_LBRACK);
+ END_STATE ();
+ case 69:
+ ACCEPT_TOKEN (anon_sym_NoDuration);
+ END_STATE ();
+ case 70:
+ ACCEPT_TOKEN (aux_sym_duration_token1);
+ END_STATE ();
+ case 71:
+ ACCEPT_TOKEN (aux_sym_duration_token2);
+ END_STATE ();
+ case 72:
+ ACCEPT_TOKEN (anon_sym_RBRACK);
+ END_STATE ();
+ case 73:
+ ACCEPT_TOKEN (sym_comment);
+ if (lookahead != 0 && lookahead != '\n')
+ ADVANCE (73);
+ END_STATE ();
+ case 74:
+ ACCEPT_TOKEN (sym_quote);
+ END_STATE ();
+ default:
+ return false;
+ }
+}
+
+static const TSLexMode ts_lex_modes[STATE_COUNT] = {
+ [0] = { .lex_state = 0 }, [1] = { .lex_state = 0 },
+ [2] = { .lex_state = 0 }, [3] = { .lex_state = 0 },
+ [4] = { .lex_state = 0 }, [5] = { .lex_state = 0 },
+ [6] = { .lex_state = 0 }, [7] = { .lex_state = 0 },
+ [8] = { .lex_state = 14 }, [9] = { .lex_state = 0 },
+ [10] = { .lex_state = 0 }, [11] = { .lex_state = 0 },
+ [12] = { .lex_state = 0 }, [13] = { .lex_state = 0 },
+ [14] = { .lex_state = 0 }, [15] = { .lex_state = 0 },
+ [16] = { .lex_state = 15 }, [17] = { .lex_state = 0 },
+ [18] = { .lex_state = 0 }, [19] = { .lex_state = 16 },
+ [20] = { .lex_state = 0 }, [21] = { .lex_state = 7 },
+ [22] = { .lex_state = 15 }, [23] = { .lex_state = 14 },
+ [24] = { .lex_state = 0 }, [25] = { .lex_state = 15 },
+ [26] = { .lex_state = 16 }, [27] = { .lex_state = 0 },
+ [28] = { .lex_state = 7 }, [29] = { .lex_state = 0 },
+ [30] = { .lex_state = 0 },
+};
+
+static const uint16_t ts_parse_table[LARGE_STATE_COUNT][SYMBOL_COUNT] = {
+ [0] = {
+ [ts_builtin_sym_end] = ACTIONS(1),
+ [anon_sym_pick] = ACTIONS(1),
+ [anon_sym_p] = ACTIONS(1),
+ [anon_sym_watch] = ACTIONS(1),
+ [anon_sym_w] = ACTIONS(1),
+ [anon_sym_drop] = ACTIONS(1),
+ [anon_sym_d] = ACTIONS(1),
+ [anon_sym_url] = ACTIONS(1),
+ [anon_sym_u] = ACTIONS(1),
+ [sym_id] = ACTIONS(1),
+ [aux_sym_date_token1] = ACTIONS(1),
+ [anon_sym_LBRACK] = ACTIONS(1),
+ [anon_sym_NoDuration] = ACTIONS(1),
+ [aux_sym_duration_token1] = ACTIONS(1),
+ [aux_sym_duration_token2] = ACTIONS(1),
+ [anon_sym_RBRACK] = ACTIONS(1),
+ [sym_comment] = ACTIONS(1),
+ [sym_quote] = ACTIONS(1),
+ },
+ [1] = {
+ [sym_source_file] = STATE(29),
+ [sym_line] = STATE(2),
+ [sym_command] = STATE(26),
+ [aux_sym_source_file_repeat1] = STATE(2),
+ [ts_builtin_sym_end] = ACTIONS(3),
+ [anon_sym_pick] = ACTIONS(5),
+ [anon_sym_p] = ACTIONS(7),
+ [anon_sym_watch] = ACTIONS(5),
+ [anon_sym_w] = ACTIONS(7),
+ [anon_sym_drop] = ACTIONS(5),
+ [anon_sym_d] = ACTIONS(7),
+ [anon_sym_url] = ACTIONS(5),
+ [anon_sym_u] = ACTIONS(7),
+ [sym_comment] = ACTIONS(9),
+ },
+};
+
+static const uint16_t ts_small_parse_table[] = {
+ [0] = 6,
+ ACTIONS (11),
+ 1,
+ ts_builtin_sym_end,
+ ACTIONS (13),
+ 1,
+ sym_comment,
+ STATE (26),
+ 1,
+ sym_command,
+ STATE (3),
+ 2,
+ sym_line,
+ aux_sym_source_file_repeat1,
+ ACTIONS (5),
+ 4,
+ anon_sym_pick,
+ anon_sym_watch,
+ anon_sym_drop,
+ anon_sym_url,
+ ACTIONS (7),
+ 4,
+ anon_sym_p,
+ anon_sym_w,
+ anon_sym_d,
+ anon_sym_u,
+ [26] = 6,
+ ACTIONS (15),
+ 1,
+ ts_builtin_sym_end,
+ ACTIONS (23),
+ 1,
+ sym_comment,
+ STATE (26),
+ 1,
+ sym_command,
+ STATE (3),
+ 2,
+ sym_line,
+ aux_sym_source_file_repeat1,
+ ACTIONS (17),
+ 4,
+ anon_sym_pick,
+ anon_sym_watch,
+ anon_sym_drop,
+ anon_sym_url,
+ ACTIONS (20),
+ 4,
+ anon_sym_p,
+ anon_sym_w,
+ anon_sym_d,
+ anon_sym_u,
+ [52] = 2,
+ ACTIONS (28),
+ 4,
+ anon_sym_p,
+ anon_sym_w,
+ anon_sym_d,
+ anon_sym_u,
+ ACTIONS (26),
+ 6,
+ ts_builtin_sym_end,
+ anon_sym_pick,
+ anon_sym_watch,
+ anon_sym_drop,
+ anon_sym_url,
+ sym_comment,
+ [67] = 3,
+ ACTIONS (30),
+ 1,
+ sym_quote,
+ STATE (21),
+ 1,
+ sym_url,
+ STATE (22),
+ 1,
+ sym__q,
+ [77] = 3,
+ ACTIONS (32),
+ 1,
+ sym_quote,
+ STATE (7),
+ 1,
+ sym_title,
+ STATE (25),
+ 1,
+ sym__q,
+ [87] = 3,
+ ACTIONS (34),
+ 1,
+ sym_quote,
+ STATE (9),
+ 1,
+ sym_date,
+ STATE (23),
+ 1,
+ sym__q,
+ [97] = 1,
+ ACTIONS (36),
+ 3,
+ anon_sym_NoDuration,
+ aux_sym_duration_token1,
+ aux_sym_duration_token2,
+ [103] = 3,
+ ACTIONS (38),
+ 1,
+ sym_quote,
+ STATE (10),
+ 1,
+ sym_author,
+ STATE (16),
+ 1,
+ sym__q,
+ [113] = 3,
+ ACTIONS (40),
+ 1,
+ sym_quote,
+ STATE (5),
+ 1,
+ sym_duration,
+ STATE (18),
+ 1,
+ sym__q,
+ [123] = 2,
+ ACTIONS (42),
+ 1,
+ sym_quote,
+ STATE (30),
+ 1,
+ sym__q,
+ [130] = 2,
+ ACTIONS (44),
+ 1,
+ sym_quote,
+ STATE (28),
+ 1,
+ sym__q,
+ [137] = 2,
+ ACTIONS (46),
+ 1,
+ sym_quote,
+ STATE (24),
+ 1,
+ sym__q,
+ [144] = 2,
+ ACTIONS (48),
+ 1,
+ sym_quote,
+ STATE (17),
+ 1,
+ sym__q,
+ [151] = 2,
+ ACTIONS (50),
+ 1,
+ sym_quote,
+ STATE (20),
+ 1,
+ sym__q,
+ [158] = 1,
+ ACTIONS (52),
+ 1,
+ aux_sym_title_token1,
+ [162] = 1,
+ ACTIONS (54),
+ 1,
+ sym_quote,
+ [166] = 1,
+ ACTIONS (56),
+ 1,
+ anon_sym_LBRACK,
+ [170] = 1,
+ ACTIONS (58),
+ 1,
+ sym_id,
+ [174] = 1,
+ ACTIONS (60),
+ 1,
+ sym_quote,
+ [178] = 1,
+ ACTIONS (62),
+ 1,
+ anon_sym_LF,
+ [182] = 1,
+ ACTIONS (64),
+ 1,
+ aux_sym_title_token1,
+ [186] = 1,
+ ACTIONS (66),
+ 1,
+ aux_sym_date_token1,
+ [190] = 1,
+ ACTIONS (68),
+ 1,
+ sym_quote,
+ [194] = 1,
+ ACTIONS (70),
+ 1,
+ aux_sym_title_token1,
+ [198] = 1,
+ ACTIONS (72),
+ 1,
+ sym_id,
+ [202] = 1,
+ ACTIONS (74),
+ 1,
+ anon_sym_RBRACK,
+ [206] = 1,
+ ACTIONS (76),
+ 1,
+ anon_sym_LF,
+ [210] = 1,
+ ACTIONS (78),
+ 1,
+ ts_builtin_sym_end,
+ [214] = 1,
+ ACTIONS (80),
+ 1,
+ sym_quote,
+};
+
+static const uint32_t ts_small_parse_table_map[] = {
+ [SMALL_STATE (2)] = 0, [SMALL_STATE (3)] = 26, [SMALL_STATE (4)] = 52,
+ [SMALL_STATE (5)] = 67, [SMALL_STATE (6)] = 77, [SMALL_STATE (7)] = 87,
+ [SMALL_STATE (8)] = 97, [SMALL_STATE (9)] = 103, [SMALL_STATE (10)] = 113,
+ [SMALL_STATE (11)] = 123, [SMALL_STATE (12)] = 130, [SMALL_STATE (13)] = 137,
+ [SMALL_STATE (14)] = 144, [SMALL_STATE (15)] = 151, [SMALL_STATE (16)] = 158,
+ [SMALL_STATE (17)] = 162, [SMALL_STATE (18)] = 166, [SMALL_STATE (19)] = 170,
+ [SMALL_STATE (20)] = 174, [SMALL_STATE (21)] = 178, [SMALL_STATE (22)] = 182,
+ [SMALL_STATE (23)] = 186, [SMALL_STATE (24)] = 190, [SMALL_STATE (25)] = 194,
+ [SMALL_STATE (26)] = 198, [SMALL_STATE (27)] = 202, [SMALL_STATE (28)] = 206,
+ [SMALL_STATE (29)] = 210, [SMALL_STATE (30)] = 214,
+};
+
+static const TSParseActionEntry ts_parse_actions[] = {
+ [0] = { .entry = { .count = 0, .reusable = false } },
+ [1] = { .entry = { .count = 1, .reusable = false } },
+ RECOVER (),
+ [3] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (sym_source_file, 0),
+ [5] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (19),
+ [7] = { .entry = { .count = 1, .reusable = false } },
+ SHIFT (19),
+ [9] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (2),
+ [11] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (sym_source_file, 1),
+ [13] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (3),
+ [15] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (aux_sym_source_file_repeat1, 2),
+ [17] = { .entry = { .count = 2, .reusable = true } },
+ REDUCE (aux_sym_source_file_repeat1, 2),
+ SHIFT_REPEAT (19),
+ [20] = { .entry = { .count = 2, .reusable = false } },
+ REDUCE (aux_sym_source_file_repeat1, 2),
+ SHIFT_REPEAT (19),
+ [23] = { .entry = { .count = 2, .reusable = true } },
+ REDUCE (aux_sym_source_file_repeat1, 2),
+ SHIFT_REPEAT (3),
+ [26] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (sym_line, 8),
+ [28] = { .entry = { .count = 1, .reusable = false } },
+ REDUCE (sym_line, 8),
+ [30] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (22),
+ [32] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (25),
+ [34] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (23),
+ [36] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (27),
+ [38] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (16),
+ [40] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (18),
+ [42] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (30),
+ [44] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (28),
+ [46] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (24),
+ [48] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (17),
+ [50] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (20),
+ [52] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (13),
+ [54] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (sym_title, 3),
+ [56] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (8),
+ [58] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (sym_command, 1),
+ [60] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (sym_date, 3),
+ [62] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (4),
+ [64] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (12),
+ [66] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (15),
+ [68] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (sym_author, 3),
+ [70] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (14),
+ [72] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (6),
+ [74] = { .entry = { .count = 1, .reusable = true } },
+ SHIFT (11),
+ [76] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (sym_url, 3),
+ [78] = { .entry = { .count = 1, .reusable = true } },
+ ACCEPT_INPUT (),
+ [80] = { .entry = { .count = 1, .reusable = true } },
+ REDUCE (sym_duration, 5),
+};
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+#ifdef _WIN32
+#define extern __declspec (dllexport)
+#endif
+
+ extern const TSLanguage *
+ tree_sitter_yts (void)
+ {
+ static const TSLanguage language = {
+ .version = LANGUAGE_VERSION,
+ .symbol_count = SYMBOL_COUNT,
+ .alias_count = ALIAS_COUNT,
+ .token_count = TOKEN_COUNT,
+ .external_token_count = EXTERNAL_TOKEN_COUNT,
+ .state_count = STATE_COUNT,
+ .large_state_count = LARGE_STATE_COUNT,
+ .production_id_count = PRODUCTION_ID_COUNT,
+ .field_count = FIELD_COUNT,
+ .max_alias_sequence_length = MAX_ALIAS_SEQUENCE_LENGTH,
+ .parse_table = &ts_parse_table[0][0],
+ .small_parse_table = ts_small_parse_table,
+ .small_parse_table_map = ts_small_parse_table_map,
+ .parse_actions = ts_parse_actions,
+ .symbol_names = ts_symbol_names,
+ .symbol_metadata = ts_symbol_metadata,
+ .public_symbol_map = ts_symbol_map,
+ .alias_map = ts_non_terminal_alias_map,
+ .alias_sequences = &ts_alias_sequences[0][0],
+ .lex_modes = ts_lex_modes,
+ .lex_fn = ts_lex,
+ .primary_state_ids = ts_primary_state_ids,
+ };
+ return &language;
+ }
+#ifdef __cplusplus
+}
+#endif
diff --git a/pkgs/sources/tree-sitter-yts/src/tree_sitter/parser.h b/pkgs/sources/tree-sitter-yts/src/tree_sitter/parser.h
new file mode 100644
index 00000000..972913cf
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/src/tree_sitter/parser.h
@@ -0,0 +1,241 @@
+#ifndef TREE_SITTER_PARSER_H_
+#define TREE_SITTER_PARSER_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#define ts_builtin_sym_error ((TSSymbol)-1)
+#define ts_builtin_sym_end 0
+#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
+
+ typedef uint16_t TSStateId;
+
+#ifndef TREE_SITTER_API_H_
+ typedef uint16_t TSSymbol;
+ typedef uint16_t TSFieldId;
+ typedef struct TSLanguage TSLanguage;
+#endif
+
+ typedef struct
+ {
+ TSFieldId field_id;
+ uint8_t child_index;
+ bool inherited;
+ } TSFieldMapEntry;
+
+ typedef struct
+ {
+ uint16_t index;
+ uint16_t length;
+ } TSFieldMapSlice;
+
+ typedef struct
+ {
+ bool visible;
+ bool named;
+ bool supertype;
+ } TSSymbolMetadata;
+
+ typedef struct TSLexer TSLexer;
+
+ struct TSLexer
+ {
+ int32_t lookahead;
+ TSSymbol result_symbol;
+ void (*advance) (TSLexer *, bool);
+ void (*mark_end) (TSLexer *);
+ uint32_t (*get_column) (TSLexer *);
+ bool (*is_at_included_range_start) (const TSLexer *);
+ bool (*eof) (const TSLexer *);
+ };
+
+ typedef enum
+ {
+ TSParseActionTypeShift,
+ TSParseActionTypeReduce,
+ TSParseActionTypeAccept,
+ TSParseActionTypeRecover,
+ } TSParseActionType;
+
+ typedef union
+ {
+ struct
+ {
+ uint8_t type;
+ TSStateId state;
+ bool extra;
+ bool repetition;
+ } shift;
+ struct
+ {
+ uint8_t type;
+ uint8_t child_count;
+ TSSymbol symbol;
+ int16_t dynamic_precedence;
+ uint16_t production_id;
+ } reduce;
+ uint8_t type;
+ } TSParseAction;
+
+ typedef struct
+ {
+ uint16_t lex_state;
+ uint16_t external_lex_state;
+ } TSLexMode;
+
+ typedef union
+ {
+ TSParseAction action;
+ struct
+ {
+ uint8_t count;
+ bool reusable;
+ } entry;
+ } TSParseActionEntry;
+
+ struct TSLanguage
+ {
+ uint32_t version;
+ uint32_t symbol_count;
+ uint32_t alias_count;
+ uint32_t token_count;
+ uint32_t external_token_count;
+ uint32_t state_count;
+ uint32_t large_state_count;
+ uint32_t production_id_count;
+ uint32_t field_count;
+ uint16_t max_alias_sequence_length;
+ const uint16_t *parse_table;
+ const uint16_t *small_parse_table;
+ const uint32_t *small_parse_table_map;
+ const TSParseActionEntry *parse_actions;
+ const char *const *symbol_names;
+ const char *const *field_names;
+ const TSFieldMapSlice *field_map_slices;
+ const TSFieldMapEntry *field_map_entries;
+ const TSSymbolMetadata *symbol_metadata;
+ const TSSymbol *public_symbol_map;
+ const uint16_t *alias_map;
+ const TSSymbol *alias_sequences;
+ const TSLexMode *lex_modes;
+ bool (*lex_fn) (TSLexer *, TSStateId);
+ bool (*keyword_lex_fn) (TSLexer *, TSStateId);
+ TSSymbol keyword_capture_token;
+ struct
+ {
+ const bool *states;
+ const TSSymbol *symbol_map;
+ void *(*create) (void);
+ void (*destroy) (void *);
+ bool (*scan) (void *, TSLexer *, const bool *symbol_whitelist);
+ unsigned (*serialize) (void *, char *);
+ void (*deserialize) (void *, const char *, unsigned);
+ } external_scanner;
+ const TSStateId *primary_state_ids;
+ };
+
+ /*
+ * Lexer Macros
+ */
+
+#define START_LEXER() \
+ bool result = false; \
+ bool skip = false; \
+ bool eof = false; \
+ int32_t lookahead; \
+ goto start; \
+ next_state: \
+ lexer->advance (lexer, skip); \
+ start: \
+ skip = false; \
+ lookahead = lexer->lookahead;
+
+#define ADVANCE(state_value) \
+ { \
+ state = state_value; \
+ goto next_state; \
+ }
+
+#define SKIP(state_value) \
+ { \
+ skip = true; \
+ state = state_value; \
+ goto next_state; \
+ }
+
+#define ACCEPT_TOKEN(symbol_value) \
+ result = true; \
+ lexer->result_symbol = symbol_value; \
+ lexer->mark_end (lexer);
+
+#define END_STATE() return result;
+
+ /*
+ * Parse Table Macros
+ */
+
+#define SMALL_STATE(id) id - LARGE_STATE_COUNT
+
+#define STATE(id) id
+
+#define ACTIONS(id) id
+
+#define SHIFT(state_value) \
+ { \
+ { \
+ .shift = {.type = TSParseActionTypeShift, .state = state_value } \
+ } \
+ }
+
+#define SHIFT_REPEAT(state_value) \
+ { \
+ { \
+ .shift \
+ = {.type = TSParseActionTypeShift, \
+ .state = state_value, \
+ .repetition = true } \
+ } \
+ }
+
+#define SHIFT_EXTRA() \
+ { \
+ { \
+ .shift = {.type = TSParseActionTypeShift, .extra = true } \
+ } \
+ }
+
+#define REDUCE(symbol_val, child_count_val, ...) \
+ { \
+ { \
+ .reduce = { .type = TSParseActionTypeReduce, \
+ .symbol = symbol_val, \
+ .child_count = child_count_val, \
+ __VA_ARGS__ }, \
+ } \
+ }
+
+#define RECOVER() \
+ { \
+ { \
+ .type = TSParseActionTypeRecover \
+ } \
+ }
+
+#define ACCEPT_INPUT() \
+ { \
+ { \
+ .type = TSParseActionTypeAccept \
+ } \
+ }
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // TREE_SITTER_PARSER_H_
diff --git a/pkgs/sources/tree-sitter-yts/treefmt.toml b/pkgs/sources/tree-sitter-yts/treefmt.toml
new file mode 100644
index 00000000..3d604b40
--- /dev/null
+++ b/pkgs/sources/tree-sitter-yts/treefmt.toml
@@ -0,0 +1,35 @@
+[formatter.nix]
+command = "nixpkgs-fmt"
+includes = ["*.nix"]
+excludes = ["test/**.nix"]
+
+[formatter.prettier]
+command = "prettier"
+options = ["--write"]
+includes = [
+ "*.css",
+ "*.html",
+ "*.js",
+ "*.json",
+ "*.jsx",
+ "*.md",
+ "*.mdx",
+ "*.scss",
+ "*.ts",
+]
+excludes = ["src/**.json"]
+
+[formatter.rust]
+command = "rustfmt"
+options = ["--edition", "2018"]
+includes = ["*.rs"]
+
+[formatter.c]
+command = "clang-format"
+options = ["-i"]
+includes = ["*.c", "*.cpp", "*.cc", "*.h", "*.hpp"]
+excludes = [
+ "bindings/node/binding.cc",
+ "src/parser.c",
+ "src/tree_sitter/parser.h",
+]
diff --git a/pkgs/sources/update_pkgs.sh b/pkgs/sources/update_pkgs.sh
new file mode 100755
index 00000000..be1573c6
--- /dev/null
+++ b/pkgs/sources/update_pkgs.sh
@@ -0,0 +1,25 @@
+#!/usr/bin/env sh
+
+die() {
+ printf "\033[31;1mError: \033[0m%s" "$1"
+ exit 1
+}
+
+cd "$(dirname "$0")" || die "Bug: run with false dirname ('$0')!"
+
+fd . --type directory --max-depth 1 | while read -r dir; do
+ cd "$dir" || die "Dir '$dir' does not exist"
+
+ if [ -f update.sh ]; then
+ printf "\033[34;1m> \033[0m\033[34;1m%s\033[0m\n" "Running '${dir}update.sh'"
+
+ [ -f flake.nix ] && nix flake update
+
+ direnv allow
+ eval "$(direnv export bash 2>/dev/null)"
+ ./update.sh "$@"
+ fi
+ cd - >/dev/null || die "Bug: Last dir does not exist"
+done
+
+# vim: ft=sh
diff --git a/pkgs/sources/update_vim_plugins/.envrc b/pkgs/sources/update_vim_plugins/.envrc
new file mode 100644
index 00000000..3550a30f
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/pkgs/sources/update_vim_plugins/check-duplicates.sh b/pkgs/sources/update_vim_plugins/check-duplicates.sh
new file mode 100755
index 00000000..781b8aeb
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/check-duplicates.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+plugins="$(grep -E "^ [a-zA-Z-]+ =" ./pkgs/vim-plugins.nix | sed -E 's/^ ([a-zA-Z-]+) =.*$/\1/' | sort)"
+count=$(echo "$plugins" | uniq -d | wc -l)
+
+echo "duplicates count: $count"
+
+if [ "$count" -gt 0 ]; then
+ filtered_plugins=$(echo "$plugins" | uniq -d)
+
+ if [ "$1" == "check-only" ]; then
+ echo "$filtered_plugins"
+ exit 1
+ else
+ known_issues=$(gh issue list --state "open" --label "bot" --json "body" | jq -r ".[].body")
+
+ echo "known_issues: $known_issues"
+
+ # iterate over plugins we found missing and
+ # compare them to all open issues.
+ # We no matching issue was found, we create a new one
+ for f in $filtered_plugins; do # do not add " " here. It would break the plugin
+ found=false
+
+ for k in $known_issues; do
+ if [[ $f == "$k" ]]; then
+ found=true
+ break
+ fi
+ done
+
+ # test if matching issue was found
+ if ! $found; then
+ echo "Did not find an issue for $f. Creating a new one ..."
+ gh issue create --title "Detected broken plugin: $f" --label "bot" --body "$f"
+ else
+ echo "Issue for $f already exists"
+ fi
+ done
+ fi
+else
+ echo "No duplicates found"
+fi
diff --git a/pkgs/sources/update_vim_plugins/default.nix b/pkgs/sources/update_vim_plugins/default.nix
new file mode 100644
index 00000000..7f0b3f0d
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/default.nix
@@ -0,0 +1,17 @@
+[
+ (
+ final: prev: {
+ update-vim-plugins = import ./package.nix {
+ inherit
+ (prev)
+ python3
+ # dependencies
+
+ nix
+ alejandra
+ nix-prefetch-git
+ ;
+ };
+ }
+ )
+]
diff --git a/pkgs/sources/update_vim_plugins/flake.lock b/pkgs/sources/update_vim_plugins/flake.lock
new file mode 100644
index 00000000..50494465
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1715087517,
+ "narHash": "sha256-CLU5Tsg24Ke4+7sH8azHWXKd0CFd4mhLWfhYgUiDBpQ=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "b211b392b8486ee79df6cdfb1157ad2133427a29",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/pkgs/sources/update_vim_plugins/flake.nix b/pkgs/sources/update_vim_plugins/flake.nix
new file mode 100644
index 00000000..ef440af0
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/flake.nix
@@ -0,0 +1,24 @@
+{
+ description = "update_vim_plugins";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ flake-utils,
+ }: (flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in {
+ devShells.default = pkgs.mkShell {
+ packages = [
+ pkgs.python3
+ pkgs.poetry
+ ];
+ };
+ }));
+}
diff --git a/pkgs/sources/update_vim_plugins/package.nix b/pkgs/sources/update_vim_plugins/package.nix
new file mode 100644
index 00000000..e74a29b1
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/package.nix
@@ -0,0 +1,47 @@
+{
+ python3,
+ # dependencies
+ nix,
+ alejandra,
+ nix-prefetch-git,
+}:
+python3.pkgs.buildPythonApplication {
+ pname = "update-vim-plugins";
+ version = "0.1.0";
+ format = "pyproject";
+
+ src = ./.;
+
+ # NOTE: The test are not really meant to work <2023-12-09>
+ doCheck = false;
+
+ nativeBuildInputs = [
+ python3.pkgs.poetry-core
+ ];
+ buildInputs = [
+ alejandra
+ nix-prefetch-git
+ nix
+ ];
+ propagatedBuildInputs = with python3.pkgs; [
+ requests
+ cleo
+ jsonpickle
+ dateparser
+ ];
+ nativeCheckInputs = with python3.pkgs; [
+ pytestCheckHook
+
+ pytest-cov
+ pytest-mock
+ ];
+ pytestFlagsArray = [
+ "--cov"
+ "update_vim_plugins"
+ "--cov-report"
+ "term-missing:skip-covered"
+ "--cov-fail-under"
+ "50"
+ "update_vim_plugins/tests"
+ ];
+}
diff --git a/pkgs/sources/update_vim_plugins/poetry.lock b/pkgs/sources/update_vim_plugins/poetry.lock
new file mode 100644
index 00000000..f4764b42
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/poetry.lock
@@ -0,0 +1,680 @@
+# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
+
+[[package]]
+name = "certifi"
+version = "2024.2.2"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
+ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.3.2"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"},
+ {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"},
+ {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"},
+ {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"},
+ {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"},
+ {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"},
+ {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"},
+ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"},
+]
+
+[[package]]
+name = "cleo"
+version = "2.1.0"
+description = "Cleo allows you to create beautiful and testable command-line interfaces."
+optional = false
+python-versions = ">=3.7,<4.0"
+files = [
+ {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"},
+ {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"},
+]
+
+[package.dependencies]
+crashtest = ">=0.4.1,<0.5.0"
+rapidfuzz = ">=3.0.0,<4.0.0"
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "coverage"
+version = "7.5.1"
+description = "Code coverage measurement for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "coverage-7.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0884920835a033b78d1c73b6d3bbcda8161a900f38a488829a83982925f6c2e"},
+ {file = "coverage-7.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:39afcd3d4339329c5f58de48a52f6e4e50f6578dd6099961cf22228feb25f38f"},
+ {file = "coverage-7.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a7b0ceee8147444347da6a66be737c9d78f3353b0681715b668b72e79203e4a"},
+ {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a9ca3f2fae0088c3c71d743d85404cec8df9be818a005ea065495bedc33da35"},
+ {file = "coverage-7.5.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd215c0c7d7aab005221608a3c2b46f58c0285a819565887ee0b718c052aa4e"},
+ {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4bf0655ab60d754491004a5efd7f9cccefcc1081a74c9ef2da4735d6ee4a6223"},
+ {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:61c4bf1ba021817de12b813338c9be9f0ad5b1e781b9b340a6d29fc13e7c1b5e"},
+ {file = "coverage-7.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:db66fc317a046556a96b453a58eced5024af4582a8dbdc0c23ca4dbc0d5b3146"},
+ {file = "coverage-7.5.1-cp310-cp310-win32.whl", hash = "sha256:b016ea6b959d3b9556cb401c55a37547135a587db0115635a443b2ce8f1c7228"},
+ {file = "coverage-7.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:df4e745a81c110e7446b1cc8131bf986157770fa405fe90e15e850aaf7619bc8"},
+ {file = "coverage-7.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:796a79f63eca8814ca3317a1ea443645c9ff0d18b188de470ed7ccd45ae79428"},
+ {file = "coverage-7.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fc84a37bfd98db31beae3c2748811a3fa72bf2007ff7902f68746d9757f3746"},
+ {file = "coverage-7.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6175d1a0559986c6ee3f7fccfc4a90ecd12ba0a383dcc2da30c2b9918d67d8a3"},
+ {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fc81d5878cd6274ce971e0a3a18a8803c3fe25457165314271cf78e3aae3aa2"},
+ {file = "coverage-7.5.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:556cf1a7cbc8028cb60e1ff0be806be2eded2daf8129b8811c63e2b9a6c43bca"},
+ {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9981706d300c18d8b220995ad22627647be11a4276721c10911e0e9fa44c83e8"},
+ {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d7fed867ee50edf1a0b4a11e8e5d0895150e572af1cd6d315d557758bfa9c057"},
+ {file = "coverage-7.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef48e2707fb320c8f139424a596f5b69955a85b178f15af261bab871873bb987"},
+ {file = "coverage-7.5.1-cp311-cp311-win32.whl", hash = "sha256:9314d5678dcc665330df5b69c1e726a0e49b27df0461c08ca12674bcc19ef136"},
+ {file = "coverage-7.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:5fa567e99765fe98f4e7d7394ce623e794d7cabb170f2ca2ac5a4174437e90dd"},
+ {file = "coverage-7.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b6cf3764c030e5338e7f61f95bd21147963cf6aa16e09d2f74f1fa52013c1206"},
+ {file = "coverage-7.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ec92012fefebee89a6b9c79bc39051a6cb3891d562b9270ab10ecfdadbc0c34"},
+ {file = "coverage-7.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16db7f26000a07efcf6aea00316f6ac57e7d9a96501e990a36f40c965ec7a95d"},
+ {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beccf7b8a10b09c4ae543582c1319c6df47d78fd732f854ac68d518ee1fb97fa"},
+ {file = "coverage-7.5.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8748731ad392d736cc9ccac03c9845b13bb07d020a33423fa5b3a36521ac6e4e"},
+ {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7352b9161b33fd0b643ccd1f21f3a3908daaddf414f1c6cb9d3a2fd618bf2572"},
+ {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7a588d39e0925f6a2bff87154752481273cdb1736270642aeb3635cb9b4cad07"},
+ {file = "coverage-7.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:68f962d9b72ce69ea8621f57551b2fa9c70509af757ee3b8105d4f51b92b41a7"},
+ {file = "coverage-7.5.1-cp312-cp312-win32.whl", hash = "sha256:f152cbf5b88aaeb836127d920dd0f5e7edff5a66f10c079157306c4343d86c19"},
+ {file = "coverage-7.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:5a5740d1fb60ddf268a3811bcd353de34eb56dc24e8f52a7f05ee513b2d4f596"},
+ {file = "coverage-7.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2213def81a50519d7cc56ed643c9e93e0247f5bbe0d1247d15fa520814a7cd7"},
+ {file = "coverage-7.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5037f8fcc2a95b1f0e80585bd9d1ec31068a9bcb157d9750a172836e98bc7a90"},
+ {file = "coverage-7.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3721c2c9e4c4953a41a26c14f4cef64330392a6d2d675c8b1db3b645e31f0e"},
+ {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca498687ca46a62ae590253fba634a1fe9836bc56f626852fb2720f334c9e4e5"},
+ {file = "coverage-7.5.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cdcbc320b14c3e5877ee79e649677cb7d89ef588852e9583e6b24c2e5072661"},
+ {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:57e0204b5b745594e5bc14b9b50006da722827f0b8c776949f1135677e88d0b8"},
+ {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8fe7502616b67b234482c3ce276ff26f39ffe88adca2acf0261df4b8454668b4"},
+ {file = "coverage-7.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9e78295f4144f9dacfed4f92935fbe1780021247c2fabf73a819b17f0ccfff8d"},
+ {file = "coverage-7.5.1-cp38-cp38-win32.whl", hash = "sha256:1434e088b41594baa71188a17533083eabf5609e8e72f16ce8c186001e6b8c41"},
+ {file = "coverage-7.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:0646599e9b139988b63704d704af8e8df7fa4cbc4a1f33df69d97f36cb0a38de"},
+ {file = "coverage-7.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4cc37def103a2725bc672f84bd939a6fe4522310503207aae4d56351644682f1"},
+ {file = "coverage-7.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fc0b4d8bfeabd25ea75e94632f5b6e047eef8adaed0c2161ada1e922e7f7cece"},
+ {file = "coverage-7.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d0a0f5e06881ecedfe6f3dd2f56dcb057b6dbeb3327fd32d4b12854df36bf26"},
+ {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9735317685ba6ec7e3754798c8871c2f49aa5e687cc794a0b1d284b2389d1bd5"},
+ {file = "coverage-7.5.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d21918e9ef11edf36764b93101e2ae8cc82aa5efdc7c5a4e9c6c35a48496d601"},
+ {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3e757949f268364b96ca894b4c342b41dc6f8f8b66c37878aacef5930db61be"},
+ {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:79afb6197e2f7f60c4824dd4b2d4c2ec5801ceb6ba9ce5d2c3080e5660d51a4f"},
+ {file = "coverage-7.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1d0d98d95dd18fe29dc66808e1accf59f037d5716f86a501fc0256455219668"},
+ {file = "coverage-7.5.1-cp39-cp39-win32.whl", hash = "sha256:1cc0fe9b0b3a8364093c53b0b4c0c2dd4bb23acbec4c9240b5f284095ccf7981"},
+ {file = "coverage-7.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:dde0070c40ea8bb3641e811c1cfbf18e265d024deff6de52c5950677a8fb1e0f"},
+ {file = "coverage-7.5.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:6537e7c10cc47c595828b8a8be04c72144725c383c4702703ff4e42e44577312"},
+ {file = "coverage-7.5.1.tar.gz", hash = "sha256:54de9ef3a9da981f7af93eafde4ede199e0846cd819eb27c88e2b712aae9708c"},
+]
+
+[package.dependencies]
+tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
+
+[package.extras]
+toml = ["tomli"]
+
+[[package]]
+name = "crashtest"
+version = "0.4.1"
+description = "Manage Python errors with ease"
+optional = false
+python-versions = ">=3.7,<4.0"
+files = [
+ {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"},
+ {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"},
+]
+
+[[package]]
+name = "dateparser"
+version = "1.2.0"
+description = "Date parsing library designed to parse dates from HTML pages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "dateparser-1.2.0-py2.py3-none-any.whl", hash = "sha256:0b21ad96534e562920a0083e97fd45fa959882d4162acc358705144520a35830"},
+ {file = "dateparser-1.2.0.tar.gz", hash = "sha256:7975b43a4222283e0ae15be7b4999d08c9a70e2d378ac87385b1ccf2cffbbb30"},
+]
+
+[package.dependencies]
+python-dateutil = "*"
+pytz = "*"
+regex = "<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27"
+tzlocal = "*"
+
+[package.extras]
+calendars = ["convertdate", "hijri-converter"]
+fasttext = ["fasttext"]
+langdetect = ["langdetect"]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.2.1"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
+ {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "idna"
+version = "3.7"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
+ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
+
+[[package]]
+name = "jsonpickle"
+version = "3.0.4"
+description = "Serialize any Python object to JSON"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "jsonpickle-3.0.4-py3-none-any.whl", hash = "sha256:04ae7567a14269579e3af66b76bda284587458d7e8a204951ca8f71a3309952e"},
+ {file = "jsonpickle-3.0.4.tar.gz", hash = "sha256:a1b14c8d6221cd8f394f2a97e735ea1d7edc927fbd135b26f2f8700657c8c62b"},
+]
+
+[package.extras]
+docs = ["furo", "rst.linker (>=1.9)", "sphinx"]
+packaging = ["build", "twine"]
+testing = ["bson", "ecdsa", "feedparser", "gmpy2", "numpy", "pandas", "pymongo", "pytest (>=3.5,!=3.7.3)", "pytest-benchmark", "pytest-benchmark[histogram]", "pytest-checkdocs (>=1.2.3)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-ruff (>=0.2.1)", "scikit-learn", "scipy", "scipy (>=1.9.3)", "simplejson", "sqlalchemy", "ujson"]
+
+[[package]]
+name = "packaging"
+version = "24.0"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
+ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
+]
+
+[[package]]
+name = "pluggy"
+version = "1.5.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+ {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pytest"
+version = "7.4.4"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
+ {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=0.12,<2.0"
+tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
+
+[package.extras]
+testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-cov"
+version = "4.1.0"
+description = "Pytest plugin for measuring coverage."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"},
+ {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"},
+]
+
+[package.dependencies]
+coverage = {version = ">=5.2.1", extras = ["toml"]}
+pytest = ">=4.6"
+
+[package.extras]
+testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
+
+[[package]]
+name = "pytest-mock"
+version = "3.14.0"
+description = "Thin-wrapper around the mock package for easier use with pytest"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"},
+ {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"},
+]
+
+[package.dependencies]
+pytest = ">=6.2.5"
+
+[package.extras]
+dev = ["pre-commit", "pytest-asyncio", "tox"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytz"
+version = "2024.1"
+description = "World timezone definitions, modern and historical"
+optional = false
+python-versions = "*"
+files = [
+ {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"},
+ {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"},
+]
+
+[[package]]
+name = "rapidfuzz"
+version = "3.9.0"
+description = "rapid fuzzy string matching"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "rapidfuzz-3.9.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bd375c4830fee11d502dd93ecadef63c137ae88e1aaa29cc15031fa66d1e0abb"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:55e2c5076f38fc1dbaacb95fa026a3e409eee6ea5ac4016d44fb30e4cad42b20"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:488f74126904db6b1bea545c2f3567ea882099f4c13f46012fe8f4b990c683df"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3f2d1ea7cd57dfcd34821e38b4924c80a31bcf8067201b1ab07386996a9faee"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b11e602987bcb4ea22b44178851f27406fca59b0836298d0beb009b504dba266"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3083512e9bf6ed2bb3d25883922974f55e21ae7f8e9f4e298634691ae1aee583"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b33c6d4b3a1190bc0b6c158c3981535f9434e8ed9ffa40cf5586d66c1819fb4b"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dcb95fde22f98e6d0480db8d6038c45fe2d18a338690e6f9bba9b82323f3469"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:08d8b49b3a4fb8572e480e73fcddc750da9cbb8696752ee12cca4bf8c8220d52"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e721842e6b601ebbeb8cc5e12c75bbdd1d9e9561ea932f2f844c418c31256e82"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7988363b3a415c5194ce1a68d380629247f8713e669ad81db7548eb156c4f365"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2d267d4c982ab7d177e994ab1f31b98ff3814f6791b90d35dda38307b9e7c989"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0bb28ab5300cf974c7eb68ea21125c493e74b35b1129e629533468b2064ae0a2"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-win32.whl", hash = "sha256:1b1f74997b6d94d66375479fa55f70b1c18e4d865d7afcd13f0785bfd40a9d3c"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c56d2efdfaa1c642029f3a7a5bb76085c5531f7a530777be98232d2ce142553c"},
+ {file = "rapidfuzz-3.9.0-cp310-cp310-win_arm64.whl", hash = "sha256:6a83128d505cac76ea560bb9afcb3f6986e14e50a6f467db9a31faef4bd9b347"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e2218d62ab63f3c5ad48eced898854d0c2c327a48f0fb02e2288d7e5332a22c8"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:36bf35df2d6c7d5820da20a6720aee34f67c15cd2daf8cf92e8141995c640c25"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:905b01a9b633394ff6bb5ebb1c5fd660e0e180c03fcf9d90199cc6ed74b87cf7"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33cfabcb7fd994938a6a08e641613ce5fe46757832edc789c6a5602e7933d6fa"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1179dcd3d150a67b8a678cd9c84f3baff7413ff13c9e8fe85e52a16c97e24c9b"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47d97e28c42f1efb7781993b67c749223f198f6653ef177a0c8f2b1c516efcaf"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28da953eb2ef9ad527e536022da7afff6ace7126cdd6f3e21ac20f8762e76d2c"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:182b4e11de928fb4834e8f8b5ecd971b5b10a86fabe8636ab65d3a9b7e0e9ca7"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c74f2da334ce597f31670db574766ddeaee5d9430c2c00e28d0fbb7f76172036"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:014ac55b03f4074f903248ded181f3000f4cdbd134e6155cbf643f0eceb4f70f"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c4ef34b2ddbf448f1d644b4ec6475df8bbe5b9d0fee173ff2e87322a151663bd"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fc02157f521af15143fae88f92ef3ddcc4e0cff05c40153a9549dc0fbdb9adb3"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ff08081c49b18ba253a99e6a47f492e6ee8019e19bbb6ddc3ed360cd3ecb2f62"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-win32.whl", hash = "sha256:b9bf90b3d96925cbf8ef44e5ee3cf39ef0c422f12d40f7a497e91febec546650"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5d5684f54d82d9b0cf0b2701e55a630527a9c3dd5ddcf7a2e726a475ac238f2"},
+ {file = "rapidfuzz-3.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:a2de844e0e971d7bd8aa41284627dbeacc90e750b90acfb016836553c7a63192"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f81fe99a69ac8ee3fd905e70c62f3af033901aeb60b69317d1d43d547b46e510"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:633b9d03fc04abc585c197104b1d0af04b1f1db1abc99f674d871224cd15557a"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab872cb57ae97c54ba7c71a9e3c9552beb57cb907c789b726895576d1ea9af6f"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdd8c15c3a14e409507fdf0c0434ec481d85c6cbeec8bdcd342a8cd1eda03825"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2444d8155d9846f206e2079bb355b85f365d9457480b0d71677a112d0a7f7128"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f83bd3d01f04061c3660742dc85143a89d49fd23eb31eccbf60ad56c4b955617"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ca799f882364e69d0872619afb19efa3652b7133c18352e4a3d86a324fb2bb1"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6993d361f28b9ef5f0fa4e79b8541c2f3507be7471b9f9cb403a255e123b31e1"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:170822a1b1719f02b58e3dce194c8ad7d4c5b39be38c0fdec603bd19c6f9cf81"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e86e39c1c1a0816ceda836e6f7bd3743b930cbc51a43a81bb433b552f203f25"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:731269812ea837e0b93d913648e404736407408e33a00b75741e8f27c590caa2"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8e5ff882d3a3d081157ceba7e0ebc7fac775f95b08cbb143accd4cece6043819"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2003071aa633477a01509890c895f9ef56cf3f2eaa72c7ec0b567f743c1abcba"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-win32.whl", hash = "sha256:13857f9070600ea1f940749f123b02d0b027afbaa45e72186df0f278915761d0"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:134b7098ac109834eeea81424b6822f33c4c52bf80b81508295611e7a21be12a"},
+ {file = "rapidfuzz-3.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:2a96209f046fe328be30fc43f06e3d4b91f0d5b74e9dcd627dbfd65890fa4a5e"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:544b0bf9d17170720809918e9ccd0d482d4a3a6eca35630d8e1459f737f71755"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d536f8beb8dd82d6efb20fe9f82c2cfab9ffa0384b5d184327e393a4edde91d"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:30f7609da871510583f87484a10820b26555a473a90ab356cdda2f3b4456256c"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f4a2468432a1db491af6f547fad8f6d55fa03e57265c2f20e5eaceb68c7907e"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a7ec4676242c8a430509cff42ce98bca2fbe30188a63d0f60fdcbfd7e84970"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dcb523243e988c849cf81220164ec3bbed378a699e595a8914fffe80596dc49f"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4eea3bf72c4fe68e957526ffd6bcbb403a21baa6b3344aaae2d3252313df6199"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4514980a5d204c076dd5b756960f6b1b7598f030009456e6109d76c4c331d03c"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9a06a99f1335fe43464d7121bc6540de7cd9c9475ac2025babb373fe7f27846b"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6c1ed63345d1581c39d4446b1a8c8f550709656ce2a3c88c47850b258167f3c2"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:cd2e6e97daf17ebb3254285cf8dd86c60d56d6cf35c67f0f9a557ef26bd66290"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9bc0f7e6256a9c668482c41c8a3de5d0aa12e8ca346dcc427b97c7edb82cba48"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c09f4e87e82a164c9db769474bc61f8c8b677f2aeb0234b8abac73d2ecf9799"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-win32.whl", hash = "sha256:e65b8f7921bf60cbb207c132842a6b45eefef48c4c3b510eb16087d6c08c70af"},
+ {file = "rapidfuzz-3.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9d6478957fb35c7844ad08f2442b62ba76c1857a56370781a707eefa4f4981e1"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:65d9250a4b0bf86320097306084bc3ca479c8f5491927c170d018787793ebe95"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:47b7c0840afa724db3b1a070bc6ed5beab73b4e659b1d395023617fc51bf68a2"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3a16c48c6df8fb633efbbdea744361025d01d79bca988f884a620e63e782fe5b"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48105991ff6e4a51c7f754df500baa070270ed3d41784ee0d097549bc9fcb16d"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a7f273906b3c7cc6d63a76e088200805947aa0bc1ada42c6a0e582e19c390d7"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c396562d304e974b4b0d5cd3afc4f92c113ea46a36e6bc62e45333d6aa8837e"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68da1b70458fea5290ec9a169fcffe0c17ff7e5bb3c3257e63d7021a50601a8e"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c5b8f9a7b177af6ce7c6ad5b95588b4b73e37917711aafa33b2e79ee80fe709"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3c42a238bf9dd48f4ccec4c6934ac718225b00bb3a438a008c219e7ccb3894c7"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a365886c42177b2beab475a50ba311b59b04f233ceaebc4c341f6f91a86a78e2"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ce897b5dafb7fb7587a95fe4d449c1ea0b6d9ac4462fbafefdbbeef6eee4cf6a"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:413ac49bae291d7e226a5c9be65c71b2630b3346bce39268d02cb3290232e4b7"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8982fc3bd49d55a91569fc8a3feba0de4cef0b391ff9091be546e9df075b81"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-win32.whl", hash = "sha256:3904d0084ab51f82e9f353031554965524f535522a48ec75c30b223eb5a0a488"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:3733aede16ea112728ffeafeb29ccc62e095ed8ec816822fa2a82e92e2c08696"},
+ {file = "rapidfuzz-3.9.0-cp39-cp39-win_arm64.whl", hash = "sha256:fc4e26f592b51f97acf0a3f8dfed95e4d830c6a8fbf359361035df836381ab81"},
+ {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e33362e98c7899b5f60dcb06ada00acd8673ce0d59aefe9a542701251fd00423"},
+ {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb67cf43ad83cb886cbbbff4df7dcaad7aedf94d64fca31aea0da7d26684283c"},
+ {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e2e106cc66453bb80d2ad9c0044f8287415676df5c8036d737d05d4b9cdbf8e"},
+ {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1256915f7e7a5cf2c151c9ac44834b37f9bd1c97e8dec6f936884f01b9dfc7d"},
+ {file = "rapidfuzz-3.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ae643220584518cbff8bf2974a0494d3e250763af816b73326a512c86ae782ce"},
+ {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:491274080742110427f38a6085bb12dffcaff1eef12dccf9e8758398c7e3957e"},
+ {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bc5559b9b94326922c096b30ae2d8e5b40b2e9c2c100c2cc396ad91bcb84d30"},
+ {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:849160dc0f128acb343af514ca827278005c1d00148d025e4035e034fc2d8c7f"},
+ {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:623883fb78e692d54ed7c43b09beec52c6685f10a45a7518128e25746667403b"},
+ {file = "rapidfuzz-3.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d20ab9abc7e19767f1951772a6ab14cb4eddd886493c2da5ee12014596ad253f"},
+ {file = "rapidfuzz-3.9.0.tar.gz", hash = "sha256:b182f0fb61f6ac435e416eb7ab330d62efdbf9b63cf0c7fa12d1f57c2eaaf6f3"},
+]
+
+[package.extras]
+full = ["numpy"]
+
+[[package]]
+name = "regex"
+version = "2024.4.28"
+description = "Alternative regular expression module, to replace re."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd196d056b40af073d95a2879678585f0b74ad35190fac04ca67954c582c6b61"},
+ {file = "regex-2024.4.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8bb381f777351bd534462f63e1c6afb10a7caa9fa2a421ae22c26e796fe31b1f"},
+ {file = "regex-2024.4.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:47af45b6153522733aa6e92543938e97a70ce0900649ba626cf5aad290b737b6"},
+ {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99d6a550425cc51c656331af0e2b1651e90eaaa23fb4acde577cf15068e2e20f"},
+ {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf29304a8011feb58913c382902fde3395957a47645bf848eea695839aa101b7"},
+ {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92da587eee39a52c91aebea8b850e4e4f095fe5928d415cb7ed656b3460ae79a"},
+ {file = "regex-2024.4.28-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6277d426e2f31bdbacb377d17a7475e32b2d7d1f02faaecc48d8e370c6a3ff31"},
+ {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28e1f28d07220c0f3da0e8fcd5a115bbb53f8b55cecf9bec0c946eb9a059a94c"},
+ {file = "regex-2024.4.28-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:aaa179975a64790c1f2701ac562b5eeb733946eeb036b5bcca05c8d928a62f10"},
+ {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6f435946b7bf7a1b438b4e6b149b947c837cb23c704e780c19ba3e6855dbbdd3"},
+ {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:19d6c11bf35a6ad077eb23852827f91c804eeb71ecb85db4ee1386825b9dc4db"},
+ {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fdae0120cddc839eb8e3c15faa8ad541cc6d906d3eb24d82fb041cfe2807bc1e"},
+ {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e672cf9caaf669053121f1766d659a8813bd547edef6e009205378faf45c67b8"},
+ {file = "regex-2024.4.28-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f57515750d07e14743db55d59759893fdb21d2668f39e549a7d6cad5d70f9fea"},
+ {file = "regex-2024.4.28-cp310-cp310-win32.whl", hash = "sha256:a1409c4eccb6981c7baabc8888d3550df518add6e06fe74fa1d9312c1838652d"},
+ {file = "regex-2024.4.28-cp310-cp310-win_amd64.whl", hash = "sha256:1f687a28640f763f23f8a9801fe9e1b37338bb1ca5d564ddd41619458f1f22d1"},
+ {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84077821c85f222362b72fdc44f7a3a13587a013a45cf14534df1cbbdc9a6796"},
+ {file = "regex-2024.4.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b45d4503de8f4f3dc02f1d28a9b039e5504a02cc18906cfe744c11def942e9eb"},
+ {file = "regex-2024.4.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:457c2cd5a646dd4ed536c92b535d73548fb8e216ebee602aa9f48e068fc393f3"},
+ {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b51739ddfd013c6f657b55a508de8b9ea78b56d22b236052c3a85a675102dc6"},
+ {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:459226445c7d7454981c4c0ce0ad1a72e1e751c3e417f305722bbcee6697e06a"},
+ {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:670fa596984b08a4a769491cbdf22350431970d0112e03d7e4eeaecaafcd0fec"},
+ {file = "regex-2024.4.28-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe00f4fe11c8a521b173e6324d862ee7ee3412bf7107570c9b564fe1119b56fb"},
+ {file = "regex-2024.4.28-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36f392dc7763fe7924575475736bddf9ab9f7a66b920932d0ea50c2ded2f5636"},
+ {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:23a412b7b1a7063f81a742463f38821097b6a37ce1e5b89dd8e871d14dbfd86b"},
+ {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f1d6e4b7b2ae3a6a9df53efbf199e4bfcff0959dbdb5fd9ced34d4407348e39a"},
+ {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:499334ad139557de97cbc4347ee921c0e2b5e9c0f009859e74f3f77918339257"},
+ {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:0940038bec2fe9e26b203d636c44d31dd8766abc1fe66262da6484bd82461ccf"},
+ {file = "regex-2024.4.28-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:66372c2a01782c5fe8e04bff4a2a0121a9897e19223d9eab30c54c50b2ebeb7f"},
+ {file = "regex-2024.4.28-cp311-cp311-win32.whl", hash = "sha256:c77d10ec3c1cf328b2f501ca32583625987ea0f23a0c2a49b37a39ee5c4c4630"},
+ {file = "regex-2024.4.28-cp311-cp311-win_amd64.whl", hash = "sha256:fc0916c4295c64d6890a46e02d4482bb5ccf33bf1a824c0eaa9e83b148291f90"},
+ {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:08a1749f04fee2811c7617fdd46d2e46d09106fa8f475c884b65c01326eb15c5"},
+ {file = "regex-2024.4.28-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b8eb28995771c087a73338f695a08c9abfdf723d185e57b97f6175c5051ff1ae"},
+ {file = "regex-2024.4.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dd7ef715ccb8040954d44cfeff17e6b8e9f79c8019daae2fd30a8806ef5435c0"},
+ {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb0315a2b26fde4005a7c401707c5352df274460f2f85b209cf6024271373013"},
+ {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fc053228a6bd3a17a9b0a3f15c3ab3cf95727b00557e92e1cfe094b88cc662"},
+ {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fe9739a686dc44733d52d6e4f7b9c77b285e49edf8570754b322bca6b85b4cc"},
+ {file = "regex-2024.4.28-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74fcf77d979364f9b69fcf8200849ca29a374973dc193a7317698aa37d8b01c"},
+ {file = "regex-2024.4.28-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:965fd0cf4694d76f6564896b422724ec7b959ef927a7cb187fc6b3f4e4f59833"},
+ {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2fef0b38c34ae675fcbb1b5db760d40c3fc3612cfa186e9e50df5782cac02bcd"},
+ {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bc365ce25f6c7c5ed70e4bc674f9137f52b7dd6a125037f9132a7be52b8a252f"},
+ {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:ac69b394764bb857429b031d29d9604842bc4cbfd964d764b1af1868eeebc4f0"},
+ {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:144a1fc54765f5c5c36d6d4b073299832aa1ec6a746a6452c3ee7b46b3d3b11d"},
+ {file = "regex-2024.4.28-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2630ca4e152c221072fd4a56d4622b5ada876f668ecd24d5ab62544ae6793ed6"},
+ {file = "regex-2024.4.28-cp312-cp312-win32.whl", hash = "sha256:7f3502f03b4da52bbe8ba962621daa846f38489cae5c4a7b5d738f15f6443d17"},
+ {file = "regex-2024.4.28-cp312-cp312-win_amd64.whl", hash = "sha256:0dd3f69098511e71880fb00f5815db9ed0ef62c05775395968299cb400aeab82"},
+ {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:374f690e1dd0dbdcddea4a5c9bdd97632cf656c69113f7cd6a361f2a67221cb6"},
+ {file = "regex-2024.4.28-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:25f87ae6b96374db20f180eab083aafe419b194e96e4f282c40191e71980c666"},
+ {file = "regex-2024.4.28-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5dbc1bcc7413eebe5f18196e22804a3be1bfdfc7e2afd415e12c068624d48247"},
+ {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f85151ec5a232335f1be022b09fbbe459042ea1951d8a48fef251223fc67eee1"},
+ {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57ba112e5530530fd175ed550373eb263db4ca98b5f00694d73b18b9a02e7185"},
+ {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:224803b74aab56aa7be313f92a8d9911dcade37e5f167db62a738d0c85fdac4b"},
+ {file = "regex-2024.4.28-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54a047b607fd2d2d52a05e6ad294602f1e0dec2291152b745870afc47c1397"},
+ {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a2a512d623f1f2d01d881513af9fc6a7c46e5cfffb7dc50c38ce959f9246c94"},
+ {file = "regex-2024.4.28-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c06bf3f38f0707592898428636cbb75d0a846651b053a1cf748763e3063a6925"},
+ {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1031a5e7b048ee371ab3653aad3030ecfad6ee9ecdc85f0242c57751a05b0ac4"},
+ {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d7a353ebfa7154c871a35caca7bfd8f9e18666829a1dc187115b80e35a29393e"},
+ {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:7e76b9cfbf5ced1aca15a0e5b6f229344d9b3123439ffce552b11faab0114a02"},
+ {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5ce479ecc068bc2a74cb98dd8dba99e070d1b2f4a8371a7dfe631f85db70fe6e"},
+ {file = "regex-2024.4.28-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d77b6f63f806578c604dca209280e4c54f0fa9a8128bb8d2cc5fb6f99da4150"},
+ {file = "regex-2024.4.28-cp38-cp38-win32.whl", hash = "sha256:d84308f097d7a513359757c69707ad339da799e53b7393819ec2ea36bc4beb58"},
+ {file = "regex-2024.4.28-cp38-cp38-win_amd64.whl", hash = "sha256:2cc1b87bba1dd1a898e664a31012725e48af826bf3971e786c53e32e02adae6c"},
+ {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7413167c507a768eafb5424413c5b2f515c606be5bb4ef8c5dee43925aa5718b"},
+ {file = "regex-2024.4.28-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:108e2dcf0b53a7c4ab8986842a8edcb8ab2e59919a74ff51c296772e8e74d0ae"},
+ {file = "regex-2024.4.28-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f1c5742c31ba7d72f2dedf7968998730664b45e38827637e0f04a2ac7de2f5f1"},
+ {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecc6148228c9ae25ce403eade13a0961de1cb016bdb35c6eafd8e7b87ad028b1"},
+ {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7d893c8cf0e2429b823ef1a1d360a25950ed11f0e2a9df2b5198821832e1947"},
+ {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4290035b169578ffbbfa50d904d26bec16a94526071ebec3dadbebf67a26b25e"},
+ {file = "regex-2024.4.28-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44a22ae1cfd82e4ffa2066eb3390777dc79468f866f0625261a93e44cdf6482b"},
+ {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd24fd140b69f0b0bcc9165c397e9b2e89ecbeda83303abf2a072609f60239e2"},
+ {file = "regex-2024.4.28-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:39fb166d2196413bead229cd64a2ffd6ec78ebab83fff7d2701103cf9f4dfd26"},
+ {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9301cc6db4d83d2c0719f7fcda37229691745168bf6ae849bea2e85fc769175d"},
+ {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c3d389e8d76a49923683123730c33e9553063d9041658f23897f0b396b2386f"},
+ {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:99ef6289b62042500d581170d06e17f5353b111a15aa6b25b05b91c6886df8fc"},
+ {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:b91d529b47798c016d4b4c1d06cc826ac40d196da54f0de3c519f5a297c5076a"},
+ {file = "regex-2024.4.28-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:43548ad74ea50456e1c68d3c67fff3de64c6edb85bcd511d1136f9b5376fc9d1"},
+ {file = "regex-2024.4.28-cp39-cp39-win32.whl", hash = "sha256:05d9b6578a22db7dedb4df81451f360395828b04f4513980b6bd7a1412c679cc"},
+ {file = "regex-2024.4.28-cp39-cp39-win_amd64.whl", hash = "sha256:3986217ec830c2109875be740531feb8ddafe0dfa49767cdcd072ed7e8927962"},
+ {file = "regex-2024.4.28.tar.gz", hash = "sha256:83ab366777ea45d58f72593adf35d36ca911ea8bd838483c1823b883a121b0e4"},
+]
+
+[[package]]
+name = "requests"
+version = "2.31.0"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
+ {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
+[[package]]
+name = "tomli"
+version = "2.0.1"
+description = "A lil' TOML parser"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
+ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
+]
+
+[[package]]
+name = "tzdata"
+version = "2024.1"
+description = "Provider of IANA time zone data"
+optional = false
+python-versions = ">=2"
+files = [
+ {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"},
+ {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"},
+]
+
+[[package]]
+name = "tzlocal"
+version = "5.2"
+description = "tzinfo object for the local timezone"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"},
+ {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"},
+]
+
+[package.dependencies]
+tzdata = {version = "*", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"]
+
+[[package]]
+name = "urllib3"
+version = "2.2.1"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"},
+ {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.10"
+content-hash = "f65cd66387236673e2a5afb3b2a75362c97815cdde592a86712737fb9ca71695"
diff --git a/pkgs/sources/update_vim_plugins/pyproject.toml b/pkgs/sources/update_vim_plugins/pyproject.toml
new file mode 100644
index 00000000..38caf76d
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/pyproject.toml
@@ -0,0 +1,45 @@
+[tool.poetry]
+name = "update_vim_plugins"
+version = "0.1.0"
+description = ""
+authors = ["Your Name <you@example.com>"]
+packages = [{ include = "update_vim_plugins" }]
+
+[tool.poetry.scripts]
+update-vim-plugins = "update_vim_plugins.__main__:main"
+
+[tool.poetry.dependencies]
+python = "^3.10"
+requests = "^2.28.2"
+cleo = "^2.0.1"
+jsonpickle = "*"
+dateparser = "^1.1.8"
+
+[tool.poetry.group.test.dependencies]
+pytest-cov = "^4.0.0"
+pytest = "^7.3.1"
+pytest-mock = "^3.10.0"
+
+[tool.poetry.group.dev]
+optional = true
+
+[tool.poetry.group.dev.dependencies]
+# black = "^23.3.0"
+# ruff-lsp = "^0.0.24"
+# mypy = "^1.2.0"
+# types-requests = "^2.28.11.17"
+# isort = "^5.12.0"
+# ruff = "^0.0.262"
+
+[tool.isort]
+profile = "black"
+
+[tool.black]
+line-length = 120
+
+[tool.ruff]
+line-length = 120
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/pkgs/sources/update_vim_plugins/update.sh b/pkgs/sources/update_vim_plugins/update.sh
new file mode 100755
index 00000000..1bad12a9
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env sh
+
+poetry update --lock
+
+# vim: ft=sh
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/__init__.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/__init__.py
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/__main__.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/__main__.py
new file mode 100644
index 00000000..a8d9e06f
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/__main__.py
@@ -0,0 +1,15 @@
+from cleo.application import Application
+
+from .update import UpdateCommand
+from .cleanup import CleanUpCommand
+
+
+def main():
+ application = Application()
+ application.add(UpdateCommand())
+ application.add(CleanUpCommand())
+ application.run()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/cleanup.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/cleanup.py
new file mode 100644
index 00000000..fd313ed0
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/cleanup.py
@@ -0,0 +1,100 @@
+from cleo.commands.command import Command
+from cleo.helpers import argument
+
+from .helpers import read_manifest_to_spec, read_blacklist_to_spec, write_manifest_from_spec
+
+
+class CleanUpCommand(Command):
+ name = "cleanup"
+ description = "Clean up manifest"
+ arguments = [argument("plug_dir", description="Path to the plugin directory", optional=False)]
+
+ def handle(self):
+ """Main command function"""
+
+ plug_dir = self.argument("plug_dir")
+ self.line("<comment>Checking manifest file</comment>")
+ # all cleaning up will be done during reading and writing automatically
+ manifest = read_manifest_to_spec(plug_dir)
+ blacklist = read_blacklist_to_spec(plug_dir)
+
+ new_manifest = [spec for spec in manifest if spec not in blacklist]
+
+ new_manifest_filterd = self.filter_renamed(new_manifest)
+
+ write_manifest_from_spec(new_manifest_filterd, plug_dir)
+
+ self.line("<comment>Done</comment>")
+
+ def filter_renamed(self, specs):
+ """Filter specs that define the same plugin (same owner and same repo) but with different properties.
+ This could be a different name, source, or branch
+ """
+
+ error = False
+ for i, p in enumerate(specs):
+ for p2 in specs:
+ same_owner = p.owner.lower() == p2.owner.lower()
+ same_repo = p.repo.lower() == p2.repo.lower()
+ different_specs = p != p2
+ marked_duplicate = p.marked_duplicate or p2.marked_duplicate
+
+ if same_owner and same_repo and different_specs and not marked_duplicate:
+ self.line("<info>The following lines appear to define the same plugin</info>")
+
+ p_props_defined = p.branch is not None or p.custom_name is not None
+ p2_props_defined = p2.branch is not None or p2.custom_name is not None
+ p_is_lower_case = p.owner == p.owner.lower() and p.name == p.name.lower()
+ p2_is_lower_case = p2.owner == p2.owner.lower() and p2.name == p2.name.lower()
+
+ # list of conditions for selecting p
+ select_p = p_props_defined and not p2_props_defined or p2_is_lower_case and not p_is_lower_case
+ # list of conditions for selecting p2
+ select_p2 = p2_props_defined and not p_props_defined or p_is_lower_case and not p2_is_lower_case
+
+ # one is more defined and is all lower, but the other is not all lower
+ # (we assume the not all lower case is the correct naming)
+ error_props_lower = (
+ p_props_defined and p_is_lower_case and not p2_props_defined and not p2_is_lower_case
+ )
+ error_props_lower2 = (
+ p2_props_defined and p2_is_lower_case and not p_props_defined and not p_is_lower_case
+ )
+
+ # both props are defined
+ error_props = p_props_defined and p2_props_defined
+
+ # the sources are different
+ error_source = p.repository_host != p2.repository_host
+
+ if error_props_lower or error_props_lower2 or error_props or error_source:
+ self.line(" • <error>Cannot determine which is the correct plugin</error>")
+ self.line(f" - {p.line}")
+ self.line(f" - {p2.line}")
+ error = True
+ # remove second spec to not encounter the error twice
+ # this will not be written to the manifest.txt because we set
+ # the error flag and will exit after the loop
+ specs.remove(p2)
+ elif select_p:
+ self.line(f" - <comment>{p.line}</comment>")
+ self.line(f" - {p2.line}")
+ specs.remove(p2)
+ elif select_p2:
+ self.line(f" - {p.line}")
+ self.line(f" - <comment>{p2.line}</comment>")
+ specs.remove(p)
+ else:
+ self.line(" • <error>Logic error in correct spec determination</error>")
+ self.line(f" - {p.line}")
+ self.line(f" - {p2.line}")
+ error = True
+ # remove second spec to not encounter the error twice
+ # this will not be written to the manifest.txt because we set
+ # the error flag and will exit after the loop
+ specs.remove(p)
+ if error:
+ # exit after all errors have been found
+ exit(1)
+
+ return specs
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/helpers.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/helpers.py
new file mode 100644
index 00000000..8a28b0e8
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/helpers.py
@@ -0,0 +1,61 @@
+from .spec import PluginSpec
+
+MANIFEST_FILE = "manifest.txt"
+BLACKLIST_FILE = "blacklist.txt"
+PKGS_FILE = "default.nix"
+JSON_FILE = ".plugins.json"
+PLUGINS_LIST_FILE = "plugins.md"
+
+
+def get_const(const: str, plug_dir: str) -> str:
+ out = plug_dir + "/" + const
+ return out
+
+
+def read_manifest(plug_dir: str) -> list[str]:
+ with open(get_const(MANIFEST_FILE, plug_dir), "r") as file:
+ specs = set([spec.strip() for spec in file.readlines()])
+
+ return sorted(specs)
+
+
+def read_manifest_to_spec(plug_dir: str) -> list[PluginSpec]:
+ manifest = read_manifest(plug_dir)
+ specs = [PluginSpec.from_spec(spec.strip()) for spec in manifest]
+
+ return sorted(specs)
+
+
+def read_blacklist(plug_dir: str) -> list[str]:
+ with open(get_const(BLACKLIST_FILE, plug_dir), "r") as file:
+ if len(file.readlines()) == 0:
+ return [""]
+ else:
+ blacklisted_specs = set([spec.strip() for spec in file.readlines()])
+
+ return sorted(blacklisted_specs)
+
+
+def read_blacklist_to_spec(plug_dir: str) -> list[PluginSpec]:
+ blacklist = read_blacklist(plug_dir)
+ specs = [PluginSpec.from_spec(spec.strip()) for spec in blacklist]
+
+ return sorted(specs)
+
+
+def write_manifest(specs: list[str] | set[str], plug_dir: str):
+ """write specs to manifest file. Does some cleaning up"""
+
+ with open(get_const(MANIFEST_FILE, plug_dir), "w") as file:
+ specs = sorted(set(specs), key=lambda x: x.lower())
+ specs = [p for p in specs]
+
+ for s in specs:
+ file.write(f"{s}\n")
+
+
+def write_manifest_from_spec(specs: list[PluginSpec], plug_dir: str):
+ """write specs to manifest file. Does some cleaning up"""
+
+ strings = [f"{spec}" for spec in specs]
+ write_manifest(strings, plug_dir)
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/nix.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/nix.py
new file mode 100644
index 00000000..66a8df4c
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/nix.py
@@ -0,0 +1,121 @@
+import abc
+import enum
+import json
+import subprocess
+
+
+def nix_prefetch_url(url):
+ """Return the sha256 hash of the given url."""
+ subprocess_output = subprocess.check_output(
+ ["nix-prefetch-url", "--type", "sha256", url],
+ stderr=subprocess.DEVNULL,
+ )
+ sha256 = subprocess_output.decode("utf-8").strip()
+ return sha256
+
+
+def nix_prefetch_git(url):
+ """Return the sha256 hash of the given git url."""
+ subprocess_output = subprocess.check_output(["nix-prefetch-git", url], stderr=subprocess.DEVNULL)
+ sha256 = json.loads(subprocess_output)["sha256"]
+ return sha256
+
+
+class Source(abc.ABC):
+ """Abstract base class for sources."""
+
+ url: str
+ sha256: str
+
+ @abc.abstractmethod
+ def __init__(self, url: str) -> None:
+ """Initialize a Source."""
+
+ @abc.abstractmethod
+ def get_nix_expression(self):
+ """Return the nix expression for this source."""
+
+ def __repr__(self):
+ """Return the representation of this source."""
+ return self.get_nix_expression()
+
+
+class UrlSource(Source):
+ """A source that is a url."""
+
+ def __init__(self, url: str) -> None:
+ """Initialize a UrlSource."""
+ self.url = url
+ self.sha256 = nix_prefetch_url(url)
+
+ def get_nix_expression(self):
+ """Return the nix expression for this source."""
+ return f'fetchurl {{ url = "{self.url}"; sha256 = "{self.sha256}"; }}'
+
+
+class GitSource(Source):
+ """A source that is a git repository."""
+
+ def __init__(self, url: str, rev: str) -> None:
+ """Initialize a GitSource."""
+ self.url = url
+ self.rev = rev
+ self.sha256 = nix_prefetch_git(url)
+
+ def get_nix_expression(self):
+ """Return the nix expression for this source."""
+ return f'fetchgit {{ url = "{self.url}"; rev = "{self.rev}"; sha256 = "{self.sha256}"; }}'
+
+
+class License(enum.Enum):
+ """An enumeration of licenses."""
+
+ AGPL_3_0 = "agpl3Only"
+ APACHE_2_0 = "asf20"
+ BSD_2_CLAUSE = "bsd2"
+ BSD_3_CLAUSE = "bsd3"
+ BSL_1_0 = "bsl1_0"
+ CC0_1_0 = "cc0"
+ EPL_2_0 = "epl20"
+ GPL_2_0 = "gpl2Only"
+ GPL_3_0 = "gpl3Only"
+ ISCLGPL_2_1 = "lgpl21Only"
+ MIT = "mit"
+ MPL_2_0 = "mpl20"
+ UNLUNLICENSE = "unlicense"
+ WTFPL = "wtfpl"
+ UNFREE = "unfree"
+ UNKNOWN = ""
+
+ @classmethod
+ def from_spdx_id(cls, spdx_id: str | None) -> "License":
+ """Return the License from the given spdx_id."""
+ mapping = {
+ "AGPL-3.0": cls.AGPL_3_0,
+ "AGPL-3.0-only": cls.AGPL_3_0,
+ "Apache-2.0": cls.APACHE_2_0,
+ "BSD-2-Clause": cls.BSD_2_CLAUSE,
+ "BSD-3-Clause": cls.BSD_3_CLAUSE,
+ "BSL-1.0": cls.BSL_1_0,
+ "CC0-1.0": cls.CC0_1_0,
+ "EPL-2.0": cls.EPL_2_0,
+ "GPL-2.0": cls.GPL_2_0,
+ "GPL-2.0-only": cls.GPL_2_0,
+ "GPL-3.0": cls.GPL_3_0,
+ "GPL-3.0-only": cls.GPL_3_0,
+ "LGPL-2.1-only": cls.ISCLGPL_2_1,
+ "MIT": cls.MIT,
+ "MPL-2.0": cls.MPL_2_0,
+ "Unlicense": cls.UNLUNLICENSE,
+ "WTFPL": cls.WTFPL,
+ }
+
+ if spdx_id is None:
+ return cls.UNKNOWN
+
+ spdx_id = spdx_id.upper()
+ return mapping.get(spdx_id, cls.UNKNOWN)
+
+ def __str__(self):
+ """Return the string representation of this license."""
+ return self.value
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/plugin.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/plugin.py
new file mode 100644
index 00000000..8334ad53
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/plugin.py
@@ -0,0 +1,182 @@
+import logging
+import os
+import urllib
+
+import requests
+import jsonpickle
+from datetime import datetime, date
+from dateparser import parse
+
+from .nix import GitSource, License, Source, UrlSource
+from .spec import PluginSpec, RepositoryHost
+
+
+logger = logging.getLogger(__name__)
+
+
+class VimPlugin:
+ """Abstract base class for vim plugins."""
+
+ name: str
+ owner: str
+ repo: str
+ version: date
+ source: Source
+ description: str = "No description"
+ homepage: str
+ license: License
+ source_line: str
+ checked: date = datetime.now().date()
+
+ def to_nix(self):
+ """Return the nix expression for this plugin."""
+ meta = f'with lib; {{ description = "{self.description}"; homepage = "{self.homepage}"; license = with licenses; [ {self.license.value} ]; }}'
+ return f'/* Generated from: {self.source_line} */ {self.name} = buildVimPlugin {{ pname = "{self.name}"; version = "{self.version}"; src = {self.source.get_nix_expression()}; meta = {meta}; }};'
+
+ def to_json(self):
+ """Serizalize the plugin to json"""
+ return jsonpickle.encode(self)
+
+ def to_markdown(self):
+ link = f"[{self.source_line}]({self.homepage})"
+ version = f"{self.version}"
+ package_name = f"{self.name}"
+ checked = f"{self.checked}"
+
+ return f"| {link} | {version} | `{package_name}` | {checked} |"
+
+ def __lt__(self, o: object) -> bool:
+ if not isinstance(o, VimPlugin):
+ return False
+
+ return self.name.lower() < o.name.lower()
+
+ def __repr__(self):
+ """Return the representation of this plugin."""
+ return f"VimPlugin({self.name!r}, {self.version.strftime('%Y-%m-%d')})"
+
+
+def _get_github_token():
+ token = os.environ.get("GITHUB_TOKEN")
+ if token is None:
+ # NOTE: This should never use more than the free api requests <2023-12-09>
+ pass
+ # logger.warning("GITHUB_TOKEN environment variable not set")
+ return token
+
+
+class GitHubPlugin(VimPlugin):
+ def __init__(self, plugin_spec: PluginSpec) -> None:
+ """Initialize a GitHubPlugin."""
+
+ full_name = f"{plugin_spec.owner}/{plugin_spec.repo}"
+ repo_info = self._api_call(f"repos/{full_name}")
+ default_branch = plugin_spec.branch or repo_info["default_branch"]
+ api_callback = self._api_call(f"repos/{full_name}/commits/{default_branch}")
+ latest_commit = api_callback["commit"]
+ sha = api_callback["sha"]
+
+ self.name = plugin_spec.name
+ self.owner = plugin_spec.owner
+ self.version = parse(latest_commit["committer"]["date"]).date()
+ self.source = UrlSource(f"https://github.com/{full_name}/archive/{sha}.tar.gz")
+ self.description = (repo_info.get("description") or "").replace('"', '\\"')
+ self.homepage = repo_info["html_url"]
+ self.license = plugin_spec.license or License.from_spdx_id((repo_info.get("license") or {}).get("spdx_id"))
+ self.source_line = plugin_spec.line
+
+ def _api_call(self, path: str, token: str | None = _get_github_token()):
+ """Call the GitHub API."""
+ url = f"https://api.github.com/{path}"
+ headers = {"Content-Type": "application/json"}
+ if token is not None:
+ headers["Authorization"] = f"token {token}"
+ response = requests.get(url, headers=headers)
+ if response.status_code != 200:
+ raise RuntimeError(f"GitHub API call failed: {response.text}")
+ return response.json()
+
+
+class GitlabPlugin(VimPlugin):
+ def __init__(self, plugin_spec: PluginSpec) -> None:
+ """Initialize a GitlabPlugin."""
+
+ full_name = urllib.parse.quote(f"{plugin_spec.owner}/{plugin_spec.repo}", safe="")
+ repo_info = self._api_call(f"projects/{full_name}")
+ default_branch = plugin_spec.branch or repo_info["default_branch"]
+ api_callback = self._api_call(f"projects/{full_name}/repository/branches/{default_branch}")
+ latest_commit = api_callback["commit"]
+ sha = latest_commit["id"]
+
+ self.name = plugin_spec.name
+ self.owner = plugin_spec.owner
+ self.version = parse(latest_commit["created_at"]).date()
+ self.source = UrlSource(f"https://gitlab.com/api/v4/projects/{full_name}/repository/archive.tar.gz?sha={sha}")
+ self.description = (repo_info.get("description") or "").replace('"', '\\"')
+ self.homepage = repo_info["web_url"]
+ self.license = plugin_spec.license or License.from_spdx_id(repo_info.get("license", {}).get("key"))
+ self.source_line = plugin_spec.line
+
+ def _api_call(self, path: str) -> dict:
+ """Call the Gitlab API."""
+ url = f"https://gitlab.com/api/v4/{path}"
+ response = requests.get(url)
+ if response.status_code != 200:
+ raise RuntimeError(f"Gitlab API call failed: {response.text}")
+ return response.json()
+
+
+def _get_sourcehut_token():
+ token = os.environ.get("SOURCEHUT_TOKEN")
+ if token is None:
+ # NOTE: This should never use more than the free requests <2023-12-09>
+ pass
+ # logger.warning("SOURCEHUT_TOKEN environment variable not set")
+ return token
+
+
+class SourceHutPlugin(VimPlugin):
+ def __init__(self, plugin_spec: PluginSpec) -> None:
+ """Initialize a SourceHutPlugin."""
+
+ repo_info = self._api_call(f"~{plugin_spec.owner}/repos/{plugin_spec.repo}")
+ if plugin_spec.branch is None:
+ commits = self._api_call(f"~{plugin_spec.owner}/repos/{plugin_spec.repo}/log")
+ else:
+ commits = self._api_call(f"~{plugin_spec.owner}/repos/{plugin_spec.repo}/log/{plugin_spec.branch}")
+ latest_commit = commits["results"][0]
+ sha = latest_commit["id"]
+
+ self.name = plugin_spec.name
+ self.owner = plugin_spec.owner
+ self.version = parse(latest_commit["timestamp"]).date()
+ self.description = (repo_info.get("description") or "").replace('"', '\\"')
+ self.homepage = f"https://git.sr.ht/~{plugin_spec.owner}/{plugin_spec.repo}"
+ self.source = GitSource(self.homepage, sha)
+ self.license = plugin_spec.license or License.UNKNOWN # cannot be determined via API
+ self.source_line = plugin_spec.line
+
+ def _api_call(self, path: str, token: str | None = _get_sourcehut_token()):
+ """Call the SourceHut API."""
+
+ url = f"https://git.sr.ht/api/{path}"
+ headers = {"Content-Type": "application/json"}
+ if token is not None:
+ headers["Authorization"] = f"token {token}"
+ response = requests.get(url, headers=headers)
+ if response.status_code != 200:
+ raise RuntimeError(f"SourceHut API call failed: {response.json()}")
+ return response.json()
+
+
+def plugin_from_spec(plugin_spec: PluginSpec) -> VimPlugin:
+ """Initialize a VimPlugin."""
+
+ if plugin_spec.repository_host == RepositoryHost.GITHUB:
+ return GitHubPlugin(plugin_spec)
+ elif plugin_spec.repository_host == RepositoryHost.GITLAB:
+ return GitlabPlugin(plugin_spec)
+ elif plugin_spec.repository_host == RepositoryHost.SOURCEHUT:
+ return SourceHutPlugin(plugin_spec)
+ else:
+ raise NotImplementedError(f"Unsupported source: {plugin_spec.repository_host}")
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/spec.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/spec.py
new file mode 100644
index 00000000..0f2fb29c
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/spec.py
@@ -0,0 +1,143 @@
+import enum
+import re
+
+from .nix import License
+
+
+class RepositoryHost(enum.Enum):
+ """A repository host."""
+
+ GITHUB = "github"
+ GITLAB = "gitlab"
+ SOURCEHUT = "sourcehut"
+
+
+class PluginSpec:
+ """A Vim plugin Spec."""
+
+ @classmethod
+ def from_spec(cls, spec):
+ """The spec line must be in the format:
+ [<repository_host>:]<owner>/<repo>[:<branch>][:name].
+
+ repository_host is one of github (default), gitlab, or sourcehut.
+ owner is the repository owner.
+ repo is the repository name.
+ branch is the git branch.
+ name is the name to use for the plugin (default is value of repo).
+ """
+ repository_host = RepositoryHost.GITHUB
+ # gitref = "master"
+
+ repository_host_regex = r"((?P<repository_host>[^:]+):)"
+ owner_regex = r"(?P<owner>[^/:]+)"
+ repo_regex = r"(?P<repo>[^:]+)"
+ branch_regex = r"(:(?P<branch>[^:]+)?)"
+ name_regex = r"(:(?P<name>[^:]+)?)"
+ license_regex = r"(:(?P<license>[^:]+)?)"
+ marked_duplicate_regex = r"(:(?P<duplicate>duplicate))"
+
+ spec_regex = re.compile(
+ f"^{repository_host_regex}?{owner_regex}/{repo_regex}{branch_regex}?{name_regex}?{license_regex}?{marked_duplicate_regex}?$",
+ )
+
+ match = spec_regex.match(spec)
+ if match is None:
+ raise ValueError(f"Invalid spec: {spec}")
+
+ group_dict = match.groupdict()
+
+ repository_host = RepositoryHost(group_dict.get("repository_host") or "github")
+
+ owner = group_dict.get("owner")
+ if owner is None:
+ raise RuntimeError("Could not get owner")
+
+ repo = group_dict.get("repo")
+ if repo is None:
+ raise RuntimeError("Could not get repo")
+
+ branch = group_dict.get("branch")
+ name = group_dict.get("name")
+ license = group_dict.get("license")
+ marked_duplicate = bool(group_dict.get("duplicate")) # True if 'duplicate', False if None
+
+ line = spec
+
+ return cls(repository_host, owner, repo, line, branch, name, license, marked_duplicate)
+
+ def __init__(
+ self,
+ repository_host: RepositoryHost,
+ owner: str,
+ repo: str,
+ line: str,
+ branch: str | None = None,
+ name: str | None = None,
+ license: str | None = None,
+ marked_duplicate: bool = False,
+ ) -> None:
+ """Initialize a VimPluginSpec."""
+ self.repository_host = repository_host
+ self.owner = owner
+ self.repo = repo
+ self.branch = branch
+ self.custom_name = name
+ self.name = name or repo.replace(".", "-").replace("_", "-")
+ self.license = License(license) if license else None
+ self.line = line
+ self.marked_duplicate = marked_duplicate
+
+ def __str__(self) -> str:
+ """Return a string representation of a VimPluginSpec."""
+ spec = ""
+
+ if self.repository_host != RepositoryHost.GITHUB:
+ spec += f"{self.repository_host.value}:"
+
+ spec += f"{self.owner}/{self.repo}"
+
+ spec += ":"
+ if self.branch is not None:
+ spec += self.branch
+
+ spec += ":"
+ if self.custom_name is not None:
+ spec += self.custom_name
+
+ spec += ":"
+ if self.license is not None:
+ spec += str(self.license)
+
+ spec += ":"
+ if self.marked_duplicate:
+ spec += "duplicate"
+
+ return spec.rstrip(":")
+
+ def __repr__(self):
+ """Return the representation of the specs"""
+ return f"PluginSpec({self.owner}/{self.repo}, {self.name})"
+
+ def to_spec(self):
+ """Return a spec line for a VimPluginSpec."""
+ return str(self)
+
+ def __lt__(self, o: object) -> bool:
+ if not isinstance(o, PluginSpec):
+ return False
+
+ return self.name.lower() < o.name.lower()
+
+ def __eq__(self, o: object) -> bool:
+ """Return True if the two specs are equal."""
+ if not isinstance(o, PluginSpec):
+ return False
+
+ return (
+ self.repository_host == o.repository_host
+ and self.owner == o.owner
+ and self.repo == o.repo
+ and self.branch == o.branch
+ and self.name == o.name
+ )
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/__init__.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/__init__.py
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/fixtures.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/fixtures.py
new file mode 100644
index 00000000..75dd251a
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/fixtures.py
@@ -0,0 +1,44 @@
+import json
+
+import pytest
+from pytest_mock import MockerFixture
+
+from update_vim_plugins.nix import GitSource, UrlSource
+
+
+@pytest.fixture()
+def url():
+ return "https://example.com"
+
+
+@pytest.fixture()
+def rev():
+ return "1234567890abcdef"
+
+
+@pytest.fixture()
+def sha256():
+ return "sha256-1234567890abcdef"
+
+
+@pytest.fixture()
+def url_source(mocker: MockerFixture, url: str, sha256: str):
+ mocker.patch("subprocess.check_output", return_value=bytes(sha256, "utf-8"))
+ return UrlSource(url)
+
+
+@pytest.fixture()
+def git_source(mocker: MockerFixture, url: str, rev: str, sha256: str):
+ return_value = {
+ "url": url,
+ "rev": rev,
+ "date": "1970-01-01T00:00:00+00:00",
+ "path": "",
+ "sha256": sha256,
+ "fetchLFS": False,
+ "fetchSubmodules": False,
+ "deepClone": False,
+ "leaveDotGit": False,
+ }
+ mocker.patch("subprocess.check_output", return_value=json.dumps(return_value).encode("utf-8"))
+ return GitSource(url, rev)
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_nix.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_nix.py
new file mode 100644
index 00000000..46e59f76
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_nix.py
@@ -0,0 +1,32 @@
+from update_vim_plugins.nix import GitSource, License, UrlSource
+
+
+def test_url_source(url_source: UrlSource, url: str, sha256: str):
+ assert url_source.url == url
+ assert url_source.sha256 == sha256
+
+
+def test_url_source_nix_expression(url_source: UrlSource, url: str, sha256: str):
+ assert url_source.get_nix_expression() == f'fetchurl {{ url = "{url}"; sha256 = "{sha256}"; }}'
+
+
+def test_git_source(git_source: GitSource, url: str, rev: str, sha256: str):
+ assert git_source.url == url
+ assert git_source.sha256 == sha256
+ assert git_source.rev == rev
+
+
+def test_git_source_nix_expression(git_source: GitSource, url: str, rev: str, sha256: str):
+ assert git_source.get_nix_expression() == f'fetchgit {{ url = "{url}"; rev = "{rev}"; sha256 = "{sha256}"; }}'
+
+
+def test_license_github():
+ github_license = "MIT"
+ license = License.from_spdx_id(github_license)
+ assert license == License.MIT
+
+
+def test_license_gitlab():
+ gitlab_license = "mit"
+ license = License.from_spdx_id(gitlab_license)
+ assert license == License.MIT
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_plugin.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_plugin.py
new file mode 100644
index 00000000..32377e24
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_plugin.py
@@ -0,0 +1,144 @@
+import json
+from typing import Callable
+
+import pytest
+from pytest_mock import MockFixture
+
+from update_vim_plugins.nix import License, UrlSource
+from update_vim_plugins.plugin import GitHubPlugin, VimPlugin
+from update_vim_plugins.spec import PluginSpec
+
+
+@pytest.fixture()
+def mock_source(sha256: str):
+ class MockSource:
+ def __init__(self, *args, **kwargs):
+ pass
+
+ def get_nix_expression(self):
+ return "src"
+
+ return MockSource()
+
+
+@pytest.fixture()
+def mock_plugin(mock_source):
+ class MockVimPlugin(VimPlugin):
+ def __init__(self):
+ self.name = "test"
+ self.version = "1.0.0"
+ self.source = mock_source
+ self.description = "No description"
+ self.homepage = "https://example.com"
+ self.license = License.UNKNOWN
+
+ return MockVimPlugin()
+
+
+def test_vim_plugin_nix_expression(mock_plugin):
+ assert (
+ mock_plugin.get_nix_expression()
+ == 'test = buildVimPluginFrom2Nix { pname = "test"; version = "1.0.0"; src = src; meta = with lib; { description = "No description"; homepage = "https://example.com"; license = with licenses; [ ]; }; };'
+ )
+
+
+class MockResponse:
+ def __init__(self, status_code: int, content: bytes):
+ self.status_code = status_code
+ self.content = content
+
+ def json(self):
+ return json.loads(self.content)
+
+
+def mock_request_get(repsonses: dict[str, MockResponse]):
+ respones_not_found = MockResponse(404, b'{"message": "Not Found"}')
+
+ def mock_get(url: str, *args, **kwargs):
+ return repsonses.get(url, respones_not_found)
+
+ return mock_get
+
+
+@pytest.fixture()
+def github_commits_response():
+ return MockResponse(
+ 200,
+ json.dumps(
+ {
+ "sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
+ "commit": {
+ "committer": {
+ "date": "2011-04-14T16:00:49Z",
+ },
+ },
+ }
+ ),
+ )
+
+
+@pytest.fixture()
+def github_get(github_commits_response: MockResponse):
+ repos_response = MockResponse(
+ 200,
+ json.dumps(
+ {
+ "html_url": "https://github.com/octocat/Hello-World",
+ "description": "This your first repo!",
+ "fork": False,
+ "default_branch": "master",
+ "license": {
+ "spdx_id": "MIT",
+ },
+ }
+ ),
+ )
+ responses = {
+ "https://api.github.com/repos/octocat/Hello-World": repos_response,
+ "https://api.github.com/repos/octocat/Hello-World/commits/master": github_commits_response,
+ }
+ return mock_request_get(responses)
+
+
+@pytest.fixture()
+def github_get_no_license(github_commits_response: MockResponse):
+ repos_response = MockResponse(
+ 200,
+ json.dumps(
+ {
+ "html_url": "https://github.com/octocat/Hello-World",
+ "description": "This your first repo!",
+ "fork": False,
+ "default_branch": "master",
+ }
+ ),
+ )
+ responses = {
+ "https://api.github.com/repos/octocat/Hello-World": repos_response,
+ "https://api.github.com/repos/octocat/Hello-World/commits/master": github_commits_response,
+ }
+ return mock_request_get(responses)
+
+
+def test_github_plugin(mocker: MockFixture, github_get: Callable, url_source: UrlSource):
+ mocker.patch("requests.get", github_get)
+ url_source = mocker.patch("update_vim_plugins.nix.UrlSource", url_source)
+
+ spec = PluginSpec.from_spec("octocat/Hello-World")
+ plugin = GitHubPlugin(spec)
+
+ assert plugin.name == "Hello-World"
+ assert plugin.version == "2011-04-14"
+ assert plugin.description == "This your first repo!"
+ assert plugin.homepage == "https://github.com/octocat/Hello-World"
+ assert plugin.license == License.MIT
+
+
+def test_github_plugin_no_license(mocker: MockFixture, github_get_no_license: Callable, url_source: UrlSource):
+ mocker.patch("requests.get", github_get_no_license)
+ url_source = mocker.patch("update_vim_plugins.nix.UrlSource", url_source)
+
+ spec = PluginSpec.from_spec("octocat/Hello-World")
+ plugin = GitHubPlugin(spec)
+
+ assert plugin.license == License.UNKNOWN
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_spec.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_spec.py
new file mode 100644
index 00000000..2b9a1d24
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/tests/test_spec.py
@@ -0,0 +1,136 @@
+import pytest
+
+from update_vim_plugins.spec import PluginSpec, RepositoryHost
+
+
+@pytest.fixture()
+def owner():
+ return "owner"
+
+
+@pytest.fixture()
+def repo():
+ return "repo.nvim"
+
+
+@pytest.fixture()
+def branch():
+ return "main"
+
+
+@pytest.fixture()
+def name():
+ return "repo-nvim"
+
+
+@pytest.fixture()
+def license():
+ return "mit"
+
+
+def test_from_spec_simple(owner: str, repo: str):
+ vim_plugin = PluginSpec.from_spec(f"{owner}/{repo}")
+
+ assert vim_plugin.owner == owner
+ assert vim_plugin.repo == repo
+
+
+def test_from_spec_with_gitref(owner: str, repo: str, branch: str):
+ vim_plugin = PluginSpec.from_spec(f"{owner}/{repo}:{branch}")
+
+ assert vim_plugin.branch == branch
+
+
+def test_from_spec_with_name(owner: str, repo: str, name: str):
+ vim_plugin = PluginSpec.from_spec(f"{owner}/{repo}::{name}")
+
+ assert vim_plugin.name == name
+
+
+@pytest.mark.parametrize("host", RepositoryHost)
+def test_from_spec_with_repository_host(owner: str, repo: str, host: RepositoryHost):
+ vim_plugin = PluginSpec.from_spec(f"{host.value}:{owner}/{repo}")
+
+ assert vim_plugin.repository_host == host
+
+
+def test_from_spec_without_repository_host(owner: str, repo: str):
+ vim_plugin = PluginSpec.from_spec(f"{owner}/{repo}")
+
+ assert vim_plugin.repository_host == RepositoryHost.GITHUB
+
+
+def test_from_spec_complex(owner: str, repo: str, branch: str, name: str):
+ vim_plugin = PluginSpec.from_spec(f"gitlab:{owner}/{repo}:{branch}:{name}")
+
+ assert vim_plugin.repository_host == RepositoryHost.GITLAB
+ assert vim_plugin.owner == owner
+ assert vim_plugin.repo == repo
+ assert vim_plugin.branch == branch
+ assert vim_plugin.name == name
+
+
+def test_from_spec_invalid_spec():
+ with pytest.raises(ValueError):
+ PluginSpec.from_spec("invalid_spec")
+
+
+def test_to_spec_simple(owner: str, repo: str):
+ vim_plugin = PluginSpec(RepositoryHost.GITHUB, owner, repo)
+
+ assert vim_plugin.to_spec() == f"{owner}/{repo}"
+
+
+def test_to_spec_with_branch(owner: str, repo: str, branch: str):
+ vim_plugin = PluginSpec(RepositoryHost.GITHUB, owner, repo, branch=branch)
+ assert vim_plugin.to_spec() == f"{owner}/{repo}:{branch}"
+
+
+def test_to_spec_with_name(owner: str, repo: str, name: str):
+ vim_plugin = PluginSpec(RepositoryHost.GITHUB, owner, repo, name=name)
+
+ assert vim_plugin.to_spec() == f"{owner}/{repo}::{name}"
+
+
+@pytest.mark.parametrize("host", [RepositoryHost.GITLAB, RepositoryHost.SOURCEHUT])
+def test_to_spec_with_repository_host(host: RepositoryHost, owner: str, repo: str):
+ vim_plugin = PluginSpec(host, owner, repo)
+
+ assert vim_plugin.to_spec() == f"{host.value}:{owner}/{repo}"
+
+
+def test_to_spec_complex(owner: str, repo: str, branch: str, name: str, license: str):
+ vim_plugin = PluginSpec(RepositoryHost.GITLAB, owner, repo, branch=branch, name=name, license=license)
+
+ assert vim_plugin.to_spec() == f"gitlab:{owner}/{repo}:{branch}:{name}:{license}"
+
+
+def test_spec_equal(owner: str, repo: str):
+ vim_plugin = PluginSpec(RepositoryHost.GITHUB, owner, repo)
+ vim_plugin2 = PluginSpec(RepositoryHost.GITHUB, owner, repo)
+
+ assert vim_plugin == vim_plugin2
+
+
+def test_spec_not_equal_different_branch(owner: str, repo: str):
+ vim_plugin = PluginSpec(RepositoryHost.GITHUB, owner, repo)
+ vim_plugin2 = PluginSpec(RepositoryHost.GITHUB, owner, repo, branch="main")
+
+ assert vim_plugin != vim_plugin2
+
+
+def test_spec_not_equal_different_name(owner: str, repo: str):
+ vim_plugin = PluginSpec(RepositoryHost.GITHUB, owner, repo)
+ vim_plugin2 = PluginSpec(RepositoryHost.GITHUB, owner, repo, name="renamed")
+
+ assert vim_plugin != vim_plugin2
+
+
+def test_spec_equal_same_normalized_name(owner: str):
+ repo = "repo.nvim"
+ name = "repo-nvim"
+
+ vim_plugin = PluginSpec(RepositoryHost.GITHUB, owner, repo)
+ vim_plugin2 = PluginSpec(RepositoryHost.GITHUB, owner, repo, name=name)
+
+ assert vim_plugin == vim_plugin2
diff --git a/pkgs/sources/update_vim_plugins/update_vim_plugins/update.py b/pkgs/sources/update_vim_plugins/update_vim_plugins/update.py
new file mode 100644
index 00000000..7eb3eeb4
--- /dev/null
+++ b/pkgs/sources/update_vim_plugins/update_vim_plugins/update.py
@@ -0,0 +1,212 @@
+import subprocess
+from random import shuffle
+from cleo.helpers import argument, option
+from cleo.commands.command import Command
+from concurrent.futures import ThreadPoolExecutor, as_completed
+
+from pprint import pprint
+
+from .plugin import plugin_from_spec
+
+from .helpers import read_manifest_to_spec, get_const
+from .helpers import JSON_FILE, PLUGINS_LIST_FILE, PKGS_FILE
+
+import json
+import jsonpickle
+
+jsonpickle.set_encoder_options("json", sort_keys=True)
+
+
+class UpdateCommand(Command):
+ name = "update"
+ description = "Generate nix module from input file"
+ arguments = [argument("plug_dir", description="Path to the plugin directory", optional=False)]
+ options = [
+ option("all", "a", description="Update all plugins. Else only update new plugins", flag=True),
+ option("dry-run", "d", description="Show which plugins would be updated", flag=True),
+ ]
+
+ def handle(self):
+ """Main command function"""
+
+ plug_dir = self.argument("plug_dir")
+ self.specs = read_manifest_to_spec(plug_dir)
+
+ if self.option("all"):
+ # update all plugins
+ spec_list = self.specs
+ known_plugins = []
+ else:
+ # filter plugins we already know
+ spec_list = self.specs
+
+ with open(get_const(JSON_FILE, plug_dir), "r") as json_file:
+ data = json.load(json_file)
+
+ known_specs = list(filter(lambda x: x.line in data, spec_list))
+ known_plugins = [jsonpickle.decode(data[x.line]) for x in known_specs]
+
+ spec_list = list(filter(lambda x: x.line not in data, spec_list))
+
+ if self.option("dry-run"):
+ self.line("<comment>These plugins would be updated</comment>")
+ pprint(spec_list)
+ self.line(f"<info>Total:</info> {len(spec_list)}")
+ exit(0)
+
+ processed_plugins, failed_plugins, failed_but_known = self.process_manifest(spec_list, plug_dir)
+
+ processed_plugins += known_plugins # add plugins from .plugins.json
+ processed_plugins: list = sorted(set(processed_plugins)) # remove duplicates based only on source line
+
+ self.check_duplicates(processed_plugins)
+
+ if failed_plugins != []:
+ self.line("<error>Not processed:</error> The following plugins could not be updated")
+ for s, e in failed_plugins:
+ self.line(f" - {s!r} - {e}")
+
+ if failed_but_known != []:
+ self.line(
+ "<error>Not updated:</error> The following plugins could not be updated but an older version is known"
+ )
+ for s, e in failed_but_known:
+ self.line(f" - {s!r} - {e}")
+
+ # update plugin "database"
+ self.write_plugins_json(processed_plugins, plug_dir)
+
+ # generate output
+ self.write_plugins_nix(processed_plugins, plug_dir)
+
+ self.write_plugins_markdown(processed_plugins, plug_dir)
+
+ self.line("<comment>Done</comment>")
+
+ def write_plugins_markdown(self, plugins, plug_dir):
+ """Write the list of all plugins to PLUGINS_LIST_FILE in markdown"""
+
+ plugins.sort()
+
+ self.line("<info>Updating plugins.md</info>")
+
+ header = f" - Plugin count: {len(plugins)}\n\n| Repo | Last Update | Nix package name | Last checked |\n|:---|:---|:---|:---|\n"
+
+ with open(get_const(PLUGINS_LIST_FILE, plug_dir), "w") as file:
+ file.write(header)
+ for plugin in plugins:
+ file.write(f"{plugin.to_markdown()}\n")
+
+ def write_plugins_nix(self, plugins, plug_dir):
+ self.line("<info>Generating nix output</info>")
+
+ plugins.sort()
+
+ header = "{ lib, buildVimPlugin, fetchurl, fetchgit }: {"
+ footer = "}"
+
+ with open(get_const(PKGS_FILE, plug_dir), "w") as file:
+ file.write(header)
+ for plugin in plugins:
+ file.write(f"{plugin.to_nix()}\n")
+ file.write(footer)
+
+ self.line("<info>Formatting nix output</info>")
+
+ subprocess.run(
+ ["alejandra", get_const(PKGS_FILE, plug_dir)],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ )
+
+ def write_plugins_json(self, plugins, plug_dir):
+ self.line("<info>Storing results in .plugins.json</info>")
+
+ plugins.sort()
+
+ with open(get_const(JSON_FILE, plug_dir), "r+") as json_file:
+ data = json.load(json_file)
+
+ for plugin in plugins:
+ data.update({f"{plugin.source_line}": plugin.to_json()})
+
+ json_file.seek(0)
+ json_file.write(json.dumps(data, indent=2, sort_keys=True))
+ json_file.truncate()
+
+ def check_duplicates(self, plugins):
+ """check for duplicates in proccesed_plugins"""
+ error = False
+ for i, plugin in enumerate(plugins):
+ for p in plugins[i + 1 :]:
+ if plugin.name == p.name:
+ self.line(
+ f"<error>Error:</error> The following two lines produce the same plugin name:\n - {plugin.source_line}\n - {p.source_line}\n -> {p.name}"
+ )
+ error = True
+
+ # We want to exit if the resulting nix file would be broken
+ # But we want to go through all plugins before we do so
+ if error:
+ exit(1)
+
+ def generate_plugin(self, spec, i, size, plug_dir):
+ debug_string = ""
+
+ processed_plugin = None
+ failed_but_known = None
+ failed_plugin = None
+ try:
+ debug_string += f" - <info>({i+1}/{size}) Processing</info> {spec!r}\n"
+ vim_plugin = plugin_from_spec(spec)
+ debug_string += f" • <comment>Success</comment> {vim_plugin!r}\n"
+ processed_plugin = vim_plugin
+ except Exception as e:
+ debug_string += f" • <error>Error:</error> Could not update <info>{spec.name}</info>. Keeping old values. Reason: {e}\n"
+ with open(get_const(JSON_FILE, plug_dir), "r") as json_file:
+ data = json.load(json_file)
+
+ plugin_json = data.get(spec.line)
+ if plugin_json:
+ vim_plugin = jsonpickle.decode(plugin_json)
+ processed_plugin = vim_plugin
+ failed_but_known = (vim_plugin, e)
+ else:
+ debug_string += f" • <error>Error:</error> No entries for <info>{spec.name}</info> in '.plugins.json'. Skipping...\n"
+ failed_plugin = (spec, e)
+
+ self.line(debug_string.strip())
+
+ return processed_plugin, failed_plugin, failed_but_known
+
+ def process_manifest(self, spec_list, plug_dir):
+ """Read specs in 'spec_list' and generate plugins"""
+
+ size = len(spec_list)
+
+ # We have to assume that we will reach an api limit. Therefore
+ # we randomize the spec list to give every entry the same change to be updated and
+ # not favor those at the start of the list
+ shuffle(spec_list)
+
+ with ThreadPoolExecutor() as executor:
+ futures = [
+ executor.submit(self.generate_plugin, spec, i, size, plug_dir) for i, spec in enumerate(spec_list)
+ ]
+ results = [future.result() for future in as_completed(futures)]
+
+ processed_plugins = [r[0] for r in results]
+ failed_plugins = [r[1] for r in results]
+ failed_but_known = [r[2] for r in results]
+
+ processed_plugins = list(filter(lambda x: x is not None, processed_plugins))
+ failed_plugins = list(filter(lambda x: x is not None, failed_plugins))
+ failed_but_known = list(filter(lambda x: x is not None, failed_but_known))
+
+ processed_plugins.sort()
+ failed_plugins.sort()
+ failed_but_known.sort()
+
+ assert len(processed_plugins) == len(spec_list) - len(failed_plugins)
+
+ return processed_plugins, failed_plugins, failed_but_known
diff --git a/pkgs/sources/yt/.env b/pkgs/sources/yt/.env
new file mode 100755
index 00000000..8018a738
--- /dev/null
+++ b/pkgs/sources/yt/.env
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+
+PATH="$(pwd)/target/release/:$(pwd)/target/debug/:$PATH"
diff --git a/pkgs/sources/yt/.envrc b/pkgs/sources/yt/.envrc
new file mode 100644
index 00000000..3550a30f
--- /dev/null
+++ b/pkgs/sources/yt/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/pkgs/sources/yt/.gitignore b/pkgs/sources/yt/.gitignore
new file mode 100644
index 00000000..c84fa049
--- /dev/null
+++ b/pkgs/sources/yt/.gitignore
@@ -0,0 +1,3 @@
+# build dirs
+/target
+/result
diff --git a/pkgs/sources/yt/Cargo.lock b/pkgs/sources/yt/Cargo.lock
new file mode 100644
index 00000000..ef2a53fd
--- /dev/null
+++ b/pkgs/sources/yt/Cargo.lock
@@ -0,0 +1,640 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.83"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "bitflags"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "cc"
+version = "1.0.97"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
+
+[[package]]
+name = "cli-log"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d2ab00dc4c82ec28af25ac085aecc11ffeabf353755715a3113a7aa044ca5cc"
+dependencies = [
+ "chrono",
+ "file-size",
+ "log",
+ "proc-status",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
+[[package]]
+name = "errno"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
+
+[[package]]
+name = "file-size"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9544f10105d33957765016b8a9baea7e689bf1f0f2f32c2fa2f568770c38d2b3"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "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 = "idna"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
+
+[[package]]
+name = "itoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.154"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
+
+[[package]]
+name = "log"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proc-status"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0e0c0ac915e7b76b47850ba4ffc377abde6c6ff9eeace61d0a89623db449712"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "serde"
+version = "1.0.201"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.201"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "syn"
+version = "2.0.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+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 = "unicode-bidi"
+version = "0.3.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "url"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf8parse"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets",
+]
+
+[[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-targets"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
+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.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
+
+[[package]]
+name = "yt"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "cli-log",
+ "log",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "url",
+]
diff --git a/pkgs/sources/yt/Cargo.toml b/pkgs/sources/yt/Cargo.toml
new file mode 100644
index 00000000..7c17d20b
--- /dev/null
+++ b/pkgs/sources/yt/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "yt"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.83"
+clap = { version = "4.5.4", features = ["derive"] }
+cli-log = "2.0.0"
+log = "0.4.21"
+serde = { version = "1.0.201", features = ["derive"] }
+serde_json = "1.0.117"
+tempfile = "3.10.1"
+url = "2.5.0"
+
+# This is here to be able to tell nix which binary to build
+[features]
+yts = []
+ytc = []
+yt = []
+default = ["yt", "yts", "ytc"]
+
+[[bin]]
+name = "yts"
+required-features = ["yts"]
+
+[[bin]]
+name = "ytc"
+required-features = ["ytc"]
+
+[[bin]]
+name = "yt"
+required-features = ["yt"]
diff --git a/pkgs/sources/yt/default.nix b/pkgs/sources/yt/default.nix
new file mode 100644
index 00000000..32396051
--- /dev/null
+++ b/pkgs/sources/yt/default.nix
@@ -0,0 +1,51 @@
+[
+ (
+ final: prev: {
+ yt = import ./yt.nix {
+ inherit
+ (prev)
+ lib
+ makeWrapper
+ rustPlatform
+ # dependencies
+
+ ytcc
+ yt-dlp
+ mpv
+ ;
+ };
+ }
+ )
+ (
+ final: prev: {
+ yts = import ./yts.nix {
+ inherit
+ (prev)
+ lib
+ makeWrapper
+ rustPlatform
+ # dependencies
+
+ ytcc
+ ;
+ };
+ }
+ )
+ (
+ final: prev: {
+ ytc = import ./ytc.nix {
+ inherit
+ (prev)
+ lib
+ makeWrapper
+ rustPlatform
+ # dependencies
+
+ ytcc
+ yt-dlp
+ mpv
+ ;
+ };
+ }
+ )
+]
diff --git a/pkgs/sources/yt/flake.lock b/pkgs/sources/yt/flake.lock
new file mode 100644
index 00000000..50494465
--- /dev/null
+++ b/pkgs/sources/yt/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1710146030,
+ "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1715087517,
+ "narHash": "sha256-CLU5Tsg24Ke4+7sH8azHWXKd0CFd4mhLWfhYgUiDBpQ=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "b211b392b8486ee79df6cdfb1157ad2133427a29",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/pkgs/sources/yt/flake.nix b/pkgs/sources/yt/flake.nix
new file mode 100644
index 00000000..561b1c0d
--- /dev/null
+++ b/pkgs/sources/yt/flake.nix
@@ -0,0 +1,30 @@
+{
+ description = "yt";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+
+ outputs = {
+ self,
+ nixpkgs,
+ flake-utils,
+ }: (flake-utils.lib.eachDefaultSystem (system: let
+ pkgs = nixpkgs.legacyPackages."${system}";
+ in {
+ devShells.default = pkgs.mkShell {
+ packages = with pkgs; [
+ # rust stuff
+ cargo
+ clippy
+ rustc
+ rustfmt
+
+ cargo-edit
+ cargo-expand
+ ];
+ };
+ }));
+}
diff --git a/pkgs/sources/yt/src/bin/yt/main.rs b/pkgs/sources/yt/src/bin/yt/main.rs
new file mode 100644
index 00000000..37348834
--- /dev/null
+++ b/pkgs/sources/yt/src/bin/yt/main.rs
@@ -0,0 +1,91 @@
+use anyhow::{bail, Context, Result};
+use std::{
+ env, fs,
+ io::{BufRead, BufReader, BufWriter, Write},
+ process::Command as StdCmd,
+};
+use tempfile::Builder;
+use yt::{
+ constants::{last_select, HELP_STR},
+ downloader::Downloader,
+ filter_line, YtccListData,
+};
+
+fn main() -> Result<()> {
+ cli_log::init_cli_log!();
+
+ let json_map = {
+ let mut ytcc = StdCmd::new("ytcc");
+ ytcc.args([
+ "--output",
+ "json",
+ "list",
+ "--order-by",
+ "publish_date",
+ "desc",
+ ]);
+
+ serde_json::from_slice::<Vec<YtccListData>>(
+ &ytcc.output().context("Failed to json from ytcc")?.stdout,
+ )
+ .context("Failed to deserialize json output")?
+ };
+
+ let temp_file = Builder::new()
+ .prefix("yt_video_select-")
+ .suffix(".yts")
+ .rand_bytes(6)
+ .tempfile()
+ .context("Failed to get tempfile")?;
+
+ {
+ let mut edit_file = BufWriter::new(&temp_file);
+
+ json_map.iter().for_each(|line| {
+ let line = line.to_string();
+ edit_file
+ .write_all(line.as_bytes())
+ .expect("This write should not fail");
+ });
+
+ edit_file.write_all(HELP_STR.as_bytes())?;
+ edit_file.flush().context("Failed to flush edit file")?;
+
+ let mut nvim = StdCmd::new("nvim");
+ nvim.arg(temp_file.path());
+ let status = nvim.status().context("Falied to run nvim")?;
+ if !status.success() {
+ bail!("nvim exited with error status: {}", status)
+ }
+ }
+
+ let read_file = temp_file.reopen()?;
+ fs::copy(
+ temp_file.path(),
+ last_select().context("Failed to get the persistent selection file path")?,
+ )
+ .context("Failed to persist selection file")?;
+
+ let mut watching = Vec::new();
+ let reader = BufReader::new(&read_file);
+ for line in reader.lines() {
+ let line = line.context("Failed to read line")?;
+
+ if let Some(downloadable) =
+ filter_line(&line).with_context(|| format!("Failed to process line: '{}'", line))?
+ {
+ watching.push(downloadable);
+ }
+ }
+
+ if watching.is_empty() {
+ return Ok(());
+ }
+
+ let downloader = Downloader::new(watching).context("Failed to construct downloader")?;
+ downloader
+ .consume()
+ .context("Failed to consume downloader")?;
+
+ Ok(())
+}
diff --git a/pkgs/sources/yt/src/bin/ytc/args.rs b/pkgs/sources/yt/src/bin/ytc/args.rs
new file mode 100644
index 00000000..8b2d6a61
--- /dev/null
+++ b/pkgs/sources/yt/src/bin/ytc/args.rs
@@ -0,0 +1,26 @@
+use clap::{Parser, Subcommand};
+/// A helper for downloading and playing youtube videos
+#[derive(Parser, Debug)]
+#[clap(author, version, about, long_about = None)]
+pub struct Args {
+ #[command(subcommand)]
+ /// The subcommand to execute
+ pub subcommand: Command,
+}
+#[derive(Subcommand, Debug)]
+pub enum Command {
+ #[clap(value_parser)]
+ /// Work based of ytcc ids
+ Id {
+ #[clap(value_parser)]
+ /// A list of ids to play
+ ids: Vec<u32>,
+ },
+ #[clap(value_parser)]
+ /// Work based of raw youtube urls
+ Url {
+ #[clap(value_parser)]
+ /// A list of urls to play
+ urls: Vec<String>,
+ },
+}
diff --git a/pkgs/sources/yt/src/bin/ytc/main.rs b/pkgs/sources/yt/src/bin/ytc/main.rs
new file mode 100644
index 00000000..b38157df
--- /dev/null
+++ b/pkgs/sources/yt/src/bin/ytc/main.rs
@@ -0,0 +1,77 @@
+use std::{env, process::Command as StdCmd};
+
+use anyhow::{bail, Context, Result};
+use clap::Parser;
+use log::debug;
+use url::Url;
+use yt::{
+ downloader::{Downloadable, Downloader},
+ YtccListData,
+};
+
+use crate::args::{Args, Command};
+
+mod args;
+
+fn main() -> Result<()> {
+ let args = Args::parse();
+ cli_log::init_cli_log!();
+
+ let playspec: Vec<Downloadable> = match args.subcommand {
+ Command::Id { ids } => {
+ let mut output = Vec::with_capacity(ids.len());
+ for id in ids {
+ debug!("Adding {}", id);
+ let mut ytcc = StdCmd::new("ytcc");
+ ytcc.args([
+ "--output",
+ "json",
+ "list",
+ "--watched",
+ "--unwatched",
+ "--attributes",
+ "url",
+ "--ids",
+ id.to_string().as_str(),
+ ]);
+ let json = serde_json::from_slice::<Vec<YtccListData>>(
+ &ytcc.output().context("Failed to get url from id")?.stdout,
+ )
+ .context("Failed to deserialize json output")?;
+
+ if json.is_empty() {
+ bail!("Could not find a video with id: {}", id);
+ }
+ assert_eq!(json.len(), 1);
+ let json = json.first().expect("Has only one element");
+
+ debug!("Id resolved to: '{}'", &json.url);
+
+ output.push(Downloadable {
+ url: Url::parse(&json.url)?,
+ id: Some(json.id),
+ })
+ }
+ output
+ }
+ Command::Url { urls } => {
+ let mut output = Vec::with_capacity(urls.len());
+ for url in urls {
+ output.push(Downloadable {
+ url: Url::parse(&url).context("Failed to parse url")?,
+ id: None,
+ })
+ }
+ output
+ }
+ };
+
+ debug!("Initializing downloader");
+ let downloader = Downloader::new(playspec)?;
+
+ downloader
+ .consume()
+ .context("Failed to consume downloader")?;
+
+ Ok(())
+}
diff --git a/pkgs/sources/yt/src/bin/yts/args.rs b/pkgs/sources/yt/src/bin/yts/args.rs
new file mode 100644
index 00000000..56989421
--- /dev/null
+++ b/pkgs/sources/yt/src/bin/yts/args.rs
@@ -0,0 +1,41 @@
+use clap::{Parser, Subcommand};
+/// A helper for selecting which videos to download from ytcc to ytc
+#[derive(Parser, Debug)]
+#[clap(author, version, about, long_about = None)]
+pub struct Args {
+ #[command(subcommand)]
+ /// subcommand to execute
+ pub subcommand: Option<Command>,
+}
+
+#[derive(Subcommand, Debug)]
+pub enum Command {
+ #[clap(value_parser)]
+ /// Which ordering to use
+ Order {
+ #[command(subcommand)]
+ command: OrderCommand,
+ },
+}
+
+#[derive(Subcommand, Debug)]
+pub enum OrderCommand {
+ #[clap(value_parser)]
+ /// Order by date
+ #[group(required = true)]
+ Date {
+ #[arg(value_parser)]
+ /// Order descending
+ desc: bool,
+ #[clap(value_parser)]
+ /// Order ascending
+ asc: bool,
+ },
+ #[clap(value_parser)]
+ /// Pass a raw SQL 'ORDER BY' value
+ Raw {
+ #[clap(value_parser)]
+ /// The raw value(s) to pass
+ value: Vec<String>,
+ },
+}
diff --git a/pkgs/sources/yt/src/bin/yts/main.rs b/pkgs/sources/yt/src/bin/yts/main.rs
new file mode 100644
index 00000000..7398db61
--- /dev/null
+++ b/pkgs/sources/yt/src/bin/yts/main.rs
@@ -0,0 +1,91 @@
+use anyhow::{bail, Context, Result};
+use clap::Parser;
+use std::{
+ env,
+ io::{BufRead, BufReader, Write},
+ process::Command as StdCmd,
+};
+use tempfile::NamedTempFile;
+use yt::{constants::HELP_STR, filter_line, YtccListData};
+
+use crate::args::{Args, Command, OrderCommand};
+
+mod args;
+
+fn main() -> Result<()> {
+ let args = Args::parse();
+ cli_log::init_cli_log!();
+
+ let ordering = match args.subcommand.unwrap_or(Command::Order {
+ command: OrderCommand::Date {
+ desc: true,
+ asc: false,
+ },
+ }) {
+ Command::Order { command } => match command {
+ OrderCommand::Date { desc, asc } => {
+ if desc {
+ vec!["--order-by".into(), "publish_date".into(), "desc".into()]
+ } else if asc {
+ vec!["--order-by".into(), "publish_date".into(), "asc".into()]
+ } else {
+ vec!["--order-by".into(), "publish_date".into(), "desc".into()]
+ }
+ }
+ OrderCommand::Raw { value } => [vec!["--order-by".into()], value].concat(),
+ },
+ };
+
+ let json_map = {
+ let mut ytcc = StdCmd::new("ytcc");
+ ytcc.args(["--output", "json", "list"]);
+ ytcc.args(ordering);
+
+ serde_json::from_slice::<Vec<YtccListData>>(
+ &ytcc.output().context("Failed to json from ytcc")?.stdout,
+ )
+ .context("Failed to deserialize json output")?
+ };
+
+ let mut edit_file = NamedTempFile::new().context("Failed to get tempfile")?;
+
+ json_map.iter().for_each(|line| {
+ let line = line.to_string();
+ edit_file
+ .write_all(line.as_bytes())
+ .expect("This write should not fail");
+ });
+
+ write!(&edit_file, "{}", HELP_STR)?;
+ edit_file.flush().context("Failed to flush edit file")?;
+
+ let read_file = edit_file.reopen()?;
+
+ let mut nvim = StdCmd::new("nvim");
+ nvim.arg(edit_file.path());
+
+ let status = nvim.status().context("Falied to run nvim")?;
+ if !status.success() {
+ bail!("Nvim exited with error status: {}", status)
+ }
+
+ let mut watching = Vec::new();
+ let reader = BufReader::new(&read_file);
+ for line in reader.lines() {
+ let line = line.context("Failed to read line")?;
+
+ if let Some(downloadable) =
+ filter_line(&line).with_context(|| format!("Failed to process line: '{}'", line))?
+ {
+ watching.push(downloadable);
+ }
+ }
+
+ let watching: String = watching
+ .iter()
+ .map(|d| d.to_string())
+ .collect::<Vec<String>>()
+ .join("\n");
+ println!("{}", &watching);
+ Ok(())
+}
diff --git a/pkgs/sources/yt/src/constants.rs b/pkgs/sources/yt/src/constants.rs
new file mode 100644
index 00000000..5e233656
--- /dev/null
+++ b/pkgs/sources/yt/src/constants.rs
@@ -0,0 +1,51 @@
+use std::{env, fs, path::PathBuf};
+
+pub const HELP_STR: &str = include_str!("./help.str");
+
+pub const YT_DLP_FLAGS: [&str; 13] = [
+ // Ignore errors arising of unavailable sponsor block API
+ "--ignore-errors",
+ "--format",
+ "bestvideo[height<=?1080]+bestaudio/best",
+ "--embed-chapters",
+ "--progress",
+ "--write-comments",
+ "--extractor-args",
+ "youtube:max_comments=150,all,100;comment_sort=top",
+ "--write-info-json",
+ "--sponsorblock-mark",
+ "default",
+ "--sponsorblock-remove",
+ "sponsor",
+];
+pub const MPV_FLAGS: [&str; 4] = [
+ "--speed=2.7",
+ "--volume=75",
+ "--keep-open=yes",
+ "--msg-level=osd/libass=fatal",
+];
+
+pub const CONCURRENT: u32 = 5;
+
+pub const DOWNLOAD_DIR: &str = "/tmp/ytcc";
+
+fn get_runtime_path(component: &'static str) -> anyhow::Result<PathBuf> {
+ let out: PathBuf = format!(
+ "{}/{}",
+ env::var("XDG_RUNTIME_DIR").expect("This should always exist"),
+ component
+ )
+ .into();
+ fs::create_dir_all(out.parent().expect("Parent should exist"))?;
+ Ok(out)
+}
+
+const STATUS_PATH: &str = "ytcc/running";
+pub fn status_path() -> anyhow::Result<PathBuf> {
+ get_runtime_path(STATUS_PATH)
+}
+
+const LAST_SELECT: &str = "ytcc/selected.yts";
+pub fn last_select() -> anyhow::Result<PathBuf> {
+ get_runtime_path(LAST_SELECT)
+}
diff --git a/pkgs/sources/yt/src/downloader.rs b/pkgs/sources/yt/src/downloader.rs
new file mode 100644
index 00000000..e915700d
--- /dev/null
+++ b/pkgs/sources/yt/src/downloader.rs
@@ -0,0 +1,212 @@
+use std::{
+ fs::{self, canonicalize},
+ io::{stderr, stdout, Read},
+ mem,
+ os::unix::fs::symlink,
+ path::PathBuf,
+ process::Command,
+ sync::mpsc::{self, Receiver, Sender},
+ thread::{self, JoinHandle},
+};
+
+use anyhow::{bail, Context, Result};
+use log::{debug, error, warn};
+use url::Url;
+
+use crate::constants::{status_path, CONCURRENT, DOWNLOAD_DIR, MPV_FLAGS, YT_DLP_FLAGS};
+
+#[derive(Debug)]
+pub struct Downloadable {
+ pub url: Url,
+ pub id: Option<u32>,
+}
+
+impl std::fmt::Display for Downloadable {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ write!(
+ f,
+ "{}|{}",
+ self.url.as_str().replace('|', ";"),
+ self.id.unwrap_or(0),
+ )
+ }
+}
+
+pub struct Downloader {
+ sent: usize,
+ download_thread: JoinHandle<Result<()>>,
+ orx: Receiver<(PathBuf, Option<u32>)>,
+ itx: Option<Sender<Downloadable>>,
+ playspec: Vec<Downloadable>,
+}
+
+impl Downloader {
+ pub fn new(mut playspec: Vec<Downloadable>) -> anyhow::Result<Downloader> {
+ let (itx, irx): (Sender<Downloadable>, Receiver<Downloadable>) = mpsc::channel();
+ let (otx, orx) = mpsc::channel();
+ let jh = thread::spawn(move || -> Result<()> {
+ while let Ok(pt) = irx.recv() {
+ debug!("Got '{}' to be downloaded", pt);
+ let path = download_url(&pt.url)
+ .with_context(|| format!("Failed to download url: '{}'", &pt.url))?;
+ otx.send((path, pt.id)).expect("Should not be dropped");
+ }
+ debug!("Finished Downloading everything");
+ Ok(())
+ });
+
+ playspec.reverse();
+ let mut output = Downloader {
+ sent: 0,
+ download_thread: jh,
+ orx,
+ itx: Some(itx),
+ playspec,
+ };
+ if output.playspec.len() <= CONCURRENT as usize {
+ output.add(output.playspec.len() as u32)?;
+ } else {
+ output.add(CONCURRENT)?;
+ }
+ Ok(output)
+ }
+
+ pub fn add(&mut self, number_to_add: u32) -> Result<()> {
+ debug!("Adding {} to be downloaded concurrently", number_to_add);
+ for _ in 0..number_to_add {
+ let pt = self.playspec.pop().expect("This call should be guarded");
+ self.itx.as_ref().expect("Should still be valid").send(pt)?;
+ self.sent += 1;
+ }
+ Ok(())
+ }
+
+ /// Return the next video already downloaded, will block until the download is complete
+ pub fn next(&mut self) -> Option<(PathBuf, Option<u32>)> {
+ debug!("Requesting next output");
+ match self.orx.recv() {
+ Ok(ok) => {
+ debug!("Output downloaded to: {}", ok.0.display());
+ if !self.playspec.is_empty() {
+ self.add(1).ok()?;
+ } else {
+ debug!(
+ "Done sending videos to be downloaded, downoladed: {} videos",
+ self.sent
+ );
+ let itx = mem::take(&mut self.itx);
+ drop(itx)
+ }
+ debug!("Returning: {}|{}", ok.0.display(), ok.1.unwrap_or(0));
+ Some(ok)
+ }
+ Err(err) => {
+ debug!("Received error while listening: {}", err);
+ None
+ }
+ }
+ }
+
+ pub fn drop(self) -> anyhow::Result<()> {
+ // Check that we really downloaded everything
+ assert_eq!(self.playspec.len(), 0);
+ match self.download_thread.join() {
+ Ok(ok) => ok,
+ Err(err) => panic!("Failed to join downloader thread: '{:#?}'", err),
+ }
+ }
+
+ pub fn consume(mut self) -> anyhow::Result<()> {
+ while let Some((path, id)) = self.next() {
+ debug!("Next path to play is: '{}'", path.display());
+ let mut info_json = canonicalize(&path).context("Failed to canoncialize path")?;
+ info_json.set_extension("info.json");
+
+ if status_path()?.is_symlink() {
+ fs::remove_file(status_path()?).context("Failed to delete old status file")?;
+ } else if !status_path()?.exists() {
+ debug!(
+ "The status path at '{}' does not exists",
+ status_path()?.display()
+ );
+ } else {
+ bail!(
+ "The status path ('{}') is not a symlink but exists!",
+ status_path()?.display()
+ );
+ }
+
+ symlink(info_json, status_path()?).context("Failed to symlink")?;
+
+ let mut mpv = Command::new("mpv");
+ mpv.stdout(stdout());
+ mpv.stderr(stderr());
+ mpv.args(MPV_FLAGS);
+ // TODO: Set the title to the name of the video, not the path <2024-02-09>
+ // mpv.arg(format!("--title="))
+ mpv.arg(&path);
+
+ let status = mpv.status().context("Failed to run mpv")?;
+ if status.success() {
+ fs::remove_file(&path)?;
+ if let Some(id) = id {
+ println!("\x1b[32;1mMarking {} as watched!\x1b[0m", id);
+ let mut ytcc = std::process::Command::new("ytcc");
+ ytcc.stdout(stdout());
+ ytcc.stderr(stderr());
+ ytcc.args(["mark"]);
+ ytcc.arg(id.to_string());
+ let status = ytcc.status().context("Failed to run ytcc")?;
+ if let Some(code) = status.code() {
+ if code != 0 {
+ bail!("Ytcc failed with status: {}", code);
+ }
+ }
+ }
+ debug!("mpv exited with: '{}'", status);
+ } else {
+ warn!("mpv exited with: '{}'", status);
+ }
+ }
+ self.drop()?;
+ Ok(())
+ }
+}
+
+fn download_url(url: &Url) -> Result<PathBuf> {
+ let output_file = tempfile::NamedTempFile::new().context("Failed to create tempfile")?;
+ output_file
+ .as_file()
+ .set_len(0)
+ .context("Failed to truncate temp-file")?;
+ if !Into::<PathBuf>::into(DOWNLOAD_DIR).exists() {
+ fs::create_dir_all(DOWNLOAD_DIR)
+ .with_context(|| format!("Failed to create download dir at: {}", DOWNLOAD_DIR))?
+ }
+ let mut yt_dlp = Command::new("yt-dlp");
+ yt_dlp.current_dir(DOWNLOAD_DIR);
+ yt_dlp.stdout(stdout());
+ yt_dlp.stderr(stderr());
+ yt_dlp.args(YT_DLP_FLAGS);
+ yt_dlp.args([
+ "--output",
+ "%(channel)s/%(title)s.%(ext)s",
+ url.as_str(),
+ "--print-to-file",
+ "after_move:filepath",
+ ]);
+ yt_dlp.arg(output_file.path().as_os_str());
+
+ let status = yt_dlp.status().context("Failed to run yt-dlp")?;
+ if !status.success() {
+ error!("yt-dlp execution failed with error: '{}'", status);
+ }
+
+ let mut path = String::new();
+ output_file
+ .as_file()
+ .read_to_string(&mut path)
+ .context("Failed to read output file temp file")?;
+ let path = path.trim();
+ Ok(path.into())
+}
diff --git a/pkgs/sources/yt/src/help.str b/pkgs/sources/yt/src/help.str
new file mode 100644
index 00000000..130fe42a
--- /dev/null
+++ b/pkgs/sources/yt/src/help.str
@@ -0,0 +1,8 @@
+# Commands:
+# w, watch = watch id
+# d, drop = mark id as watched
+# u, url = open the associated URL in the `timesinks.youtube` Firefox profile
+# p, pick = leave id as is; This is a noop
+#
+# These lines can be re-ordered; they are executed from top to bottom.
+# vim: filetype=yts conceallevel=2 concealcursor=nc colorcolumn=
diff --git a/pkgs/sources/yt/src/lib.rs b/pkgs/sources/yt/src/lib.rs
new file mode 100644
index 00000000..b089c1a2
--- /dev/null
+++ b/pkgs/sources/yt/src/lib.rs
@@ -0,0 +1,185 @@
+use anyhow::{bail, Context};
+use downloader::Downloadable;
+use serde::Deserialize;
+use url::Url;
+
+pub mod constants;
+pub mod downloader;
+
+#[derive(Deserialize)]
+pub struct YtccListData {
+ pub url: String,
+ pub title: String,
+ pub description: String,
+ pub publish_date: String,
+ pub watch_date: Option<f64>,
+ pub duration: String,
+ pub thumbnail_url: Option<String>,
+ pub extractor_hash: String,
+ pub id: u32,
+ pub playlists: Vec<YtccPlaylistData>,
+}
+
+impl std::fmt::Display for YtccListData {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ write!(
+ f,
+ r#"pick {} "{}" "{}" "{}" "{}" "{}"{}"#,
+ self.id,
+ self.title.replace(['"', '„', '”'], "'"),
+ self.publish_date,
+ self.playlists
+ .iter()
+ .map(|p| p.name.replace('"', "'"))
+ .collect::<Vec<String>>()
+ .join(", "),
+ Duration::from(self.duration.trim()),
+ self.url.replace('"', "'"),
+ "\n"
+ )
+ }
+}
+
+#[derive(Deserialize)]
+pub struct YtccPlaylistData {
+ pub name: String,
+ pub url: String,
+ pub reverse: bool,
+}
+
+pub enum LineCommand {
+ Pick,
+ Drop,
+ Watch,
+ Url,
+}
+
+impl std::str::FromStr for LineCommand {
+ type Err = anyhow::Error;
+ fn from_str(v: &str) -> Result<Self, <Self as std::str::FromStr>::Err> {
+ match v {
+ "pick" | "p" => Ok(Self::Pick),
+ "drop" | "d" => Ok(Self::Drop),
+ "watch" | "w" => Ok(Self::Watch),
+ "url" | "u" => Ok(Self::Url),
+ other => bail!("'{}' is not a recognized command!", other),
+ }
+ }
+}
+
+pub struct Line {
+ pub cmd: LineCommand,
+ pub id: u32,
+ pub url: Url,
+}
+
+/// We expect that each line is correctly formatted, and simply use default ones if they are not
+impl From<&str> for Line {
+ fn from(v: &str) -> Self {
+ let buf: Vec<_> = v.split_whitespace().collect();
+ let url: Url = Url::parse(
+ buf.last()
+ .expect("This should always exists")
+ .trim_matches('"'),
+ )
+ .expect("This parsing should work,as the url is generated");
+
+ Line {
+ cmd: buf
+ .get(0)
+ .unwrap_or(&"pick")
+ .parse()
+ .unwrap_or(LineCommand::Pick),
+ id: buf.get(1).unwrap_or(&"0").parse().unwrap_or(0),
+ url,
+ }
+ }
+}
+
+pub struct Duration {
+ time: u32,
+}
+
+impl From<&str> for Duration {
+ fn from(v: &str) -> Self {
+ let buf: Vec<_> = v.split(':').take(2).collect();
+ Self {
+ time: (buf[0].parse::<u32>().expect("Should be a number") * 60)
+ + buf[1].parse::<u32>().expect("Should be a number"),
+ }
+ }
+}
+
+impl std::fmt::Display for Duration {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ const SECOND: u32 = 1;
+ const MINUTE: u32 = 60 * SECOND;
+ const HOUR: u32 = 60 * MINUTE;
+
+ let base_hour = self.time - (self.time % HOUR);
+ let base_min = (self.time % HOUR) - ((self.time % HOUR) % MINUTE);
+ let base_sec = (self.time % HOUR) % MINUTE;
+
+ let h = base_hour / HOUR;
+ let m = base_min / MINUTE;
+ let s = base_sec / SECOND;
+
+ if self.time == 0 {
+ write!(f, "[No Duration]")
+ } else if h > 0 {
+ write!(f, "[{h}h {m}m]")
+ } else {
+ write!(f, "[{m}m {s}s]")
+ }
+ }
+}
+#[cfg(test)]
+mod test {
+ use crate::Duration;
+
+ #[test]
+ fn test_display_duration_1h() {
+ let dur = Duration { time: 60 * 60 };
+ assert_eq!("[1h 0m]".to_owned(), dur.to_string());
+ }
+ #[test]
+ fn test_display_duration_30min() {
+ let dur = Duration { time: 60 * 30 };
+ assert_eq!("[30m 0s]".to_owned(), dur.to_string());
+ }
+}
+
+pub fn ytcc_drop(id: u32) -> anyhow::Result<()> {
+ let mut ytcc = std::process::Command::new("ytcc");
+ ytcc.args(["mark", &format!("{}", id)]);
+ if !ytcc.status().context("Failed to run ytcc")?.success() {
+ bail!("`ytcc mark {}` failed to execute", id)
+ }
+ Ok(())
+}
+
+pub fn filter_line(line: &str) -> anyhow::Result<Option<Downloadable>> {
+ // Filter out comments and empty lines
+ if line.starts_with('#') || line.trim().is_empty() {
+ return Ok(None);
+ }
+
+ let line = Line::from(line);
+ match line.cmd {
+ LineCommand::Pick => Ok(None),
+ LineCommand::Drop => ytcc_drop(line.id)
+ .with_context(|| format!("Failed to drop: {}", line.id))
+ .map(|_| None),
+ LineCommand::Watch => Ok(Some(Downloadable {
+ id: Some(line.id),
+ url: line.url,
+ })),
+ LineCommand::Url => {
+ let mut firefox = std::process::Command::new("firefox");
+ firefox.args(["-P", "timesinks.youtube"]);
+ firefox.arg(line.url.as_str());
+ let _handle = firefox.spawn().context("Failed to run firefox")?;
+ Ok(None)
+ }
+ }
+}
diff --git a/pkgs/sources/yt/todo b/pkgs/sources/yt/todo
new file mode 100644
index 00000000..3f22042c
--- /dev/null
+++ b/pkgs/sources/yt/todo
@@ -0,0 +1 @@
+subtitles
diff --git a/pkgs/sources/yt/update.sh b/pkgs/sources/yt/update.sh
new file mode 100755
index 00000000..e500bb23
--- /dev/null
+++ b/pkgs/sources/yt/update.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+nix flake update
+
+[ "$1" = "upgrade" ] && cargo upgrade
+cargo update
+
+# vim: ft=sh
diff --git a/pkgs/sources/yt/yt.nix b/pkgs/sources/yt/yt.nix
new file mode 100644
index 00000000..aaa971c3
--- /dev/null
+++ b/pkgs/sources/yt/yt.nix
@@ -0,0 +1,29 @@
+{
+ lib,
+ rustPlatform,
+ ytcc,
+ yt-dlp,
+ mpv,
+ makeWrapper,
+}:
+rustPlatform.buildRustPackage {
+ pname = "yt";
+ version = "0.1.0";
+
+ src = ./.;
+ cargoLock = {
+ lockFile = ./Cargo.lock;
+ };
+
+ buildNoDefaultFeatures = true;
+ buildFeatures = ["yt"];
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ postInstall = ''
+ wrapProgram $out/bin/yt \
+ --prefix PATH : ${lib.makeBinPath [mpv yt-dlp ytcc]}
+ '';
+}
diff --git a/pkgs/sources/yt/ytc.nix b/pkgs/sources/yt/ytc.nix
new file mode 100644
index 00000000..dff5bcf8
--- /dev/null
+++ b/pkgs/sources/yt/ytc.nix
@@ -0,0 +1,29 @@
+{
+ lib,
+ rustPlatform,
+ ytcc,
+ yt-dlp,
+ mpv,
+ makeWrapper,
+}:
+rustPlatform.buildRustPackage {
+ pname = "ytc";
+ version = "0.1.0";
+
+ src = ./.;
+ cargoLock = {
+ lockFile = ./Cargo.lock;
+ };
+
+ buildNoDefaultFeatures = true;
+ buildFeatures = ["ytc"];
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ postInstall = ''
+ wrapProgram $out/bin/ytc \
+ --set PATH ${lib.makeBinPath [mpv yt-dlp ytcc]}
+ '';
+}
diff --git a/pkgs/sources/yt/yts.nix b/pkgs/sources/yt/yts.nix
new file mode 100644
index 00000000..9a8b172e
--- /dev/null
+++ b/pkgs/sources/yt/yts.nix
@@ -0,0 +1,27 @@
+{
+ lib,
+ rustPlatform,
+ ytcc,
+ makeWrapper,
+}:
+rustPlatform.buildRustPackage {
+ pname = "yts";
+ version = "0.1.0";
+
+ src = ./.;
+ cargoLock = {
+ lockFile = ./Cargo.lock;
+ };
+
+ buildNoDefaultFeatures = true;
+ buildFeatures = ["yts"];
+
+ nativeBuildInputs = [
+ makeWrapper
+ ];
+
+ postInstall = ''
+ wrapProgram $out/bin/yts \
+ --prefix PATH : ${lib.makeBinPath [ytcc]}
+ '';
+}