Introduce PackageContents struct for structured representation of package data, refactor show_contents for multiple content types, and enhance handling of manifests with improved filtering and output formatting.

This commit is contained in:
Till Wegmueller 2025-07-23 22:39:49 +02:00
parent 01b059bf5d
commit 2cb63fbef0
No known key found for this signature in database
4 changed files with 241 additions and 83 deletions

View file

@ -21,7 +21,7 @@ use crate::digest::Digest;
use crate::fmri::Fmri; use crate::fmri::Fmri;
use crate::payload::{Payload, PayloadCompressionAlgorithm}; 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 /// Repository implementation that uses the local filesystem
pub struct FileBackend { pub struct FileBackend {
@ -587,11 +587,32 @@ impl Repository for FileBackend {
} }
/// Show the contents of packages /// Show the contents of packages
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<(String, String, String)>> { fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<PackageContents>> {
// We don't need to get the list of packages since we'll process the manifests directly // We don't need to get the list of packages since we'll process the manifests directly
// For each package, list contents // Use a HashMap to store package information
let mut contents = Vec::new(); let mut packages = std::collections::HashMap::new();
// Define a struct to hold the content vectors for each package
struct PackageContentVectors {
files: Vec<String>,
directories: Vec<String>,
links: Vec<String>,
dependencies: Vec<String>,
licenses: Vec<String>,
}
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 // Filter publishers if specified
let publishers = if let Some(pub_name) = publisher { let publishers = if let Some(pub_name) = publisher {
@ -672,77 +693,51 @@ impl Repository for FileBackend {
continue; continue;
} }
// Process file actions // Get or create the content vectors for this package
for file in &manifest.files { let content_vectors = packages
// Skip if action type filtering is enabled and "file" is not in the list .entry(pkg_id.clone())
if let Some(types) = action_types { .or_insert_with(PackageContentVectors::new);
if !types.contains(&"file".to_string()) {
continue;
}
}
// Add the file content information to the result // Process file actions
contents.push((pkg_id.clone(), file.path.clone(), "file".to_string())); 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());
}
} }
// Process directory actions // Process directory actions
if action_types.is_none() || action_types.as_ref().unwrap().contains(&"dir".to_string()) {
for dir in &manifest.directories { for dir in &manifest.directories {
// Skip if action type filtering is enabled and "dir" is not in the list content_vectors.directories.push(dir.path.clone());
if let Some(types) = action_types {
if !types.contains(&"dir".to_string()) {
continue;
} }
} }
// Add the directory content information to the result
contents.push((pkg_id.clone(), dir.path.clone(), "dir".to_string()));
}
// Process link actions // Process link actions
if action_types.is_none() || action_types.as_ref().unwrap().contains(&"link".to_string()) {
for link in &manifest.links { for link in &manifest.links {
// Skip if action type filtering is enabled and "link" is not in the list content_vectors.links.push(link.path.clone());
if let Some(types) = action_types {
if !types.contains(&"link".to_string()) {
continue;
} }
} }
// Add the link content information to the result
contents.push((pkg_id.clone(), link.path.clone(), "link".to_string()));
}
// Process dependency actions // Process dependency actions
if action_types.is_none() || action_types.as_ref().unwrap().contains(&"depend".to_string()) {
for depend in &manifest.dependencies { 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;
}
}
// For dependencies, use the fmri as the path
if let Some(fmri) = &depend.fmri { if let Some(fmri) = &depend.fmri {
contents.push((pkg_id.clone(), fmri.to_string(), "depend".to_string())); content_vectors.dependencies.push(fmri.to_string());
}
} }
} }
// Process license actions // Process license actions
if action_types.is_none() || action_types.as_ref().unwrap().contains(&"license".to_string()) {
for license in &manifest.licenses { 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;
}
}
// For licenses, use the license path from properties if available
if let Some(path_prop) = license.properties.get("path") { if let Some(path_prop) = license.properties.get("path") {
contents.push((pkg_id.clone(), path_prop.value.clone(), "license".to_string())); content_vectors.licenses.push(path_prop.value.clone());
} else if let Some(license_prop) = license.properties.get("license") { } else if let Some(license_prop) = license.properties.get("license") {
contents.push((pkg_id.clone(), license_prop.value.clone(), "license".to_string())); content_vectors.licenses.push(license_prop.value.clone());
} else { } else {
// If no path property, use the payload as the path content_vectors.licenses.push(license.payload.clone());
contents.push((pkg_id.clone(), license.payload.clone(), "license".to_string())); }
} }
} }
}, },
@ -757,7 +752,53 @@ impl Repository for FileBackend {
} }
} }
Ok(contents) // Convert the HashMap to a Vec<PackageContents>
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 /// Rebuild repository metadata

