working
This commit is contained in:
parent
1eb7d4c1eb
commit
35c3f4c423
10 changed files with 353 additions and 738 deletions
186
Cargo.lock
generated
186
Cargo.lock
generated
|
|
@ -134,6 +134,12 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.4"
|
version = "2.9.4"
|
||||||
|
|
@ -305,15 +311,31 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio 0.8.11",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"mio",
|
"mio 1.0.4",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix 0.38.44",
|
"rustix 0.38.44",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
|
|
@ -327,11 +349,11 @@ version = "0.29.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"derive_more 2.0.1",
|
"derive_more 2.0.1",
|
||||||
"document-features",
|
"document-features",
|
||||||
"mio",
|
"mio 1.0.4",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix 1.1.2",
|
"rustix 1.1.2",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
|
|
@ -499,6 +521,12 @@ dependencies = [
|
||||||
"dtoa",
|
"dtoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ego-tree"
|
name = "ego-tree"
|
||||||
version = "0.6.3"
|
version = "0.6.3"
|
||||||
|
|
@ -650,6 +678,15 @@ dependencies = [
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuzzy-matcher"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
|
||||||
|
dependencies = [
|
||||||
|
"thread_local",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fxhash"
|
name = "fxhash"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -1052,6 +1089,20 @@ version = "2.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inquire"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2628910d0114e9139056161d8644a2026be7b117f8498943f9437748b04c9e0a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.4",
|
||||||
|
"crossterm 0.29.0",
|
||||||
|
"dyn-clone",
|
||||||
|
"fuzzy-matcher",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width 0.2.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instability"
|
name = "instability"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
@ -1071,7 +1122,7 @@ version = "0.7.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
@ -1237,6 +1288,18 @@ dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
|
@ -1309,7 +1372,7 @@ version = "0.10.73"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"foreign-types",
|
"foreign-types",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -1357,6 +1420,7 @@ dependencies = [
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.29.0",
|
||||||
"html_parser",
|
"html_parser",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
|
"inquire",
|
||||||
"md5",
|
"md5",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
|
|
@ -1368,6 +1432,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"spinners",
|
"spinners",
|
||||||
|
"tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1676,7 +1741,7 @@ version = "0.29.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"cassowary",
|
"cassowary",
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"crossterm 0.28.1",
|
"crossterm 0.28.1",
|
||||||
|
|
@ -1697,7 +1762,7 @@ version = "0.5.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1797,7 +1862,7 @@ version = "0.38.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.4.15",
|
"linux-raw-sys 0.4.15",
|
||||||
|
|
@ -1810,7 +1875,7 @@ version = "1.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.11.0",
|
"linux-raw-sys 0.11.0",
|
||||||
|
|
@ -1899,7 +1964,7 @@ version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -1922,7 +1987,7 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06"
|
checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"cssparser",
|
"cssparser",
|
||||||
"derive_more 0.99.20",
|
"derive_more 0.99.20",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
|
|
@ -2039,7 +2104,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 0.8.11",
|
||||||
|
"mio 1.0.4",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2238,7 +2304,7 @@ version = "0.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
"system-configuration-sys",
|
"system-configuration-sys",
|
||||||
]
|
]
|
||||||
|
|
@ -2317,6 +2383,15 @@ dependencies = [
|
||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
|
@ -2337,7 +2412,7 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"io-uring",
|
"io-uring",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 1.0.4",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
|
@ -2398,7 +2473,7 @@ version = "0.6.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags 2.9.4",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
|
|
@ -2447,6 +2522,19 @@ version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui"
|
||||||
|
version = "0.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"cassowary",
|
||||||
|
"crossterm 0.25.0",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width 0.1.14",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.18.0"
|
version = "1.18.0"
|
||||||
|
|
@ -2736,6 +2824,15 @@ dependencies = [
|
||||||
"windows-link 0.1.3",
|
"windows-link 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
|
@ -2772,6 +2869,21 @@ dependencies = [
|
||||||
"windows-link 0.2.0",
|
"windows-link 0.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -2805,6 +2917,12 @@ dependencies = [
|
||||||
"windows_x86_64_msvc 0.53.0",
|
"windows_x86_64_msvc 0.53.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -2817,6 +2935,12 @@ version = "0.53.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -2829,6 +2953,12 @@ version = "0.53.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -2853,6 +2983,12 @@ version = "0.53.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -2865,6 +3001,12 @@ version = "0.53.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -2877,6 +3019,12 @@ version = "0.53.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
@ -2889,6 +3037,12 @@ version = "0.53.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ md5 = "0.8.0"
|
||||||
# HTTP
|
# HTTP
|
||||||
reqwest = { version = "0.12.23", features = ["blocking", "json"] }
|
reqwest = { version = "0.12.23", features = ["blocking", "json"] }
|
||||||
semver = "1.0.27"
|
semver = "1.0.27"
|
||||||
|
inquire = "0.9.1"
|
||||||
|
tui = "0.19.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# TUI feature flag
|
# TUI feature flag
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,54 @@
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use console::style;
|
|
||||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
|
||||||
use reqwest::blocking::Client;
|
|
||||||
|
|
||||||
fn verify_md5(file_path: &Path, expected_hash: &str) -> bool {
|
|
||||||
let mut f = match File::open(file_path) {
|
|
||||||
Ok(f) => f,
|
|
||||||
Err(_) => return false,
|
|
||||||
};
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
if f.read_to_end(&mut buffer).is_err() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let digest = md5::compute(&buffer);
|
|
||||||
let hex = format!("{:x}", digest);
|
|
||||||
hex == expected_hash
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn download_files(
|
pub fn download_files(
|
||||||
wget_list: &str,
|
files: &[String],
|
||||||
target_dir: &Path,
|
target_dir: &Path,
|
||||||
package_mirror: Option<String>,
|
mirror: Option<&str>,
|
||||||
md5_map: Option<&HashMap<String, String>>,
|
md5_map: Option<&HashMap<String, String>>,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<()> {
|
||||||
fs::create_dir_all(target_dir)?;
|
fs::create_dir_all(target_dir)?;
|
||||||
|
|
||||||
let urls: Vec<&str> = wget_list.lines().filter(|l| !l.trim().is_empty()).collect();
|
|
||||||
let total = urls.len();
|
|
||||||
let client = Arc::new(Client::new());
|
|
||||||
let mp = Arc::new(MultiProgress::new());
|
|
||||||
|
|
||||||
let md5_map = md5_map.cloned();
|
|
||||||
|
|
||||||
let mut handles = vec![];
|
let mut handles = vec![];
|
||||||
|
|
||||||
for (i, url) in urls.into_iter().enumerate() {
|
for url in files.iter().cloned() {
|
||||||
let client = Arc::clone(&client);
|
|
||||||
let mp = Arc::clone(&mp);
|
|
||||||
let target_dir = target_dir.to_path_buf();
|
let target_dir = target_dir.to_path_buf();
|
||||||
let package_mirror = package_mirror.clone();
|
let mirror = mirror.map(|m| m.to_string());
|
||||||
let url = url.to_string();
|
let md5_map = md5_map.cloned();
|
||||||
let md5_map = md5_map.clone();
|
|
||||||
|
|
||||||
let handle = thread::spawn(move || -> Result<(), Box<dyn std::error::Error + Send>> {
|
let handle = thread::spawn(move || -> Result<()> {
|
||||||
let filename = url.split('/').next_back().unwrap_or("file.tar.xz");
|
let download_url = if let Some(m) = &mirror {
|
||||||
|
url.replace("ftp.gnu.org", m)
|
||||||
|
} else {
|
||||||
|
url.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let filename = download_url
|
||||||
|
.split('/')
|
||||||
|
.last()
|
||||||
|
.ok_or_else(|| anyhow!("Failed to extract filename"))?;
|
||||||
let filepath = target_dir.join(filename);
|
let filepath = target_dir.join(filename);
|
||||||
|
|
||||||
let download_url = if let Some(ref mirror) = package_mirror {
|
let mut resp = reqwest::blocking::get(&download_url)?;
|
||||||
if url.contains("ftp.gnu.org") {
|
let mut buffer = Vec::new();
|
||||||
url.replacen("ftp.gnu.org", mirror, 1)
|
resp.read_to_end(&mut buffer)?;
|
||||||
} else {
|
|
||||||
url.to_string()
|
let mut file = File::create(&filepath)?;
|
||||||
|
file.write_all(&buffer)?;
|
||||||
|
|
||||||
|
if let Some(md5s) = md5_map.as_ref() {
|
||||||
|
if let Some(expected) = md5s.get(filename) {
|
||||||
|
let digest = md5::compute(&buffer);
|
||||||
|
if format!("{:x}", digest) != *expected {
|
||||||
|
return Err(anyhow!("MD5 mismatch for {}", filename));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
url.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
let pb = mp.add(ProgressBar::new(0));
|
|
||||||
pb.set_style(
|
|
||||||
ProgressStyle::with_template(
|
|
||||||
"{bar:40.cyan/blue} {bytes}/{total_bytes} ({eta}) {msg}",
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.progress_chars("=> "),
|
|
||||||
);
|
|
||||||
pb.set_message(format!(
|
|
||||||
"[{}/{}] {}",
|
|
||||||
i + 1,
|
|
||||||
total,
|
|
||||||
style(filename).yellow()
|
|
||||||
));
|
|
||||||
|
|
||||||
let mut resp = client
|
|
||||||
.get(&download_url)
|
|
||||||
.send()
|
|
||||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
|
||||||
let total_size = resp.content_length().unwrap_or(0);
|
|
||||||
pb.set_length(total_size);
|
|
||||||
|
|
||||||
let mut file = File::create(&filepath)
|
|
||||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
|
||||||
let mut downloaded: u64 = 0;
|
|
||||||
let mut buffer = [0u8; 8192];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let bytes_read = resp
|
|
||||||
.read(&mut buffer)
|
|
||||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
|
||||||
if bytes_read == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
file.write_all(&buffer[..bytes_read])
|
|
||||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
|
||||||
downloaded += bytes_read as u64;
|
|
||||||
pb.set_position(downloaded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let status = if let Some(ref md5_map) = md5_map {
|
|
||||||
if let Some(expected_hash) = md5_map.get(filename) {
|
|
||||||
if verify_md5(&filepath, expected_hash) {
|
|
||||||
style("✅").green()
|
|
||||||
} else {
|
|
||||||
style("❌").red()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
style("⚠️").yellow()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
style("⚠️").yellow()
|
|
||||||
};
|
|
||||||
|
|
||||||
pb.finish_with_message(format!("{} {}", status, style(filename).yellow()));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -124,8 +56,7 @@ pub fn download_files(
|
||||||
}
|
}
|
||||||
|
|
||||||
for handle in handles {
|
for handle in handles {
|
||||||
let result = handle.join().unwrap();
|
handle.join().map_err(|_| anyhow!("Thread panicked"))??;
|
||||||
result.map_err(|e| e as Box<dyn std::error::Error>)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
39
src/html.rs
39
src/html.rs
|
|
@ -1,33 +1,14 @@
|
||||||
use html_parser::Dom;
|
use scraper::{Html, Selector};
|
||||||
use reqwest::blocking::get;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
/// Lädt die HTML-Seite von der angegebenen URL herunter und konvertiert sie in JSON
|
pub fn fetch_pre_blocks(url: &str) -> anyhow::Result<Vec<String>> {
|
||||||
pub fn fetch_and_parse_html_to_json(url: &str) -> Result<String, Box<dyn Error>> {
|
let body = reqwest::blocking::get(url)?.text()?;
|
||||||
// HTML herunterladen
|
let document = Html::parse_document(&body);
|
||||||
let response = get(url)?;
|
let selector = Selector::parse("pre").unwrap();
|
||||||
if !response.status().is_success() {
|
|
||||||
return Err(format!("Fehler beim Abrufen der URL {}: {}", url, response.status()).into());
|
let mut results = Vec::new();
|
||||||
|
for element in document.select(&selector) {
|
||||||
|
results.push(element.inner_html());
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = response.text()?;
|
Ok(results)
|
||||||
|
|
||||||
// HTML parsen
|
|
||||||
let dom = Dom::parse(&body)?;
|
|
||||||
|
|
||||||
// In JSON konvertieren
|
|
||||||
let json = dom.to_json_pretty()?;
|
|
||||||
Ok(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_fetch_and_parse() {
|
|
||||||
let url = "https://www.linuxfromscratch.org/~thomas/multilib-m32/chapter02/hostreqs.html";
|
|
||||||
let json = fetch_and_parse_html_to_json(url).expect("Fehler beim Parsen");
|
|
||||||
assert!(json.contains("Host System Requirements"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
src/main.rs
62
src/main.rs
|
|
@ -8,76 +8,16 @@ mod wget_list;
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
mod tui;
|
mod tui;
|
||||||
|
|
||||||
use console::style;
|
|
||||||
use rand::Rng;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::env;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
{
|
{
|
||||||
// TUI-Modus
|
|
||||||
tui::tui_menu()?;
|
tui::tui_menu()?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "tui"))]
|
#[cfg(not(feature = "tui"))]
|
||||||
{
|
{
|
||||||
// --- Dynamische Version-Prüfung direkt aus HTML ---
|
println!("TUI feature not enabled. Compile with `--features tui` to run TUI.");
|
||||||
let ok = version_check::run_version_checks_from_html(
|
|
||||||
"https://www.linuxfromscratch.org/~thomas/multilib-m32/chapter02/hostreqs.html",
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
eprintln!(
|
|
||||||
"{} Some version checks failed. Exiting.",
|
|
||||||
style("❌").red().bold()
|
|
||||||
);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"{} All version checks passed. Starting downloader...",
|
|
||||||
style("✅").green().bold()
|
|
||||||
);
|
|
||||||
|
|
||||||
// --- Bestimme LFS-Sources-Pfad ---
|
|
||||||
let lfs_sources = match env::var("LFS") {
|
|
||||||
Ok(lfs) => PathBuf::from(lfs).join("sources"),
|
|
||||||
Err(_) => {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let random_number: u32 = rng.gen_range(1000..=9999);
|
|
||||||
let tmp_path = format!("/tmp/lfs_{}", random_number);
|
|
||||||
println!(
|
|
||||||
"{} Using temporary path {}",
|
|
||||||
style("ℹ️").blue(),
|
|
||||||
style(&tmp_path).yellow()
|
|
||||||
);
|
|
||||||
PathBuf::from(tmp_path).join("sources")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- CLI Mirror-Auswahl: default oder erweiterbar ---
|
|
||||||
let package_mirror: Option<String> = None;
|
|
||||||
|
|
||||||
// --- Hole wget-Liste ---
|
|
||||||
let wget_list = wget_list::get_wget_list()?;
|
|
||||||
|
|
||||||
// --- Bereite MD5-Map vor ---
|
|
||||||
let mut md5_map: HashMap<String, String> = HashMap::new();
|
|
||||||
let md5_content = md5_utils::get_md5sums()?;
|
|
||||||
for line in md5_content.lines() {
|
|
||||||
let mut parts = line.split_whitespace();
|
|
||||||
if let (Some(hash), Some(filename)) = (parts.next(), parts.next()) {
|
|
||||||
md5_map.insert(filename.to_string(), hash.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Lade Dateien herunter ---
|
|
||||||
downloader::download_files(&wget_list, &lfs_sources, package_mirror, Some(&md5_map))?;
|
|
||||||
|
|
||||||
println!("{} All done!", style("🎉").green().bold());
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,14 @@ use reqwest::blocking::Client;
|
||||||
use reqwest::redirect::Policy;
|
use reqwest::redirect::Policy;
|
||||||
|
|
||||||
pub fn get_md5sums() -> Result<String> {
|
pub fn get_md5sums() -> Result<String> {
|
||||||
let client = Client::builder().redirect(Policy::none()).build()?;
|
let client = Client::builder().redirect(Policy::limited(5)).build()?;
|
||||||
let res = client
|
let res = client
|
||||||
.get("https://www.linuxfromscratch.org/~thomas/multilib-m32/md5sums")
|
.get("https://www.linuxfromscratch.org/~thomas/multilib-m32/md5sums")
|
||||||
.send()?
|
.send()?;
|
||||||
.text()?;
|
|
||||||
Ok(res)
|
if !res.status().is_success() {
|
||||||
|
anyhow::bail!("Failed to fetch MD5sums: HTTP {}", res.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.text()?)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,14 @@ pub fn fetch_mirrors() -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||||
.get("https://www.linuxfromscratch.org/lfs/mirrors.html#files")
|
.get("https://www.linuxfromscratch.org/lfs/mirrors.html#files")
|
||||||
.send()?
|
.send()?
|
||||||
.text()?;
|
.text()?;
|
||||||
|
|
||||||
let document = Html::parse_document(&res);
|
let document = Html::parse_document(&res);
|
||||||
let selector = Selector::parse("a[href^='http']").unwrap();
|
let selector = Selector::parse("a[href^='http']").unwrap();
|
||||||
|
|
||||||
let mirrors = document
|
let mirrors = document
|
||||||
.select(&selector)
|
.select(&selector)
|
||||||
.filter_map(|element| {
|
.filter_map(|element| {
|
||||||
let href = element.value().attr("href")?;
|
let href = element.value().attr("href")?;
|
||||||
// Basic filtering to get potential mirror URLs
|
|
||||||
if href.contains("ftp.gnu.org") || href.contains("mirror") {
|
if href.contains("ftp.gnu.org") || href.contains("mirror") {
|
||||||
Some(href.to_string())
|
Some(href.to_string())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -23,25 +24,20 @@ pub fn fetch_mirrors() -> Result<Vec<String>, Box<dyn std::error::Error>> {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(mirrors)
|
Ok(mirrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn choose_package_mirror() -> Option<String> {
|
pub fn choose_package_mirror() -> Option<String> {
|
||||||
let mirrors = match fetch_mirrors() {
|
let mirrors = fetch_mirrors().unwrap_or_else(|_| {
|
||||||
Ok(mirrors) => mirrors,
|
vec![
|
||||||
Err(e) => {
|
"ftp.fau.de".to_string(),
|
||||||
println!("Failed to fetch mirrors: {}", e);
|
"mirror.kernel.org".to_string(),
|
||||||
// Fallback to a default list if fetching fails
|
"mirror.example.org".to_string(),
|
||||||
vec![
|
]
|
||||||
"ftp.fau.de".to_string(),
|
});
|
||||||
"mirror.kernel.org".to_string(),
|
|
||||||
"mirror.example.org".to_string(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Optional: choose a mirror for GNU source packages (replace ftp.gnu.org):");
|
|
||||||
|
|
||||||
|
println!("Optional: choose a mirror for GNU source packages:");
|
||||||
for (i, mirror) in mirrors.iter().enumerate() {
|
for (i, mirror) in mirrors.iter().enumerate() {
|
||||||
println!(" [{}] {}", i + 1, mirror);
|
println!(" [{}] {}", i + 1, mirror);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
439
src/tui.rs
439
src/tui.rs
|
|
@ -1,417 +1,104 @@
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use crate::html::fetch_and_parse_html_to_json;
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use crate::{downloader, md5_utils, mirrors, wget_list};
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
event::{self, Event, KeyCode},
|
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||||
execute,
|
execute,
|
||||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
|
||||||
};
|
};
|
||||||
#[cfg(feature = "tui")]
|
use std::{error::Error, io};
|
||||||
use rand::Rng;
|
use tui::{
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use ratatui::{
|
|
||||||
Terminal,
|
Terminal,
|
||||||
backend::CrosstermBackend,
|
backend::CrosstermBackend,
|
||||||
layout::{Constraint, Direction, Layout},
|
layout::{Constraint, Direction, Layout},
|
||||||
style::{Color, Style},
|
style::{Color, Modifier, Style},
|
||||||
widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
|
widgets::{Block, Borders, List, ListItem},
|
||||||
};
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
fs,
|
|
||||||
io::stdout,
|
|
||||||
path::PathBuf,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
thread,
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
pub fn tui_menu() -> Result<(), Box<dyn Error>> {
|
||||||
use reqwest::blocking::get;
|
// Setup terminal
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use scraper::{Html, Selector};
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
// ----------------- HTML fetch function -----------------
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
fn fetch_html_to_json(url: &str) -> Result<String, Box<dyn std::error::Error>> {
|
|
||||||
let body = get(url)?.text()?;
|
|
||||||
let document = Html::parse_document(&body);
|
|
||||||
|
|
||||||
let selector = Selector::parse("body").unwrap();
|
|
||||||
let mut contents = vec![];
|
|
||||||
for element in document.select(&selector) {
|
|
||||||
contents.push(element.text().collect::<Vec<_>>().join(" "));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(serde_json::to_string_pretty(
|
|
||||||
&json!({ "body_text": contents }),
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------- TUI functions -----------------
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
fn init_environment() -> Result<PathBuf, Box<dyn std::error::Error>> {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let random_number: u32 = rng.gen_range(1000..=9999);
|
|
||||||
let tmp_base_path = PathBuf::from(format!("/tmp/lfs_{}", random_number));
|
|
||||||
let lfs_sources_path = tmp_base_path.join("sources");
|
|
||||||
|
|
||||||
fs::create_dir_all(&lfs_sources_path)?;
|
|
||||||
Ok(lfs_sources_path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
fn select_mirrors_tui(mirrors: Vec<String>) -> Vec<String> {
|
|
||||||
if mirrors.is_empty() {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut selected: Vec<bool> = vec![false; mirrors.len()];
|
|
||||||
let mut state = ListState::default();
|
|
||||||
state.select(Some(0));
|
|
||||||
|
|
||||||
let mut stdout = stdout();
|
|
||||||
execute!(stdout, EnterAlternateScreen).unwrap();
|
|
||||||
enable_raw_mode().unwrap();
|
|
||||||
let backend = CrosstermBackend::new(stdout);
|
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
terminal
|
|
||||||
.draw(|f| {
|
|
||||||
let size = f.size();
|
|
||||||
let items: Vec<ListItem> = mirrors
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, mirror)| {
|
|
||||||
let prefix = if selected[i] { "[x] " } else { "[ ] " };
|
|
||||||
ListItem::new(format!("{}{}", prefix, mirror))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let list = List::new(items)
|
|
||||||
.block(
|
|
||||||
Block::default()
|
|
||||||
.title("Select mirrors")
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
)
|
|
||||||
.highlight_symbol(">> ");
|
|
||||||
f.render_stateful_widget(list, size, &mut state);
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Event::Key(key) = event::read().unwrap() {
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Down => {
|
|
||||||
let i = state.selected().unwrap_or(0);
|
|
||||||
if i < mirrors.len() - 1 {
|
|
||||||
state.select(Some(i + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Up => {
|
|
||||||
let i = state.selected().unwrap_or(0);
|
|
||||||
if i > 0 {
|
|
||||||
state.select(Some(i - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Char(' ') => {
|
|
||||||
let i = state.selected().unwrap_or(0);
|
|
||||||
selected[i] = !selected[i];
|
|
||||||
}
|
|
||||||
KeyCode::Enter => {
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap();
|
|
||||||
return mirrors
|
|
||||||
.into_iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(i, m)| if selected[i] { Some(m) } else { None })
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
KeyCode::Esc => {
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap();
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
fn download_packages_tui(lfs_sources: &PathBuf) {
|
|
||||||
let mirrors_list = mirrors::fetch_mirrors().unwrap_or_default();
|
|
||||||
let selected_mirrors = select_mirrors_tui(mirrors_list);
|
|
||||||
if selected_mirrors.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let wget_list_content = wget_list::get_wget_list().unwrap_or_default();
|
|
||||||
let wget_list: Vec<String> = wget_list_content.lines().map(|s| s.to_string()).collect();
|
|
||||||
if wget_list.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut md5_map = HashMap::new();
|
|
||||||
if let Ok(md5_content) = md5_utils::get_md5sums() {
|
|
||||||
for line in md5_content.lines() {
|
|
||||||
if let Some((hash, filename)) = line.split_once(' ') {
|
|
||||||
md5_map.insert(filename.to_string(), hash.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let download_state: Arc<Mutex<Vec<(String, String)>>> = Arc::new(Mutex::new(
|
|
||||||
wget_list
|
|
||||||
.iter()
|
|
||||||
.map(|f| (f.clone(), "Pending".into()))
|
|
||||||
.collect(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let download_state_clone = Arc::clone(&download_state);
|
|
||||||
let mirrors_clone = selected_mirrors.clone();
|
|
||||||
let lfs_sources = lfs_sources.clone();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
for file in &wget_list {
|
|
||||||
let mut status = "Failed".to_string();
|
|
||||||
for mirror in &mirrors_clone {
|
|
||||||
if downloader::download_files(
|
|
||||||
file,
|
|
||||||
&lfs_sources,
|
|
||||||
Some(mirror.clone()),
|
|
||||||
Some(&md5_map),
|
|
||||||
)
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
status = format!("Downloaded from {}", mirror);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut state = download_state_clone.lock().unwrap();
|
|
||||||
if let Some(entry) = state.iter_mut().find(|(f, _)| f == file) {
|
|
||||||
entry.1 = status.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut stdout = stdout();
|
|
||||||
execute!(stdout, EnterAlternateScreen).unwrap();
|
|
||||||
enable_raw_mode().unwrap();
|
|
||||||
let backend = CrosstermBackend::new(stdout);
|
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
terminal
|
|
||||||
.draw(|f| {
|
|
||||||
let size = f.size();
|
|
||||||
let items: Vec<ListItem> = {
|
|
||||||
let state = download_state.lock().unwrap();
|
|
||||||
state
|
|
||||||
.iter()
|
|
||||||
.map(|(f, s)| ListItem::new(format!("{}: {}", f, s)))
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
let list = List::new(items).block(
|
|
||||||
Block::default()
|
|
||||||
.title("Downloading Packages")
|
|
||||||
.borders(Borders::ALL),
|
|
||||||
);
|
|
||||||
f.render_widget(list, size);
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let state = download_state.lock().unwrap();
|
|
||||||
if state.iter().all(|(_, s)| s != "Pending") {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
thread::sleep(Duration::from_millis(100));
|
|
||||||
}
|
|
||||||
|
|
||||||
disable_raw_mode().unwrap();
|
|
||||||
execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------- Cleanup function -----------------
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
fn cleanup_temp_directories() -> Result<usize, Box<dyn std::error::Error>> {
|
|
||||||
let mut cleaned_count = 0;
|
|
||||||
for entry in fs::read_dir("/tmp")? {
|
|
||||||
let entry = entry?;
|
|
||||||
let path = entry.path();
|
|
||||||
if path.is_dir() {
|
|
||||||
if let Some(name) = path.file_name().and_then(|s| s.to_str()) {
|
|
||||||
if name.starts_with("lfs_") {
|
|
||||||
fs::remove_dir_all(&path)?;
|
|
||||||
cleaned_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(cleaned_count)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------- Main TUI menu -----------------
|
|
||||||
#[cfg(feature = "tui")]
|
|
||||||
pub fn tui_menu() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let mut stdout = stdout();
|
|
||||||
execute!(stdout, EnterAlternateScreen)?;
|
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||||
let backend = CrosstermBackend::new(stdout);
|
let backend = CrosstermBackend::new(stdout);
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|
||||||
let menu_items = vec![
|
let menu_items = vec!["Start downloader", "Exit"];
|
||||||
"🌱 Init environment",
|
let mut selected = 0;
|
||||||
"📦 Download packages",
|
|
||||||
"🔍 Check status",
|
|
||||||
"🧹 Clean up temp directories",
|
|
||||||
"📄 Test JSON fetch", // NEW BUTTON
|
|
||||||
"❌ Exit",
|
|
||||||
];
|
|
||||||
let mut state = ListState::default();
|
|
||||||
state.select(Some(0));
|
|
||||||
|
|
||||||
let mut lfs_sources: Option<PathBuf> = None;
|
|
||||||
let mut status_message: Option<String> = None;
|
|
||||||
let mut status_message_timer: Option<Instant> = None;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
terminal.draw(|f| {
|
terminal.draw(|f| {
|
||||||
let size = f.size();
|
let size = f.size();
|
||||||
let block = Block::default()
|
|
||||||
.title("✨ lpkg TUI 🌈")
|
|
||||||
.borders(Borders::ALL);
|
|
||||||
f.render_widget(block, size);
|
|
||||||
|
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.margin(2)
|
.margin(5)
|
||||||
.constraints(vec![Constraint::Length(3); menu_items.len()])
|
.constraints([Constraint::Length(menu_items.len() as u16)].as_ref())
|
||||||
.split(size);
|
.split(size);
|
||||||
|
|
||||||
for (i, item) in menu_items.iter().enumerate() {
|
let items: Vec<ListItem> = menu_items
|
||||||
let mut style = Style::default();
|
.iter()
|
||||||
if Some(i) == state.selected() {
|
.enumerate()
|
||||||
style = style.bg(Color::Red).fg(Color::White);
|
.map(|(i, m)| {
|
||||||
}
|
let style = if i == selected {
|
||||||
let list_item = ListItem::new(*item).style(style);
|
Style::default()
|
||||||
let list = List::new(vec![list_item]).block(Block::default().borders(Borders::ALL));
|
.fg(Color::Yellow)
|
||||||
f.render_widget(list, chunks[i]);
|
.add_modifier(Modifier::BOLD)
|
||||||
}
|
} else {
|
||||||
|
Style::default()
|
||||||
|
};
|
||||||
|
ListItem::new(*m).style(style)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
if let Some(msg) = &status_message {
|
let list = List::new(items).block(
|
||||||
let paragraph = Paragraph::new(msg.as_str()).block(
|
Block::default()
|
||||||
Block::default()
|
.title("LFS Downloader")
|
||||||
.borders(Borders::NONE)
|
.borders(Borders::ALL),
|
||||||
.style(Style::default().fg(Color::Yellow)),
|
);
|
||||||
);
|
f.render_widget(list, chunks[0]);
|
||||||
let msg_area = Layout::default()
|
|
||||||
.direction(Direction::Horizontal)
|
|
||||||
.constraints([
|
|
||||||
Constraint::Percentage(10),
|
|
||||||
Constraint::Percentage(80),
|
|
||||||
Constraint::Percentage(10),
|
|
||||||
])
|
|
||||||
.split(size)[1];
|
|
||||||
let msg_area = Layout::default()
|
|
||||||
.direction(Direction::Vertical)
|
|
||||||
.constraints([Constraint::Min(0), Constraint::Length(1)])
|
|
||||||
.split(msg_area)[1];
|
|
||||||
f.render_widget(paragraph, msg_area);
|
|
||||||
}
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if let Some(timer) = status_message_timer {
|
// Handle input
|
||||||
if timer.elapsed() > Duration::from_secs(3) {
|
|
||||||
status_message = None;
|
|
||||||
status_message_timer = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Event::Key(key) = event::read()? {
|
if let Event::Key(key) = event::read()? {
|
||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Down => {
|
|
||||||
let i = state.selected().unwrap_or(0);
|
|
||||||
if i < menu_items.len() - 1 {
|
|
||||||
state.select(Some(i + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
let i = state.selected().unwrap_or(0);
|
if selected > 0 {
|
||||||
if i > 0 {
|
selected -= 1;
|
||||||
state.select(Some(i - 1));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyCode::Enter => match state.selected() {
|
KeyCode::Down => {
|
||||||
Some(0) => {
|
if selected < menu_items.len() - 1 {
|
||||||
// Init environment
|
selected += 1;
|
||||||
match init_environment() {
|
}
|
||||||
Ok(path) => {
|
}
|
||||||
lfs_sources = Some(path.clone());
|
KeyCode::Enter => {
|
||||||
status_message =
|
match selected {
|
||||||
Some(format!("✅ Environment initialized: {}", path.display()));
|
0 => {
|
||||||
}
|
// Start downloader
|
||||||
Err(e) => {
|
disable_raw_mode()?;
|
||||||
status_message =
|
execute!(
|
||||||
Some(format!("❌ Failed to init environment: {}", e));
|
terminal.backend_mut(),
|
||||||
}
|
LeaveAlternateScreen,
|
||||||
|
DisableMouseCapture
|
||||||
|
)?;
|
||||||
|
super::start_downloader()?; // call your downloader function
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
status_message_timer = Some(Instant::now());
|
1 => {
|
||||||
}
|
break; // Exit
|
||||||
Some(1) => {
|
|
||||||
// Download packages
|
|
||||||
if let Some(path) = &lfs_sources {
|
|
||||||
download_packages_tui(path);
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
Some(2) => {
|
}
|
||||||
// Status
|
|
||||||
status_message = Some("🔍 Status selected! (TODO)".to_string());
|
|
||||||
status_message_timer = Some(Instant::now());
|
|
||||||
}
|
|
||||||
Some(3) => {
|
|
||||||
// Cleanup
|
|
||||||
match cleanup_temp_directories() {
|
|
||||||
Ok(count) => {
|
|
||||||
status_message =
|
|
||||||
Some(format!("✅ Cleaned {} temporary dirs", count));
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
status_message = Some(format!("❌ Failed cleanup: {}", e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
status_message_timer = Some(Instant::now());
|
|
||||||
}
|
|
||||||
Some(4) => {
|
|
||||||
// Test JSON fetch
|
|
||||||
// Leave TUI first
|
|
||||||
disable_raw_mode()?;
|
|
||||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
|
||||||
terminal.show_cursor()?;
|
|
||||||
|
|
||||||
match fetch_and_parse_html_to_json(
|
|
||||||
"https://www.linuxfromscratch.org/~thomas/multilib-m32/chapter02/hostreqs.html",
|
|
||||||
) {
|
|
||||||
Ok(json) => println!("✅ JSON output:\n{}", json),
|
|
||||||
Err(e) => eprintln!("❌ Error: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
Some(5) | _ => break,
|
|
||||||
},
|
|
||||||
KeyCode::Esc => break,
|
KeyCode::Esc => break,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore terminal
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
|
execute!(
|
||||||
terminal.show_cursor()?;
|
terminal.backend_mut(),
|
||||||
|
LeaveAlternateScreen,
|
||||||
|
DisableMouseCapture
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
use reqwest;
|
|
||||||
use scraper::{Html, Selector};
|
|
||||||
use semver::Version;
|
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
/// Führt ein Kommando aus und gibt die erste Zeile der Version zurück
|
/// Führt ein Kommando aus und gibt stdout zurück
|
||||||
fn run_command(cmd: &str, args: &[&str]) -> Option<String> {
|
fn run_command(cmd: &str, args: &[&str]) -> Option<String> {
|
||||||
let output = Command::new(cmd).args(args).output().ok()?;
|
let output = Command::new(cmd).args(args).output().ok()?;
|
||||||
if output.status.success() {
|
if output.status.success() {
|
||||||
|
|
@ -13,145 +10,64 @@ fn run_command(cmd: &str, args: &[&str]) -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vergleicht zwei Versionen mit semver (für Programme)
|
/// Vergleicht Versionen (semver für Programme)
|
||||||
fn check_version(installed: &str, required: &str) -> bool {
|
fn check_version(installed: &str, required: &str) -> bool {
|
||||||
let i = Version::parse(installed).ok();
|
match (
|
||||||
let r = Version::parse(required).ok();
|
semver::Version::parse(installed),
|
||||||
match (i, r) {
|
semver::Version::parse(required),
|
||||||
(Some(i), Some(r)) => i >= r,
|
) {
|
||||||
|
(Ok(i), Ok(r)) => i >= r,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Vergleicht Kernel-Versionen (numerisch)
|
/// Prüft einen <pre>-Block auf Versionen
|
||||||
fn check_kernel_version(installed: &str, required: &str) -> bool {
|
pub fn run_version_checks_from_block(block: &str) -> bool {
|
||||||
let parse_ver = |v: &str| {
|
|
||||||
v.split(['.', '-'])
|
|
||||||
.filter_map(|s| s.parse::<u32>().ok())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
};
|
|
||||||
|
|
||||||
let i = parse_ver(installed);
|
|
||||||
let r = parse_ver(required);
|
|
||||||
|
|
||||||
for (a, b) in i.iter().zip(r.iter()) {
|
|
||||||
if a > b {
|
|
||||||
return true;
|
|
||||||
} else if a < b {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i.len() >= r.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Führt eine Version-Prüfung durch
|
|
||||||
fn ver_check(program: &str, cmd: &str, min_version: &str) -> bool {
|
|
||||||
match run_command(cmd, &["--version"]) {
|
|
||||||
Some(output) => {
|
|
||||||
let ver = output
|
|
||||||
.lines()
|
|
||||||
.next()
|
|
||||||
.unwrap_or("")
|
|
||||||
.split_whitespace()
|
|
||||||
.last()
|
|
||||||
.unwrap_or("");
|
|
||||||
if check_version(ver, min_version) {
|
|
||||||
println!("OK: {:<12} {:<8} >= {}", program, ver, min_version);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"ERROR: {:<12} version {} is too old ({} required)",
|
|
||||||
program, ver, min_version
|
|
||||||
);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
eprintln!("ERROR: Cannot find {}", program);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Führt die Kernel-Prüfung durch
|
|
||||||
fn ver_kernel(min_version: &str) -> bool {
|
|
||||||
let kernel = run_command("uname", &["-r"]).unwrap_or_default();
|
|
||||||
if check_kernel_version(&kernel, min_version) {
|
|
||||||
println!("OK: Linux Kernel {} >= {}", kernel, min_version);
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"ERROR: Linux Kernel {} is too old ({} required)",
|
|
||||||
kernel, min_version
|
|
||||||
);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lädt die LFS-Seite und führt alle Versionsprüfungen aus
|
|
||||||
pub fn run_version_checks_from_html(url: &str) -> Result<bool, Box<dyn std::error::Error>> {
|
|
||||||
let html_text = reqwest::blocking::get(url)?.text()?;
|
|
||||||
let document = Html::parse_document(&html_text);
|
|
||||||
let selector = Selector::parse("pre").unwrap();
|
|
||||||
|
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
for element in document.select(&selector) {
|
for line in block.lines() {
|
||||||
let pre_text = element.text().collect::<Vec<_>>().join("\n");
|
let line = line.trim();
|
||||||
|
if line.starts_with("ver_check") {
|
||||||
for line in pre_text.lines() {
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
let line = line.trim();
|
if parts.len() >= 4 {
|
||||||
if line.starts_with("ver_check") {
|
let prog = parts[1];
|
||||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
let cmd = parts[2];
|
||||||
if parts.len() >= 4 {
|
let ver = parts[3];
|
||||||
let prog = parts[1];
|
let installed = run_command(cmd, &["--version"]).unwrap_or_default();
|
||||||
let cmd = parts[2];
|
let ver_inst = installed
|
||||||
let ver = parts[3];
|
.lines()
|
||||||
ok &= ver_check(prog, cmd, ver);
|
.next()
|
||||||
|
.unwrap_or("")
|
||||||
|
.split_whitespace()
|
||||||
|
.last()
|
||||||
|
.unwrap_or("");
|
||||||
|
if check_version(ver_inst, ver) {
|
||||||
|
println!("OK: {} {} >= {}", prog, ver_inst, ver);
|
||||||
|
} else {
|
||||||
|
eprintln!("ERROR: {} {} < {}", prog, ver_inst, ver);
|
||||||
|
ok = false;
|
||||||
}
|
}
|
||||||
} else if line.starts_with("ver_kernel") {
|
}
|
||||||
if let Some(ver) = line.split_whitespace().nth(1) {
|
} else if line.starts_with("ver_kernel") {
|
||||||
ok &= ver_kernel(ver);
|
if let Some(ver) = line.split_whitespace().nth(1) {
|
||||||
|
let kernel = run_command("uname", &["-r"]).unwrap_or_default();
|
||||||
|
let installed = kernel
|
||||||
|
.split(['-', '.'])
|
||||||
|
.filter_map(|s| s.parse::<u32>().ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let required = ver
|
||||||
|
.split(['-', '.'])
|
||||||
|
.filter_map(|s| s.parse::<u32>().ok())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if installed >= required {
|
||||||
|
println!("OK: Linux Kernel {} >= {}", kernel, ver);
|
||||||
|
} else {
|
||||||
|
eprintln!("ERROR: Linux Kernel {} < {}", kernel, ver);
|
||||||
|
ok = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alias-Checks
|
ok
|
||||||
let alias_check = |cmd: &str, expected: &str| {
|
|
||||||
if let Some(output) = run_command(cmd, &["--version"]) {
|
|
||||||
if output.to_lowercase().contains(&expected.to_lowercase()) {
|
|
||||||
println!("OK: {:<4} is {}", cmd, expected);
|
|
||||||
} else {
|
|
||||||
eprintln!("ERROR: {:<4} is NOT {}", cmd, expected);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
alias_check("awk", "GNU");
|
|
||||||
alias_check("yacc", "Bison");
|
|
||||||
alias_check("sh", "Bash");
|
|
||||||
|
|
||||||
// Compiler-Test
|
|
||||||
if run_command("g++", &["--version"]).is_some() {
|
|
||||||
println!("OK: g++ works");
|
|
||||||
} else {
|
|
||||||
eprintln!("ERROR: g++ does NOT work");
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// nproc-Test
|
|
||||||
let nproc = run_command("nproc", &[]).unwrap_or_default();
|
|
||||||
if nproc.is_empty() {
|
|
||||||
eprintln!("ERROR: nproc is not available or empty");
|
|
||||||
ok = false;
|
|
||||||
} else {
|
|
||||||
println!("OK: nproc reports {} logical cores available", nproc);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
eprintln!("Some version checks failed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ok)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,14 @@ use reqwest::blocking::Client;
|
||||||
use reqwest::redirect::Policy;
|
use reqwest::redirect::Policy;
|
||||||
|
|
||||||
pub fn get_wget_list() -> Result<String> {
|
pub fn get_wget_list() -> Result<String> {
|
||||||
let client = Client::builder().redirect(Policy::none()).build()?;
|
let client = Client::builder().redirect(Policy::limited(5)).build()?;
|
||||||
let res = client
|
let res = client
|
||||||
.get("https://www.linuxfromscratch.org/~thomas/multilib-m32/wget-list-sysv")
|
.get("https://www.linuxfromscratch.org/~thomas/multilib-m32/wget-list-sysv")
|
||||||
.send()?
|
.send()?;
|
||||||
.text()?;
|
|
||||||
Ok(res)
|
if !res.status().is_success() {
|
||||||
|
anyhow::bail!("Failed to fetch wget-list: HTTP {}", res.status());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res.text()?)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue