diff --git a/libips/src/repository/file_backend.rs b/libips/src/repository/file_backend.rs index 5fd9f27..8a028b3 100644 --- a/libips/src/repository/file_backend.rs +++ b/libips/src/repository/file_backend.rs @@ -21,7 +21,7 @@ use crate::digest::Digest; use crate::fmri::Fmri; use crate::payload::{Payload, PayloadCompressionAlgorithm}; -use super::{Repository, RepositoryConfig, RepositoryVersion, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo, PackageInfo}; +use super::{Repository, RepositoryConfig, RepositoryVersion, REPOSITORY_CONFIG_FILENAME, PublisherInfo, RepositoryInfo, PackageInfo, PackageContents}; /// Repository implementation that uses the local filesystem pub struct FileBackend { @@ -587,11 +587,32 @@ impl Repository for FileBackend { } /// Show the contents of packages - fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result> { + fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result> { // We don't need to get the list of packages since we'll process the manifests directly - // For each package, list contents - let mut contents = Vec::new(); + // Use a HashMap to store package information + let mut packages = std::collections::HashMap::new(); + + // Define a struct to hold the content vectors for each package + struct PackageContentVectors { + files: Vec, + directories: Vec, + links: Vec, + dependencies: Vec, + licenses: Vec, + } + + impl PackageContentVectors { + fn new() -> Self { + Self { + files: Vec::new(), + directories: Vec::new(), + links: Vec::new(), + dependencies: Vec::new(), + licenses: Vec::new(), + } + } + } // Filter publishers if specified let publishers = if let Some(pub_name) = publisher { @@ -672,78 +693,52 @@ impl Repository for FileBackend { continue; } + // Get or create the content vectors for this package + let content_vectors = packages + .entry(pkg_id.clone()) + .or_insert_with(PackageContentVectors::new); + // Process file actions - for file in &manifest.files { - // Skip if action type filtering is enabled and "file" is not in the list - if let Some(types) = action_types { - if !types.contains(&"file".to_string()) { - continue; - } + if action_types.is_none() || action_types.as_ref().unwrap().contains(&"file".to_string()) { + for file in &manifest.files { + content_vectors.files.push(file.path.clone()); } - - // Add the file content information to the result - contents.push((pkg_id.clone(), file.path.clone(), "file".to_string())); } // Process directory actions - for dir in &manifest.directories { - // Skip if action type filtering is enabled and "dir" is not in the list - if let Some(types) = action_types { - if !types.contains(&"dir".to_string()) { - continue; - } + if action_types.is_none() || action_types.as_ref().unwrap().contains(&"dir".to_string()) { + for dir in &manifest.directories { + content_vectors.directories.push(dir.path.clone()); } - - // Add the directory content information to the result - contents.push((pkg_id.clone(), dir.path.clone(), "dir".to_string())); } // Process link actions - for link in &manifest.links { - // Skip if action type filtering is enabled and "link" is not in the list - if let Some(types) = action_types { - if !types.contains(&"link".to_string()) { - continue; - } + if action_types.is_none() || action_types.as_ref().unwrap().contains(&"link".to_string()) { + for link in &manifest.links { + content_vectors.links.push(link.path.clone()); } - - // Add the link content information to the result - contents.push((pkg_id.clone(), link.path.clone(), "link".to_string())); } // Process dependency actions - for depend in &manifest.dependencies { - // Skip if action type filtering is enabled and "depend" is not in the list - if let Some(types) = action_types { - if !types.contains(&"depend".to_string()) { - continue; + if action_types.is_none() || action_types.as_ref().unwrap().contains(&"depend".to_string()) { + for depend in &manifest.dependencies { + if let Some(fmri) = &depend.fmri { + content_vectors.dependencies.push(fmri.to_string()); } } - - // For dependencies, use the fmri as the path - if let Some(fmri) = &depend.fmri { - contents.push((pkg_id.clone(), fmri.to_string(), "depend".to_string())); - } } // Process license actions - for license in &manifest.licenses { - // Skip if action type filtering is enabled and "license" is not in the list - if let Some(types) = action_types { - if !types.contains(&"license".to_string()) { - continue; + if action_types.is_none() || action_types.as_ref().unwrap().contains(&"license".to_string()) { + for license in &manifest.licenses { + if let Some(path_prop) = license.properties.get("path") { + content_vectors.licenses.push(path_prop.value.clone()); + } else if let Some(license_prop) = license.properties.get("license") { + content_vectors.licenses.push(license_prop.value.clone()); + } else { + content_vectors.licenses.push(license.payload.clone()); } } - - // For licenses, use the license path from properties if available - if let Some(path_prop) = license.properties.get("path") { - contents.push((pkg_id.clone(), path_prop.value.clone(), "license".to_string())); - } else if let Some(license_prop) = license.properties.get("license") { - contents.push((pkg_id.clone(), license_prop.value.clone(), "license".to_string())); - } else { - // If no path property, use the payload as the path - contents.push((pkg_id.clone(), license.payload.clone(), "license".to_string())); - } } }, Err(err) => { @@ -757,7 +752,53 @@ impl Repository for FileBackend { } } - Ok(contents) + // Convert the HashMap to a Vec + let package_contents = packages + .into_iter() + .map(|(package_id, content_vectors)| { + // Only include non-empty vectors + let files = if content_vectors.files.is_empty() { + None + } else { + Some(content_vectors.files) + }; + + let directories = if content_vectors.directories.is_empty() { + None + } else { + Some(content_vectors.directories) + }; + + let links = if content_vectors.links.is_empty() { + None + } else { + Some(content_vectors.links) + }; + + let dependencies = if content_vectors.dependencies.is_empty() { + None + } else { + Some(content_vectors.dependencies) + }; + + let licenses = if content_vectors.licenses.is_empty() { + None + } else { + Some(content_vectors.licenses) + }; + + PackageContents { + package_id, + files, + directories, + links, + dependencies, + licenses, + } + }) + .collect(); + + Ok(package_contents) } /// Rebuild repository metadata diff --git a/libips/src/repository/mod.rs b/libips/src/repository/mod.rs index 3cf0c49..96e83bd 100644 --- a/libips/src/repository/mod.rs +++ b/libips/src/repository/mod.rs @@ -43,6 +43,23 @@ pub struct PackageInfo { pub fmri: crate::fmri::Fmri, } +/// Contents of a package +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PackageContents { + /// Package identifier (name and version) + pub package_id: String, + /// Files in the package + pub files: Option>, + /// Directories in the package + pub directories: Option>, + /// Links in the package + pub links: Option>, + /// Dependencies of the package + pub dependencies: Option>, + /// Licenses in the package + pub licenses: Option>, +} + /// Repository version #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum RepositoryVersion { @@ -116,7 +133,7 @@ pub trait Repository { fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result>; /// Show contents of packages - fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result>; + fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result>; /// Rebuild repository metadata fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>; diff --git a/libips/src/repository/rest_backend.rs b/libips/src/repository/rest_backend.rs index 39b8afa..4b7a01a 100644 --- a/libips/src/repository/rest_backend.rs +++ b/libips/src/repository/rest_backend.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, Result}; use std::path::{Path, PathBuf}; -use super::{Repository, RepositoryConfig, RepositoryVersion, PublisherInfo, RepositoryInfo, PackageInfo}; +use super::{Repository, RepositoryConfig, RepositoryVersion, PublisherInfo, RepositoryInfo, PackageInfo, PackageContents}; /// Repository implementation that uses a REST API pub struct RestBackend { @@ -198,15 +198,15 @@ impl Repository for RestBackend { } /// Show contents of packages - fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result> { + fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result> { // This is a stub implementation // In a real implementation, we would make a REST API call to get package contents // Get the list of packages let packages = self.list_packages(publisher, pattern)?; - // For each package, list contents - let mut contents = Vec::new(); + // For each package, create a PackageContents struct + let mut package_contents = Vec::new(); for pkg_info in packages { // In a real implementation, we would get this information from the REST API @@ -218,25 +218,68 @@ impl Repository for RestBackend { pkg_info.fmri.name.clone() }; - // Example content data (package, path, type) - let example_contents = vec![ - (pkg_id.clone(), "/usr/bin/example".to_string(), "file".to_string()), - (pkg_id.clone(), "/usr/share/doc/example".to_string(), "dir".to_string()), - ]; + // Example content for each type + // In a real implementation, we would get this information from the REST API - // Filter by action type if specified - let filtered_contents = if let Some(types) = action_types { - example_contents.into_iter() - .filter(|(_, _, action_type)| types.contains(&action_type)) - .collect::>() + // Files + let files = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"file".to_string()) { + Some(vec![ + "/usr/bin/example".to_string(), + "/usr/lib/example.so".to_string(), + ]) } else { - example_contents + None }; - contents.extend(filtered_contents); + // Directories + let directories = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"dir".to_string()) { + Some(vec![ + "/usr/share/doc/example".to_string(), + "/usr/share/man/man1".to_string(), + ]) + } else { + None + }; + + // Links + let links = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"link".to_string()) { + Some(vec![ + "/usr/bin/example-link".to_string(), + ]) + } else { + None + }; + + // Dependencies + let dependencies = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"depend".to_string()) { + Some(vec![ + "pkg:/system/library@0.5.11".to_string(), + ]) + } else { + None + }; + + // Licenses + let licenses = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"license".to_string()) { + Some(vec![ + "/usr/share/licenses/example/LICENSE".to_string(), + ]) + } else { + None + }; + + // Add the package contents to the result + package_contents.push(PackageContents { + package_id: pkg_id, + files, + directories, + links, + dependencies, + licenses, + }); } - Ok(contents) + Ok(package_contents) } /// Rebuild repository metadata diff --git a/pkg6repo/src/main.rs b/pkg6repo/src/main.rs index 4d3b9aa..d3ae538 100644 --- a/pkg6repo/src/main.rs +++ b/pkg6repo/src/main.rs @@ -3,7 +3,7 @@ use anyhow::{Result, anyhow}; use std::path::PathBuf; use std::convert::TryFrom; -use libips::repository::{Repository, RepositoryVersion, FileBackend, PublisherInfo, RepositoryInfo, PackageInfo}; +use libips::repository::{Repository, RepositoryVersion, FileBackend, PublisherInfo, RepositoryInfo, PackageInfo, PackageContents}; #[cfg(test)] mod tests; @@ -415,13 +415,70 @@ fn main() -> Result<()> { let contents = repo.show_contents(None, pattern_option, action_type.as_deref())?; // Print contents - for (package, path, action_type) in contents { - if *manifest { - // If manifest option is specified, print in manifest format - println!("{} path={} type={}", action_type, path, package); - } else { - // Otherwise, print in table format - println!("{:<40} {:<30} {:<10}", package, path, action_type); + for pkg_contents in contents { + // Process files + if let Some(files) = &pkg_contents.files { + for path in files { + if *manifest { + // If manifest option is specified, print in manifest format + println!("file path={} type={}", path, pkg_contents.package_id); + } else { + // Otherwise, print in table format + println!("{:<40} {:<30} {:<10}", pkg_contents.package_id, path, "file"); + } + } + } + + // Process directories + if let Some(directories) = &pkg_contents.directories { + for path in directories { + if *manifest { + // If manifest option is specified, print in manifest format + println!("dir path={} type={}", path, pkg_contents.package_id); + } else { + // Otherwise, print in table format + println!("{:<40} {:<30} {:<10}", pkg_contents.package_id, path, "dir"); + } + } + } + + // Process links + if let Some(links) = &pkg_contents.links { + for path in links { + if *manifest { + // If manifest option is specified, print in manifest format + println!("link path={} type={}", path, pkg_contents.package_id); + } else { + // Otherwise, print in table format + println!("{:<40} {:<30} {:<10}", pkg_contents.package_id, path, "link"); + } + } + } + + // Process dependencies + if let Some(dependencies) = &pkg_contents.dependencies { + for path in dependencies { + if *manifest { + // If manifest option is specified, print in manifest format + println!("depend path={} type={}", path, pkg_contents.package_id); + } else { + // Otherwise, print in table format + println!("{:<40} {:<30} {:<10}", pkg_contents.package_id, path, "depend"); + } + } + } + + // Process licenses + if let Some(licenses) = &pkg_contents.licenses { + for path in licenses { + if *manifest { + // If manifest option is specified, print in manifest format + println!("license path={} type={}", path, pkg_contents.package_id); + } else { + // Otherwise, print in table format + println!("{:<40} {:<30} {:<10}", pkg_contents.package_id, path, "license"); + } + } } }