From 208234df276626765e9c762189791703b70e5382 Mon Sep 17 00:00:00 2001 From: Lucy Date: Tue, 30 Sep 2025 20:38:13 +0200 Subject: [PATCH] working --- Cargo.lock | 217 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 + src/html.rs | 33 +++++++ src/main.rs | 40 +++----- src/mirrors.rs | 46 ++++----- src/tui.rs | 165 ++++++++++++++++++++------------ src/version_check.rs | 101 ++++++++++++++++---- 7 files changed, 478 insertions(+), 127 deletions(-) create mode 100644 src/html.rs diff --git a/Cargo.lock b/Cargo.lock index 52ac6a8..1f51225 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -131,6 +140,15 @@ version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.19.0" @@ -278,6 +296,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "crossterm" version = "0.28.1" @@ -321,6 +348,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "cssparser" version = "0.31.2" @@ -411,6 +448,16 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -422,6 +469,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "document-features" version = "0.2.11" @@ -606,6 +659,16 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getopts" version = "0.2.24" @@ -712,6 +775,21 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "html_parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f56db07b6612644f6f7719f8ef944f75fff9d6378fdf3d316fd32194184abd" +dependencies = [ + "doc-comment", + "pest", + "pest_derive", + "serde", + "serde_derive", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "http" version = "1.3.1" @@ -1277,14 +1355,17 @@ dependencies = [ "clap", "console", "crossterm 0.29.0", + "html_parser", "indicatif", "md5", "num_cpus", "rand 0.9.2", "ratatui", + "regex", "reqwest", "scraper", "serde", + "serde_json", "spinners", ] @@ -1323,6 +1404,50 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8" +dependencies = [ + "memchr", + "thiserror 2.0.17", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc58706f770acb1dbd0973e6530a3cff4746fb721207feb3a8a6064cd0b6c663" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d4f36811dfe07f7b8573462465d5cb8965fffc2e71ae377a33aecf14c2c9a2f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "pest_meta" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42919b05089acbd0a5dcd5405fb304d17d1053847b81163d09c4ad18ce8e8420" +dependencies = [ + "pest", + "sha2", +] + [[package]] name = "phf" version = "0.10.1" @@ -1574,6 +1699,35 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + [[package]] name = "reqwest" version = "0.12.23" @@ -1844,6 +1998,17 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2105,6 +2270,46 @@ dependencies = [ "utf-8", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "tinystr" version = "0.8.1" @@ -2235,6 +2440,18 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-ident" version = "1.0.19" diff --git a/Cargo.toml b/Cargo.toml index 79b1063..b667dea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,15 +8,18 @@ anyhow = "1.0.100" clap = { version = "4.5.48", features= ["derive"] } console = "0.16.1" crossterm = { version = "0.29.0", optional = true } +html_parser = "0.7.0" indicatif = "0.18.0" md5 = "0.8.0" num_cpus = "1.17.0" rand = "0.9.2" ratatui = { version = "0.29.0", optional = true } +regex = "1.11.3" reqwest = { version = "0.12.23", features = ["blocking", "json"] } scraper = "0.19.0" serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.145" spinners = "4.1.1" [features] diff --git a/src/html.rs b/src/html.rs new file mode 100644 index 0000000..0d9281c --- /dev/null +++ b/src/html.rs @@ -0,0 +1,33 @@ +use html_parser::Dom; +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_and_parse_html_to_json(url: &str) -> Result> { + // HTML herunterladen + let response = get(url)?; + if !response.status().is_success() { + return Err(format!("Fehler beim Abrufen der URL {}: {}", url, response.status()).into()); + } + + let body = response.text()?; + + // 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")); + } +} diff --git a/src/main.rs b/src/main.rs index c18abaf..debfdb4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,12 @@ mod downloader; +mod html; mod md5_utils; mod mirrors; mod version_check; mod wget_list; #[cfg(feature = "tui")] -mod tui; // Importiere das TUI-Modul, wenn das Feature aktiv ist +mod tui; use console::style; use rand::Rng; @@ -16,34 +17,30 @@ use std::path::PathBuf; fn main() -> Result<(), Box> { #[cfg(feature = "tui")] { - // Wenn das TUI-Feature aktiv ist, starte das TUI-Menü + // TUI-Modus tui::tui_menu()?; Ok(()) } #[cfg(not(feature = "tui"))] { - // Wenn das TUI-Feature NICHT aktiv ist, führe die CLI-Logik aus - // --- Run host system version checks --- - if version_check::run_version_checks() { - eprintln!( - "{} Host system does not meet minimum requirements. Exiting.", - style("❌").red().bold() - ); - std::process::exit(1); - } + // --- Dynamische Version-Prüfung aus HTML --- + let json = html::fetch_and_parse_html_to_json( + "https://www.linuxfromscratch.org/~thomas/multilib-m32/chapter02/hostreqs.html", + )?; + version_check::run_version_checks_from_json(&json); println!( "{} All version checks passed. Starting downloader...", style("✅").green().bold() ); - // --- Determine LFS sources path --- + // --- 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(); // Verwende thread_rng() statt rng() - let random_number: u32 = rng.gen_range(1000..=9999); // Verwende gen_range() statt random_range() + 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 {}", @@ -54,20 +51,13 @@ fn main() -> Result<(), Box> { } }; - // --- Choose mirror and fetch wget list --- - // Diese Zeile wird entfernt, da die Mirror-Auswahl in der TUI erfolgt - // let package_mirror = mirrors::choose_package_mirror(); - - // Da die Mirror-Auswahl nun in der TUI erfolgt, müssen wir hier einen Standardwert oder eine andere Logik verwenden, - // wenn das TUI nicht aktiv ist. Für dieses Beispiel nehmen wir an, dass wir keinen Mirror verwenden, - // wenn das TUI nicht aktiv ist, oder wir könnten eine andere CLI-basierte Auswahl implementieren. - // Für den Moment setzen wir es auf None, was bedeutet, dass der Standard-Mirror verwendet wird. + // --- CLI Mirror-Auswahl: default oder erweiterbar --- let package_mirror: Option = None; - + // --- Hole wget-Liste --- let wget_list = wget_list::get_wget_list()?; - // --- Prepare MD5 map --- + // --- Bereite MD5-Map vor --- let mut md5_map: HashMap = HashMap::new(); let md5_content = md5_utils::get_md5sums()?; for line in md5_content.lines() { @@ -77,7 +67,7 @@ fn main() -> Result<(), Box> { } } - // --- Download files --- + // --- Lade Dateien herunter --- downloader::download_files(&wget_list, &lfs_sources, package_mirror, Some(&md5_map))?; println!("{} All done!", style("🎉").green().bold()); diff --git a/src/mirrors.rs b/src/mirrors.rs index 72f2bc6..c8a442e 100644 --- a/src/mirrors.rs +++ b/src/mirrors.rs @@ -3,6 +3,29 @@ use reqwest::blocking::Client; use scraper::{Html, Selector}; use std::io::{self, Write}; +pub fn fetch_mirrors() -> Result, Box> { + let client = Client::new(); + let res = client + .get("https://www.linuxfromscratch.org/lfs/mirrors.html#files") + .send()? + .text()?; + let document = Html::parse_document(&res); + let selector = Selector::parse("a[href^='http']").unwrap(); + let mirrors = document + .select(&selector) + .filter_map(|element| { + let href = element.value().attr("href")?; + // Basic filtering to get potential mirror URLs + if href.contains("ftp.gnu.org") || href.contains("mirror") { + Some(href.to_string()) + } else { + None + } + }) + .collect(); + Ok(mirrors) +} + pub fn choose_package_mirror() -> Option { let mirrors = match fetch_mirrors() { Ok(mirrors) => mirrors, @@ -42,26 +65,3 @@ pub fn choose_package_mirror() -> Option { Some(chosen.to_string()) } } - -pub fn fetch_mirrors() -> Result, Box> { - let client = Client::new(); - let res = client - .get("https://www.linuxfromscratch.org/lfs/mirrors.html#files") - .send()? - .text()?; - let document = Html::parse_document(&res); - let selector = Selector::parse("a[href^='http']").unwrap(); - let mirrors = document - .select(&selector) - .filter_map(|element| { - let href = element.value().attr("href")?; - // Basic filtering to get potential mirror URLs - if href.contains("ftp.gnu.org") || href.contains("mirror") { - Some(href.to_string()) - } else { - None - } - }) - .collect(); - Ok(mirrors) -} diff --git a/src/tui.rs b/src/tui.rs index 433a78a..c2feecf 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,4 +1,6 @@ #[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::{ @@ -7,6 +9,8 @@ use crossterm::{ terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode}, }; #[cfg(feature = "tui")] +use rand::Rng; +#[cfg(feature = "tui")] use ratatui::{ Terminal, backend::CrosstermBackend, @@ -17,25 +21,47 @@ use ratatui::{ #[cfg(feature = "tui")] use std::{ collections::HashMap, + fs, io::stdout, path::PathBuf, sync::{Arc, Mutex}, thread, time::{Duration, Instant}, - fs, // Added for file system operations }; -#[cfg(feature = "tui")] // Hinzugefügt: Import des Rng-Traits -use rand::Rng; +#[cfg(feature = "tui")] +use reqwest::blocking::get; +#[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> { + 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::>().join(" ")); + } + + Ok(serde_json::to_string_pretty( + &json!({ "body_text": contents }), + )?) +} + +// ----------------- TUI functions ----------------- #[cfg(feature = "tui")] fn init_environment() -> Result> { - let mut rng = rand::rng(); + 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"); - std::fs::create_dir_all(&lfs_sources_path)?; - + fs::create_dir_all(&lfs_sources_path)?; Ok(lfs_sources_path) } @@ -58,7 +84,7 @@ fn select_mirrors_tui(mirrors: Vec) -> Vec { loop { terminal .draw(|f| { - let size = f.area(); + let size = f.size(); let items: Vec = mirrors .iter() .enumerate() @@ -70,8 +96,8 @@ fn select_mirrors_tui(mirrors: Vec) -> Vec { let list = List::new(items) .block( Block::default() - .borders(Borders::ALL) - .title("Select mirrors"), + .title("Select mirrors") + .borders(Borders::ALL), ) .highlight_symbol(">> "); f.render_stateful_widget(list, size, &mut state); @@ -182,7 +208,7 @@ fn download_packages_tui(lfs_sources: &PathBuf) { loop { terminal .draw(|f| { - let size = f.area(); + let size = f.size(); let items: Vec = { let state = download_state.lock().unwrap(); state @@ -210,6 +236,26 @@ fn download_packages_tui(lfs_sources: &PathBuf) { execute!(terminal.backend_mut(), LeaveAlternateScreen).unwrap(); } +// ----------------- Cleanup function ----------------- +#[cfg(feature = "tui")] +fn cleanup_temp_directories() -> Result> { + 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> { let mut stdout = stdout(); @@ -222,19 +268,20 @@ pub fn tui_menu() -> Result<(), Box> { "🌱 Init environment", "📦 Download packages", "🔍 Check status", - "🧹 Clean up temp directories", // Neuer Menüpunkt + "🧹 Clean up temp directories", + "📄 Test JSON fetch", // NEW BUTTON "❌ Exit", ]; let mut state = ListState::default(); state.select(Some(0)); let mut lfs_sources: Option = None; - let mut status_message: Option = None; // Für Statusmeldungen - let mut status_message_timer: Option = None; // Für temporäre Meldungen + let mut status_message: Option = None; + let mut status_message_timer: Option = None; loop { terminal.draw(|f| { - let size = f.area(); + let size = f.size(); let block = Block::default() .title("✨ lpkg TUI 🌈") .borders(Borders::ALL); @@ -256,34 +303,35 @@ pub fn tui_menu() -> Result<(), Box> { f.render_widget(list, chunks[i]); } - // Statusmeldung anzeigen if let Some(msg) = &status_message { - let msg_block = Block::default() - .borders(Borders::NONE) - .style(Style::default().fg(Color::Yellow)); - let paragraph = Paragraph::new(msg.as_str()).block(msg_block); - // Positionierung der Statusmeldung (z.B. unten in der Mitte) + let paragraph = Paragraph::new(msg.as_str()).block( + Block::default() + .borders(Borders::NONE) + .style(Style::default().fg(Color::Yellow)), + ); let msg_area = Layout::default() .direction(Direction::Horizontal) - .constraints([Constraint::Percentage(10), Constraint::Percentage(80), Constraint::Percentage(10)]) - .split(size)[1]; // Mittlerer Bereich + .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]; // Unterste Zeile + .split(msg_area)[1]; f.render_widget(paragraph, msg_area); } })?; - // Statusmeldung nach einer bestimmten Zeit ausblenden if let Some(timer) = status_message_timer { - if timer.elapsed() > Duration::from_secs(3) { // 3 Sekunden anzeigen + if timer.elapsed() > Duration::from_secs(3) { status_message = None; status_message_timer = None; } } - if let Event::Key(key) = event::read()? { match key.code { KeyCode::Down => { @@ -300,44 +348,61 @@ pub fn tui_menu() -> Result<(), Box> { } KeyCode::Enter => match state.selected() { Some(0) => { + // Init environment match init_environment() { Ok(path) => { lfs_sources = Some(path.clone()); - status_message = Some(format!("✅ Environment initialized: {}", path.display())); + status_message = + Some(format!("✅ Environment initialized: {}", path.display())); } Err(e) => { - status_message = Some(format!("❌ Failed to initialize environment: {}", e)); + status_message = + Some(format!("❌ Failed to init environment: {}", e)); } } - status_message_timer = Some(std::time::Instant::now()); + status_message_timer = Some(Instant::now()); } Some(1) => { - if let Some(ref path) = lfs_sources { + // Download packages + if let Some(path) = &lfs_sources { download_packages_tui(path); - } else { - status_message = Some("⚠️ Please initialize environment first!".to_string()); - status_message_timer = Some(std::time::Instant::now()); } } Some(2) => { + // Status status_message = Some("🔍 Status selected! (TODO)".to_string()); - status_message_timer = Some(std::time::Instant::now()); + status_message_timer = Some(Instant::now()); } Some(3) => { - // Aufruf der neuen Bereinigungsfunktion - status_message = Some("🧹 Cleaning up temporary directories...".to_string()); - status_message_timer = Some(std::time::Instant::now()); + // Cleanup match cleanup_temp_directories() { Ok(count) => { - status_message = Some(format!("✅ Cleaned up {} temporary directories.", count)); + status_message = + Some(format!("✅ Cleaned {} temporary dirs", count)); } Err(e) => { - status_message = Some(format!("❌ Failed to clean up: {}", e)); + status_message = Some(format!("❌ Failed cleanup: {}", e)); } } - status_message_timer = Some(std::time::Instant::now()); + status_message_timer = Some(Instant::now()); } - Some(4) | _ => break, + 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, _ => {} @@ -350,21 +415,3 @@ pub fn tui_menu() -> Result<(), Box> { terminal.show_cursor()?; Ok(()) } - -#[cfg(feature = "tui")] -fn cleanup_temp_directories() -> Result> { - let mut cleaned_count = 0; - for entry in std::fs::read_dir("/tmp")? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - if let Some(dir_name) = path.file_name().and_then(|s| s.to_str()) { - if dir_name.starts_with("lfs_") { - std::fs::remove_dir_all(&path)?; - cleaned_count += 1; - } - } - } - } - Ok(cleaned_count) -} diff --git a/src/version_check.rs b/src/version_check.rs index adec279..b950c7e 100644 --- a/src/version_check.rs +++ b/src/version_check.rs @@ -1,6 +1,9 @@ +use reqwest; +use scraper::{Html, Selector}; use std::process::Command; -pub fn run_command(cmd: &str, args: &[&str]) -> Option { +/// Führt ein Kommando aus und gibt die erste Zeile der Version zurück +fn run_command(cmd: &str, args: &[&str]) -> Option { let output = Command::new(cmd).args(args).output().ok()?; if output.status.success() { Some(String::from_utf8_lossy(&output.stdout).trim().to_string()) @@ -9,7 +12,8 @@ pub fn run_command(cmd: &str, args: &[&str]) -> Option { } } -pub fn check_version(installed: &str, required: &str) -> bool { +/// Vergleicht zwei Versionen +fn check_version(installed: &str, required: &str) -> bool { let parse_ver = |v: &str| { v.split(['.', '-']) .filter_map(|s| s.parse::().ok()) @@ -29,8 +33,9 @@ pub fn check_version(installed: &str, required: &str) -> bool { i.len() >= r.len() } -pub fn ver_check(program: &str, arg: &str, min_version: &str) -> bool { - match run_command(program, &[arg]) { +/// 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() @@ -57,27 +62,83 @@ pub fn ver_check(program: &str, arg: &str, min_version: &str) -> bool { } } -pub fn run_version_checks() -> bool { +/// 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_version(&kernel, min_version) { + println!("OK: Linux Kernel {} >= {}", kernel, min_version); + true + } else { + println!( + "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<(), Box> { + 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; - ok &= ver_check("bash", "--version", "3.2"); - ok &= ver_check("gcc", "--version", "5.4"); - ok &= ver_check("make", "--version", "4.0"); - ok &= ver_check("tar", "--version", "1.22"); + for element in document.select(&selector) { + let pre_text = element.text().collect::>().join("\n"); - // Kernel check - if let Some(kernel) = run_command("uname", &["-r"]) { - if check_version(&kernel, "5.4") { - println!("OK: Linux Kernel {} >= 5.4", kernel); - } else { - println!("ERROR: Linux Kernel {} is too old (5.4 required)", kernel); - ok = false; + for line in pre_text.lines() { + let line = line.trim(); + if line.starts_with("ver_check") { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 4 { + let prog = parts[1]; + let cmd = parts[2]; + let ver = parts[3]; + ok &= ver_check(prog, cmd, ver); + } + } else if line.starts_with("ver_kernel") { + if let Some(ver) = line.split_whitespace().nth(1) { + ok &= ver_kernel(ver); + } + } } } - // CPU cores - let cores = num_cpus::get(); - println!("OK: {} logical cores available", cores); + // Alias-Checks + 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 { + println!("ERROR: {:<4} is NOT {}", cmd, expected); + } + } + }; - ok + alias_check("awk", "GNU"); + alias_check("yacc", "Bison"); + alias_check("sh", "Bash"); + + // Compiler-Test + if run_command("g++", &["-x", "c++", "-"]).is_some() { + println!("OK: g++ works"); + } else { + println!("ERROR: g++ does NOT work"); + } + + // nproc-Test + let nproc = run_command("nproc", &[]).unwrap_or_default(); + if nproc.is_empty() { + println!("ERROR: nproc is not available or empty"); + } else { + println!("OK: nproc reports {} logical cores available", nproc); + } + + if !ok { + println!("Some version checks failed."); + } + + Ok(()) }