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::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<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
// 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<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
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<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

View file

@ -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<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
#[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<Vec<PackageInfo>>;
/// 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
fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>;

View file

@ -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<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
// 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::<Vec<_>>()
// 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

View file

@ -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");
}
}
}
}