View file

@ -43,6 +43,23 @@ pub struct PackageInfo {
pub fmri: crate::fmri::Fmri, 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<Vec<String>>,
/// Directories in the package
pub directories: Option<Vec<String>>,
/// Links in the package
pub links: Option<Vec<String>>,
/// Dependencies of the package
pub dependencies: Option<Vec<String>>,
/// Licenses in the package
pub licenses: Option<Vec<String>>,
}
/// Repository version /// Repository version
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum RepositoryVersion { pub enum RepositoryVersion {
@ -116,7 +133,7 @@ pub trait Repository {
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<PackageInfo>>; fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<PackageInfo>>;
/// Show contents of packages /// Show contents of packages
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<(String, String, String)>>; fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<PackageContents>>;
/// Rebuild repository metadata /// Rebuild repository metadata
fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>; fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>;

View file

@ -6,7 +6,7 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use std::path::{Path, PathBuf}; 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 /// Repository implementation that uses a REST API
pub struct RestBackend { pub struct RestBackend {
@ -198,15 +198,15 @@ impl Repository for RestBackend {
} }
/// Show contents of packages /// Show contents of packages
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<(String, String, String)>> { fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<PackageContents>> {
// This is a stub implementation // This is a stub implementation
// In a real implementation, we would make a REST API call to get package contents // In a real implementation, we would make a REST API call to get package contents
// Get the list of packages // Get the list of packages
let packages = self.list_packages(publisher, pattern)?; let packages = self.list_packages(publisher, pattern)?;
// For each package, list contents // For each package, create a PackageContents struct
let mut contents = Vec::new(); let mut package_contents = Vec::new();
for pkg_info in packages { for pkg_info in packages {
// In a real implementation, we would get this information from the REST API // 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() pkg_info.fmri.name.clone()
}; };
// Example content data (package, path, type) // Example content for each type
let example_contents = vec![ // In a real implementation, we would get this information from the REST API
(pkg_id.clone(), "/usr/bin/example".to_string(), "file".to_string()),
(pkg_id.clone(), "/usr/share/doc/example".to_string(), "dir".to_string()),
];
// Filter by action type if specified // Files
let filtered_contents = if let Some(types) = action_types { let files = if action_types.is_none() || action_types.as_ref().unwrap().contains(&"file".to_string()) {
example_contents.into_iter() Some(vec![
.filter(|(_, _, action_type)| types.contains(&action_type)) "/usr/bin/example".to_string(),
.collect::<Vec<_>>() "/usr/lib/example.so".to_string(),
])
} else { } 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 /// Rebuild repository metadata

View file

@ -3,7 +3,7 @@ use anyhow::{Result, anyhow};
use std::path::PathBuf; use std::path::PathBuf;
use std::convert::TryFrom; 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)] #[cfg(test)]
mod tests; mod tests;
@ -415,13 +415,70 @@ fn main() -> Result<()> {
let contents = repo.show_contents(None, pattern_option, action_type.as_deref())?; let contents = repo.show_contents(None, pattern_option, action_type.as_deref())?;
// Print contents // Print contents
for (package, path, action_type) in contents { for pkg_contents in contents {
// Process files
if let Some(files) = &pkg_contents.files {
for path in files {
if *manifest { if *manifest {
// If manifest option is specified, print in manifest format // If manifest option is specified, print in manifest format
println!("{} path={} type={}", action_type, path, package); println!("file path={} type={}", path, pkg_contents.package_id);
} else { } else {
// Otherwise, print in table format // Otherwise, print in table format
println!("{:<40} {:<30} {:<10}", package, path, action_type); 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");
}
}
} }
} }