diff --git a/Cargo.lock b/Cargo.lock index fc05d080..14609e8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -885,6 +885,7 @@ dependencies = [ "redoxer", "regex", "serde", + "serde_json", "strip-ansi-escapes", "termion", "toml", @@ -1018,24 +1019,47 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -1458,3 +1482,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index ad32286f..91e9cca2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,12 +42,13 @@ redox-pkg = { git = "https://gitlab.redox-os.org/redox-os/pkgutils.git", default redox_installer = { git = "https://gitlab.redox-os.org/redox-os/installer.git", default-features = false } redoxer = { git = "https://gitlab.redox-os.org/redox-os/redoxer.git", default-features = false } regex = "1.11" -serde = { version = "=1.0.197", features = ["derive"] } +serde = { version = "1", features = ["derive"] } termion = "4" toml = "0.8" walkdir = "2.3.1" ansi-to-tui = { version = "8", optional = true } strip-ansi-escapes = { version = "0.2.1", optional = true } +serde_json = "1" [dependencies.ratatui] version = "0.30" diff --git a/src/web.rs b/src/web.rs index e669bc8a..9eaaf626 100644 --- a/src/web.rs +++ b/src/web.rs @@ -13,6 +13,7 @@ use crate::{ }; pub mod html; +pub mod search; #[derive(Clone)] pub struct CliWebConfig { @@ -58,6 +59,7 @@ pub fn generate_web(all_packages: &Vec, config: &CliWebConfig) { let mut valid_packages = Vec::new(); let mut dependents_map: HashMap> = HashMap::new(); + let mut files_map: BTreeMap = BTreeMap::new(); for package_name in all_packages { let Ok(package_name) = PackageName::new(package_name) else { @@ -108,6 +110,10 @@ pub fn generate_web(all_packages: &Vec, config: &CliWebConfig) { &html_path, &config, ); + + if let Some(file_map) = stage_files { + files_map.insert(package.name.to_string(), file_map); + } } let mut grouped_packages: BTreeMap> = BTreeMap::new(); @@ -120,7 +126,16 @@ pub fn generate_web(all_packages: &Vec, config: &CliWebConfig) { let index_path = repo_path.join("index.html"); let style_path = repo_path.join("style.css"); generate_html_index(grouped_packages, &index_path, &config); - fs::write(style_path, CSS).expect("Failed to write CSS file"); + fs::write(style_path, CSS).expect("Failed to write style.css"); + + let mut index_files = search::FileIndexBuilder::new(); + for (package, files) in &files_map { + index_files.parse(package, files); + } + let files_path = repo_path.join("files.json"); + index_files + .write(&files_path) + .expect("Failed to write files.json"); } pub(crate) fn get_category(dir: &Path) -> String { diff --git a/src/web/search.rs b/src/web/search.rs new file mode 100644 index 00000000..85653709 --- /dev/null +++ b/src/web/search.rs @@ -0,0 +1,113 @@ +use serde::Serialize; +use std::collections::BTreeMap; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +use crate::{Result, wrap_io_err}; + +#[derive(Serialize, Debug, Clone)] +#[serde(untagged)] +enum FsNode { + Dir(BTreeMap), + File(String), +} + +pub struct FileIndexBuilder { + inner: BTreeMap, +} +impl FileIndexBuilder { + pub const fn new() -> Self { + Self { + inner: BTreeMap::new(), + } + } + fn insert_node(&mut self, path_stack: &[String], name: &str, pkg_name: Option<&str>) { + let mut current = &mut self.inner; + for dir in path_stack { + let node = current + .entry(dir.clone()) + .or_insert_with(|| FsNode::Dir(BTreeMap::new())); + + if let FsNode::File(_) = node { + // previously a file, silently replace to dir + *node = FsNode::Dir(BTreeMap::new()); + } + + match node { + FsNode::Dir(map) => current = map, + _ => unreachable!(), + } + } + + if let Some(pkg) = pkg_name { + current + .entry(name.to_string()) + .and_modify(|node| { + if let FsNode::File(existing_pkgs) = node { + existing_pkgs.push(','); + existing_pkgs.push_str(pkg); + } else { + *node = FsNode::File(pkg.to_string()); + } + }) + .or_insert_with(|| FsNode::File(pkg.to_string())); + } else { + current + .entry(name.to_string()) + .or_insert_with(|| FsNode::Dir(BTreeMap::new())); + } + } + pub fn parse(&mut self, pkg: &str, content: &str) { + let mut path_stack: Vec = Vec::new(); + + for line in content.lines() { + if line.trim().is_empty() { + continue; + } + + let mut prefix_chars: usize = 0; + let mut name_start_byte = 0; + + for (idx, c) in line.char_indices() { + if "│├└─ ".contains(c) { + prefix_chars += 1; + } else { + name_start_byte = idx; + break; + } + } + + if name_start_byte == 0 && prefix_chars == 0 { + continue; + } + + let depth = (prefix_chars / 4).saturating_sub(1); + let remainder = line[name_start_byte..].trim(); + + path_stack.truncate(depth); + + if remainder.ends_with('/') { + let name = remainder.trim_end_matches('/'); + self.insert_node(&path_stack, name, None); + path_stack.push(name.to_string()); + } else { + let name = if let Some(paren_idx) = remainder.rfind('(') { + // Strip size + remainder[..paren_idx].trim() + } else { + remainder + }; + self.insert_node(&path_stack, name, Some(pkg)); + } + } + } + pub fn write(&self, path: &Path) -> Result<()> { + // JSON: because javascript have them natively + let json_output = serde_json::to_string(&self.inner).unwrap(); + let mut file = File::create(path).map_err(wrap_io_err!(path, "Opening file"))?; + file.write_all(json_output.as_bytes()) + .map_err(wrap_io_err!(path, "Writing file"))?; + Ok(()) + } +}