This works so far
This commit is contained in:
commit
e7dedf3e02
|
@ -0,0 +1 @@
|
|||
target/
|
|
@ -0,0 +1,769 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[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 = "anstream"
|
||||
version = "0.6.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[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.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"futures-core",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
|
||||
|
||||
[[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 = "serde"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[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.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.47.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"io-uring",
|
||||
"libc",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"socket2",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "where-cmd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"inotify",
|
||||
"memmap2",
|
||||
"regex",
|
||||
"serde",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "where-cmd"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
memmap2 = "0.9"
|
||||
inotify = "0.10"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
walkdir = "2.0"
|
||||
regex = "1.0"
|
||||
chrono = "0.4"
|
|
@ -0,0 +1,16 @@
|
|||
default:
|
||||
just -l
|
||||
|
||||
run mode="quiet":
|
||||
#! /bin/bash
|
||||
case "{{ mode }}" in
|
||||
quiet)
|
||||
RUSTFLAGS="-A warnings" cargo run --quiet
|
||||
;;
|
||||
*)
|
||||
cargo run
|
||||
;;
|
||||
esac
|
||||
|
||||
build:
|
||||
cargo build --release
|
|
@ -0,0 +1,59 @@
|
|||
pub mod tree;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "where", about = "Universal system file and package mapper")]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub enum Commands {
|
||||
/// Scan system and build/update database
|
||||
Scan {
|
||||
#[arg(short, long, help = "Force full rescan")]
|
||||
force: bool,
|
||||
},
|
||||
|
||||
/// Show file information
|
||||
File {
|
||||
path: String,
|
||||
},
|
||||
|
||||
/// List files in tree format for packages
|
||||
Tree {
|
||||
#[arg(long, help = "Show tree for specific package")]
|
||||
package: Option<String>,
|
||||
|
||||
#[arg(short, long, help = "Maximum depth")]
|
||||
depth: Option<usize>,
|
||||
|
||||
#[arg(short, long, help = "Filter by source type")]
|
||||
source: Option<String>,
|
||||
},
|
||||
|
||||
/// Search for files
|
||||
Find {
|
||||
#[arg(short, long, help = "File name pattern")]
|
||||
name: Option<String>,
|
||||
|
||||
#[arg(short, long, help = "Package name")]
|
||||
package: Option<String>,
|
||||
|
||||
#[arg(short, long, help = "Files larger than size")]
|
||||
size: Option<String>,
|
||||
},
|
||||
|
||||
/// Show package information
|
||||
Package {
|
||||
name: String,
|
||||
},
|
||||
|
||||
/// List all packages
|
||||
Packages {
|
||||
#[arg(short, long, help = "Filter by source")]
|
||||
source: Option<String>,
|
||||
},
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
use crate::{PackageManager, PackageSource, WhereError};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn print_package_tree(manager: &dyn PackageManager, package_name: &str) -> Result<(), WhereError> {
|
||||
// Get package info first
|
||||
let package_info = match manager.get_package_info(package_name)? {
|
||||
Some(info) => info,
|
||||
None => {
|
||||
println!("Package '{}' not found", package_name);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
// Print package header
|
||||
println!("{}{} {}\x1b[0m",
|
||||
package_info.source.color_code(),
|
||||
package_info.name,
|
||||
package_info.version);
|
||||
|
||||
// Get all files for this package
|
||||
let files = manager.get_package_files(package_name)?;
|
||||
|
||||
if files.is_empty() {
|
||||
println!(" (no files found)");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Build directory tree structure
|
||||
let tree = build_tree_structure(&files);
|
||||
|
||||
// Print the tree
|
||||
print_tree_node(&tree, "", true);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TreeNode {
|
||||
name: String,
|
||||
is_file: bool,
|
||||
children: HashMap<String, TreeNode>,
|
||||
}
|
||||
|
||||
impl TreeNode {
|
||||
fn new(name: String, is_file: bool) -> Self {
|
||||
TreeNode {
|
||||
name,
|
||||
is_file,
|
||||
children: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_tree_structure(files: &[std::path::PathBuf]) -> TreeNode {
|
||||
let mut root = TreeNode::new("".to_string(), false);
|
||||
|
||||
for file_path in files {
|
||||
let path_str = file_path.to_string_lossy();
|
||||
let components: Vec<&str> = path_str.split('/').filter(|s| !s.is_empty()).collect();
|
||||
|
||||
let mut current = &mut root;
|
||||
|
||||
for (i, component) in components.iter().enumerate() {
|
||||
let is_last = i == components.len() - 1;
|
||||
let is_file = is_last && file_path.is_file();
|
||||
|
||||
current.children
|
||||
.entry(component.to_string())
|
||||
.or_insert_with(|| TreeNode::new(component.to_string(), is_file));
|
||||
|
||||
current = current.children.get_mut(*component).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
fn print_tree_node(node: &TreeNode, prefix: &str, is_last: bool) {
|
||||
if !node.name.is_empty() {
|
||||
let connector = if is_last { "└── " } else { "├── " };
|
||||
let file_indicator = if node.is_file { "" } else { "/" };
|
||||
|
||||
println!("{}{}{}{}", prefix, connector, node.name, file_indicator);
|
||||
}
|
||||
|
||||
let mut children: Vec<_> = node.children.iter().collect();
|
||||
children.sort_by_key(|(name, child)| (!child.is_file, name.to_lowercase()));
|
||||
|
||||
for (i, (_, child)) in children.iter().enumerate() {
|
||||
let is_child_last = i == children.len() - 1;
|
||||
let child_prefix = if node.name.is_empty() {
|
||||
prefix.to_string()
|
||||
} else {
|
||||
format!("{}{} ", prefix, if is_last { " " } else { "│" })
|
||||
};
|
||||
|
||||
print_tree_node(child, &child_prefix, is_child_last);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
use memmap2::Mmap;
|
||||
use std::fs::File;
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct DatabaseHeader {
|
||||
magic: [u8; 8], // "WHEREDB\0"
|
||||
version: u32,
|
||||
created: u64, // Unix timestamp
|
||||
file_count: u64,
|
||||
package_count: u32,
|
||||
files_offset: u64,
|
||||
packages_offset: u64,
|
||||
strings_offset: u64,
|
||||
index_offset: u64,
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct FileRecord {
|
||||
path_hash: u64, // FNV-1a hash for quick lookups
|
||||
package_id: u32,
|
||||
source: u8,
|
||||
permissions: u16,
|
||||
size: u64,
|
||||
mtime: u64,
|
||||
path_offset: u32, // offset into string pool
|
||||
}
|
||||
|
||||
#[repr(C, packed)]
|
||||
pub struct PackageRecord {
|
||||
id: u32,
|
||||
source: u8,
|
||||
install_time: u64,
|
||||
name_offset: u32,
|
||||
version_offset: u32,
|
||||
file_count: u32,
|
||||
first_file_idx: u32,
|
||||
}
|
||||
|
||||
pub struct WhereDatabase {
|
||||
_file: File,
|
||||
mmap: Mmap,
|
||||
header: &'static DatabaseHeader,
|
||||
}
|
||||
|
||||
impl WhereDatabase {
|
||||
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, WhereError> {
|
||||
let file = File::open(path)?;
|
||||
let mmap = unsafe { memmap2::MmapOptions::new().map(&file)? };
|
||||
|
||||
let header = unsafe {
|
||||
&*(mmap.as_ptr() as *const DatabaseHeader)
|
||||
};
|
||||
|
||||
// Validate magic number
|
||||
if &header.magic != b"WHEREDB\0" {
|
||||
return Err(WhereError::InvalidDatabase);
|
||||
}
|
||||
|
||||
Ok(WhereDatabase {
|
||||
_file: file,
|
||||
mmap,
|
||||
header,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_file(&self, path: &str) -> Option<FileInfo> {
|
||||
let hash = fnv1a_hash(path.as_bytes());
|
||||
|
||||
// Binary search through sorted file records
|
||||
let files = self.get_file_records();
|
||||
match files.binary_search_by_key(&hash, |record| record.path_hash) {
|
||||
Ok(idx) => {
|
||||
let record = &files[idx];
|
||||
Some(FileInfo {
|
||||
path: self.get_string(record.path_offset),
|
||||
package: self.get_package_name(record.package_id),
|
||||
source: PackageSource::from_u8(record.source),
|
||||
size: record.size,
|
||||
modified: record.mtime,
|
||||
})
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
pub mod package_managers;
|
||||
pub mod cli;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use package_managers::{detect_available_managers, PackageManager, PackageSource};
|
||||
use cli::{Cli, Commands};
|
||||
use clap::Parser;
|
||||
|
||||
// Define the types that your modules need
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PackageInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub source: PackageSource,
|
||||
pub install_time: u64,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WhereError {
|
||||
Io(std::io::Error),
|
||||
Utf8(std::string::FromUtf8Error),
|
||||
CommandFailed(String),
|
||||
PackageNotFound(String),
|
||||
ManagerNotAvailable(String),
|
||||
InvalidDatabase,
|
||||
}
|
||||
|
||||
// Implement From traits for error conversion
|
||||
impl From<std::io::Error> for WhereError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
WhereError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::string::FromUtf8Error> for WhereError {
|
||||
fn from(err: std::string::FromUtf8Error) -> Self {
|
||||
WhereError::Utf8(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for WhereError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WhereError::Io(e) => write!(f, "IO error: {}", e),
|
||||
WhereError::Utf8(e) => write!(f, "UTF-8 error: {}", e),
|
||||
WhereError::CommandFailed(s) => write!(f, "Command failed: {}", s),
|
||||
WhereError::PackageNotFound(s) => write!(f, "Package not found: {}", s),
|
||||
WhereError::ManagerNotAvailable(s) => write!(f, "Manager not available: {}", s),
|
||||
WhereError::InvalidDatabase => write!(f, "Invalid database format"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for WhereError {}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cli = Cli::parse();
|
||||
let managers = detect_available_managers();
|
||||
|
||||
match cli.command {
|
||||
Commands::Tree { depth, package, source } => {
|
||||
handle_tree_command(&managers, depth, package, source)?;
|
||||
}
|
||||
Commands::Package { name } => {
|
||||
handle_package_command(&managers, &name)?;
|
||||
}
|
||||
Commands::Packages { source } => {
|
||||
handle_packages_command(&managers, source)?;
|
||||
}
|
||||
Commands::File { path } => {
|
||||
handle_file_command(&managers, &path)?;
|
||||
}
|
||||
Commands::Find { name, package, size } => {
|
||||
handle_find_command(&managers, name, package, size)?;
|
||||
}
|
||||
Commands::Scan { force } => {
|
||||
handle_scan_command(&managers, force)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_tree_command(
|
||||
managers: &[Box<dyn PackageManager>],
|
||||
_depth: Option<usize>,
|
||||
package_filter: Option<String>,
|
||||
_source_filter: Option<String>
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(dnf) = managers.iter().find(|m| m.name() == "dnf") {
|
||||
if let Some(package_name) = package_filter {
|
||||
// Show tree for specific package
|
||||
cli::tree::print_package_tree(dnf.as_ref(), &package_name)?;
|
||||
} else {
|
||||
// Show tree for first few packages as demo
|
||||
println!("Package file trees (first 5 packages):");
|
||||
let packages = dnf.get_installed_packages()?;
|
||||
for package in packages.iter().take(5) {
|
||||
cli::tree::print_package_tree(dnf.as_ref(), &package.name)?;
|
||||
println!(); // Empty line between packages
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("No DNF package manager found");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_package_command(
|
||||
managers: &[Box<dyn PackageManager>],
|
||||
name: &str
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(dnf) = managers.iter().find(|m| m.name() == "dnf") {
|
||||
match dnf.get_package_info(name) {
|
||||
Ok(Some(info)) => {
|
||||
println!("Package: {}", info.name);
|
||||
println!("Version: {}", info.version);
|
||||
println!("Source: {}", info.source.name());
|
||||
println!("Install time: {}", info.install_time);
|
||||
println!("Size: {} bytes", info.size);
|
||||
|
||||
// Show file tree for this package
|
||||
println!("\nFiles:");
|
||||
cli::tree::print_package_tree(dnf.as_ref(), name)?;
|
||||
}
|
||||
Ok(None) => println!("Package '{}' not found", name),
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_packages_command(
|
||||
managers: &[Box<dyn PackageManager>],
|
||||
_source_filter: Option<String>
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(dnf) = managers.iter().find(|m| m.name() == "dnf") {
|
||||
let packages = dnf.get_installed_packages()?;
|
||||
println!("Installed packages ({} total):", packages.len());
|
||||
for package in packages.iter() {
|
||||
println!(" {} {} ({})",
|
||||
package.name,
|
||||
package.version,
|
||||
package.source.name());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_file_command(
|
||||
managers: &[Box<dyn PackageManager>],
|
||||
path: &str
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(dnf) = managers.iter().find(|m| m.name() == "dnf") {
|
||||
match dnf.get_file_owner(std::path::Path::new(path)) {
|
||||
Ok(Some(owner)) => println!("{} is owned by package: {}", path, owner),
|
||||
Ok(None) => println!("{} is not owned by any package", path),
|
||||
Err(e) => println!("Error checking {}: {}", path, e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_find_command(
|
||||
managers: &[Box<dyn PackageManager>],
|
||||
_name: Option<String>,
|
||||
package: Option<String>,
|
||||
_size: Option<String>
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(package_name) = package {
|
||||
if let Some(dnf) = managers.iter().find(|m| m.name() == "dnf") {
|
||||
match dnf.get_package_files(&package_name) {
|
||||
Ok(files) => {
|
||||
println!("Files in package '{}':", package_name);
|
||||
for file in files {
|
||||
println!(" {}", file.display());
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("Find command needs more specific criteria");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_scan_command(
|
||||
_managers: &[Box<dyn PackageManager>],
|
||||
_force: bool
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Scan functionality not yet implemented");
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use crate::{PackageInfo, WhereError};
|
||||
use super::{PackageManager, PackageSource};
|
||||
|
||||
pub struct DnfManager;
|
||||
|
||||
impl PackageManager for DnfManager {
|
||||
fn name(&self) -> &'static str { "dnf" }
|
||||
|
||||
fn source_type(&self) -> PackageSource {
|
||||
PackageSource::Dnf
|
||||
}
|
||||
|
||||
fn is_available(&self) -> bool {
|
||||
Path::new("/usr/bin/dnf").exists() || Path::new("/usr/bin/rpm").exists()
|
||||
}
|
||||
|
||||
fn get_installed_packages(&self) -> Result<Vec<PackageInfo>, WhereError> {
|
||||
// Use rpm directly for better performance and parsing
|
||||
let output = Command::new("rpm")
|
||||
.args(["-qa", "--queryformat", "%{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{SIZE}\n"])
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(WhereError::CommandFailed("rpm query failed".to_string()));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
Ok(stdout.lines()
|
||||
.filter_map(|line| {
|
||||
let parts: Vec<&str> = line.split('\t').collect();
|
||||
if parts.len() >= 4 {
|
||||
Some(PackageInfo {
|
||||
name: parts[0].to_string(),
|
||||
version: parts[1].to_string(),
|
||||
source: PackageSource::Dnf,
|
||||
install_time: parts[2].parse().unwrap_or(0),
|
||||
size: parts[3].parse().unwrap_or(0),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn get_package_files(&self, package: &str) -> Result<Vec<PathBuf>, WhereError> {
|
||||
let output = Command::new("rpm")
|
||||
.args(["-ql", package])
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(WhereError::PackageNotFound(package.to_string()));
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
Ok(stdout.lines()
|
||||
.filter(|line| !line.is_empty())
|
||||
.map(|line| PathBuf::from(line.trim()))
|
||||
.filter(|path| path.exists()) // Only include files that actually exist
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn get_file_owner(&self, path: &Path) -> Result<Option<String>, WhereError> {
|
||||
let output = Command::new("rpm")
|
||||
.args(["-qf", &path.to_string_lossy()])
|
||||
.output()?;
|
||||
|
||||
if output.status.success() {
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
let package_name = stdout.trim();
|
||||
|
||||
// RPM returns full package name with version, extract just the name
|
||||
if let Some(base_name) = package_name.split('-').next() {
|
||||
return Ok(Some(base_name.to_string()));
|
||||
}
|
||||
|
||||
Ok(Some(package_name.to_string()))
|
||||
} else {
|
||||
// Check if it's a "file not owned by any package" error vs other errors
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
if stderr.contains("is not owned by any package") {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(WhereError::CommandFailed(format!("rpm query failed: {}", stderr)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_package_info(&self, package: &str) -> Result<Option<PackageInfo>, WhereError> {
|
||||
let output = Command::new("rpm")
|
||||
.args(["-qi", package])
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
let mut name = String::new();
|
||||
let mut version = String::new();
|
||||
let mut install_time = 0u64;
|
||||
let mut size = 0u64;
|
||||
|
||||
for line in stdout.lines() {
|
||||
if line.starts_with("Name : ") {
|
||||
name = line.strip_prefix("Name : ").unwrap_or("").to_string();
|
||||
} else if line.starts_with("Version : ") {
|
||||
let version_part = line.strip_prefix("Version : ").unwrap_or("");
|
||||
// Get release info too
|
||||
if let Some(release_line) = stdout.lines()
|
||||
.find(|l| l.starts_with("Release : ")) {
|
||||
let release = release_line.strip_prefix("Release : ").unwrap_or("");
|
||||
version = format!("{}-{}", version_part, release);
|
||||
} else {
|
||||
version = version_part.to_string();
|
||||
}
|
||||
} else if line.starts_with("Install Date: ") {
|
||||
let date_str = line.strip_prefix("Install Date: ").unwrap_or("");
|
||||
install_time = self.parse_rpm_date(date_str).unwrap_or(0);
|
||||
} else if line.starts_with("Size : ") {
|
||||
let size_str = line.strip_prefix("Size : ").unwrap_or("");
|
||||
size = size_str.split_whitespace().next()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(0);
|
||||
}
|
||||
}
|
||||
|
||||
if !name.is_empty() {
|
||||
Ok(Some(PackageInfo {
|
||||
name,
|
||||
version,
|
||||
source: PackageSource::Dnf,
|
||||
install_time,
|
||||
size,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DnfManager {
|
||||
fn parse_rpm_date(&self, date_str: &str) -> Option<u64> {
|
||||
// RPM date format: "Wed 28 Jul 2025 10:30:00 AM UTC"
|
||||
// Convert to Unix timestamp
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
// Try parsing the RPM date format
|
||||
if let Ok(dt) = DateTime::parse_from_str(date_str, "%a %d %b %Y %I:%M:%S %p %Z") {
|
||||
Some(dt.timestamp() as u64)
|
||||
} else if let Ok(dt) = DateTime::parse_from_str(date_str, "%a %b %d %H:%M:%S %Y") {
|
||||
Some(dt.timestamp() as u64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub mod dnf;
|
||||
// pub mod flatpak;
|
||||
// pub mod snap;
|
||||
|
||||
use self::dnf::DnfManager;
|
||||
// Uncomment these as we implement the managers
|
||||
// use self::flatpak::FlatpakManager;
|
||||
// use self::snap::SnapManager;
|
||||
|
||||
use crate::{PackageInfo, WhereError};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PackageSource {
|
||||
Apt = 1,
|
||||
Dnf = 2,
|
||||
Pacman = 3,
|
||||
Flatpak = 4,
|
||||
Snap = 5,
|
||||
Homebrew = 6,
|
||||
Manual = 7,
|
||||
}
|
||||
|
||||
impl PackageSource {
|
||||
pub fn from_u8(value: u8) -> Self {
|
||||
match value {
|
||||
1 => PackageSource::Apt,
|
||||
2 => PackageSource::Dnf,
|
||||
3 => PackageSource::Pacman,
|
||||
4 => PackageSource::Flatpak,
|
||||
5 => PackageSource::Snap,
|
||||
6 => PackageSource::Homebrew,
|
||||
_ => PackageSource::Manual,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
PackageSource::Apt => "apt",
|
||||
PackageSource::Dnf => "dnf",
|
||||
PackageSource::Pacman => "pacman",
|
||||
PackageSource::Flatpak => "flatpak",
|
||||
PackageSource::Snap => "snap",
|
||||
PackageSource::Homebrew => "homebrew",
|
||||
PackageSource::Manual => "manual",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color_code(&self) -> &'static str {
|
||||
match self {
|
||||
PackageSource::Apt => "\x1b[32m", // Green
|
||||
PackageSource::Dnf => "\x1b[31m", // Red
|
||||
PackageSource::Pacman => "\x1b[36m", // Cyan
|
||||
PackageSource::Flatpak => "\x1b[34m", // Blue
|
||||
PackageSource::Snap => "\x1b[35m", // Magenta
|
||||
PackageSource::Homebrew => "\x1b[33m", // Yellow
|
||||
PackageSource::Manual => "\x1b[37m", // White
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PackageManager {
|
||||
fn name(&self) -> &'static str;
|
||||
fn source_type(&self) -> PackageSource;
|
||||
fn is_available(&self) -> bool;
|
||||
fn get_installed_packages(&self) -> Result<Vec<PackageInfo>, WhereError>;
|
||||
fn get_package_files(&self, package: &str) -> Result<Vec<PathBuf>, WhereError>;
|
||||
fn get_file_owner(&self, path: &Path) -> Result<Option<String>, WhereError>;
|
||||
fn get_package_info(&self, package: &str) -> Result<Option<PackageInfo>, WhereError>;
|
||||
|
||||
/// Priority for file ownership resolution (higher = preferred)
|
||||
fn priority(&self) -> u8 {
|
||||
match self.source_type() {
|
||||
PackageSource::Dnf => 10, // System package manager gets highest priority
|
||||
PackageSource::Apt => 10,
|
||||
PackageSource::Pacman => 10,
|
||||
PackageSource::Flatpak => 5, // User applications
|
||||
PackageSource::Snap => 5,
|
||||
PackageSource::Homebrew => 3, // Usually supplementary
|
||||
PackageSource::Manual => 1, // Lowest priority
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect all available package managers on the system
|
||||
pub fn detect_available_managers() -> Vec<Box<dyn PackageManager>> {
|
||||
let mut managers: Vec<Box<dyn PackageManager>> = Vec::new();
|
||||
|
||||
// Check system package managers first
|
||||
let dnf = DnfManager;
|
||||
if dnf.is_available() {
|
||||
managers.push(Box::new(dnf));
|
||||
}
|
||||
|
||||
// Add other managers as we implement them
|
||||
// let flatpak = FlatpakManager;
|
||||
// if flatpak.is_available() {
|
||||
// managers.push(Box::new(flatpak));
|
||||
// }
|
||||
|
||||
// let snap = SnapManager;
|
||||
// if snap.is_available() {
|
||||
// managers.push(Box::new(snap));
|
||||
// }
|
||||
|
||||
// Sort by priority (highest first) for consistent ordering
|
||||
managers.sort_by(|a, b| b.priority().cmp(&a.priority()));
|
||||
|
||||
managers
|
||||
}
|
||||
|
||||
/// Find which package owns a file, checking all available managers
|
||||
pub fn find_file_owner(path: &Path) -> Result<Option<(String, PackageSource)>, WhereError> {
|
||||
let managers = detect_available_managers();
|
||||
|
||||
for manager in managers {
|
||||
if let Ok(Some(package)) = manager.get_file_owner(path) {
|
||||
return Ok(Some((package, manager.source_type())));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get all installed packages from all available managers
|
||||
pub fn get_all_packages() -> Result<Vec<PackageInfo>, WhereError> {
|
||||
let managers = detect_available_managers();
|
||||
let mut all_packages = Vec::new();
|
||||
|
||||
for manager in managers {
|
||||
match manager.get_installed_packages() {
|
||||
Ok(mut packages) => all_packages.append(&mut packages),
|
||||
Err(e) => {
|
||||
eprintln!("Warning: Failed to get packages from {}: {}", manager.name(), e);
|
||||
// Continue with other managers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by name for consistent output
|
||||
all_packages.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
Ok(all_packages)
|
||||
}
|
||||
|
||||
/// Get package information by name, searching all managers
|
||||
pub fn find_package(name: &str) -> Result<Option<PackageInfo>, WhereError> {
|
||||
let managers = detect_available_managers();
|
||||
|
||||
for manager in managers {
|
||||
if let Ok(Some(package)) = manager.get_package_info(name) {
|
||||
return Ok(Some(package));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Get packages from a specific source type
|
||||
pub fn get_packages_by_source(source: PackageSource) -> Result<Vec<PackageInfo>, WhereError> {
|
||||
let managers = detect_available_managers();
|
||||
|
||||
for manager in managers {
|
||||
if manager.source_type() == source {
|
||||
return manager.get_installed_packages();
|
||||
}
|
||||
}
|
||||
|
||||
Err(WhereError::ManagerNotAvailable(source.name().to_string()))
|
||||
}
|
||||
|
||||
/// Summary of available package managers
|
||||
#[derive(Debug)]
|
||||
pub struct ManagerSummary {
|
||||
pub name: String,
|
||||
pub source_type: PackageSource,
|
||||
pub available: bool,
|
||||
pub package_count: Option<usize>,
|
||||
}
|
||||
|
||||
pub fn get_manager_summary() -> Vec<ManagerSummary> {
|
||||
let available_managers = detect_available_managers();
|
||||
let mut summaries = Vec::new();
|
||||
|
||||
for manager in available_managers {
|
||||
let package_count = manager.get_installed_packages()
|
||||
.map(|packages| packages.len())
|
||||
.ok();
|
||||
|
||||
summaries.push(ManagerSummary {
|
||||
name: manager.name().to_string(),
|
||||
source_type: manager.source_type(),
|
||||
available: true,
|
||||
package_count,
|
||||
});
|
||||
}
|
||||
|
||||
summaries
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_package_source_conversion() {
|
||||
assert_eq!(PackageSource::from_u8(2), PackageSource::Dnf);
|
||||
assert_eq!(PackageSource::Dnf.name(), "dnf");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_manager_detection() {
|
||||
let managers = detect_available_managers();
|
||||
assert!(!managers.is_empty(), "Should detect at least one package manager");
|
||||
|
||||
// On Fedora, should detect DNF
|
||||
let has_dnf = managers.iter().any(|m| m.name() == "dnf");
|
||||
if Path::new("/usr/bin/dnf").exists() {
|
||||
assert!(has_dnf, "Should detect DNF on systems where it's installed");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue