use crate::{Config, PathData};
use std::ffi::OsString;
use std::fs::{FileType, Metadata};
use std::path::PathBuf;
use uucore::error::UResult;
#[derive(Debug, Clone)]
pub struct EntryInfo {
pub path: PathBuf,
pub display_name: OsString,
pub file_type: Option<FileType>,
pub metadata: Option<Metadata>,
pub security_context: String,
pub command_line: bool,
pub must_dereference: bool,
}
impl EntryInfo {
pub fn is_dir(&self) -> bool {
self.file_type.as_ref().is_some_and(FileType::is_dir)
}
pub fn is_file(&self) -> bool {
self.file_type.as_ref().is_some_and(FileType::is_file)
}
pub fn is_symlink(&self) -> bool {
self.file_type.as_ref().is_some_and(FileType::is_symlink)
}
pub fn size(&self) -> Option<u64> {
self.metadata.as_ref().map(Metadata::len)
}
pub fn file_name(&self) -> Option<&str> {
self.display_name.to_str()
}
}
pub enum StreamMode {
Batch,
Streaming,
}
pub trait LsOutput {
fn stream_mode(&self) -> StreamMode {
StreamMode::Batch
}
fn write_entry(&mut self, _entry: &EntryInfo) -> UResult<()> {
Ok(())
}
fn write_entries(&mut self, entries: &[PathData], config: &Config) -> UResult<()> {
for entry in entries {
self.write_entry(&entry.to_entry_info(config))?;
}
Ok(())
}
fn write_dir_header(
&mut self,
_path_data: &PathData,
_config: &Config,
_is_first: bool,
) -> UResult<()> {
Ok(())
}
fn write_total(&mut self, _total_size: u64, _config: &Config) -> UResult<()> {
Ok(())
}
fn flush(&mut self) -> UResult<()> {
Ok(())
}
fn finalize(&mut self, _config: &Config) -> UResult<()> {
Ok(())
}
fn initialize(&mut self, _config: &Config) -> UResult<()> {
Ok(())
}
}
#[derive(Debug, Default)]
pub struct StreamingOutput {
entries: Vec<EntryInfo>,
directories: Vec<PathBuf>,
totals: Vec<u64>,
}
impl StreamingOutput {
pub fn new() -> Self {
Self::default()
}
pub fn entries(&self) -> &[EntryInfo] {
&self.entries
}
pub fn into_entries(self) -> Vec<EntryInfo> {
self.entries
}
pub fn directories(&self) -> &[PathBuf] {
&self.directories
}
pub fn totals(&self) -> &[u64] {
&self.totals
}
pub fn clear(&mut self) {
self.entries.clear();
self.directories.clear();
self.totals.clear();
}
}
impl LsOutput for StreamingOutput {
fn stream_mode(&self) -> StreamMode {
StreamMode::Streaming
}
fn write_entry(&mut self, entry: &EntryInfo) -> UResult<()> {
self.entries.push(entry.clone());
Ok(())
}
fn write_dir_header(
&mut self,
path_data: &PathData,
_config: &Config,
_is_first: bool,
) -> UResult<()> {
self.directories.push(path_data.path().to_path_buf());
Ok(())
}
fn write_total(&mut self, total_size: u64, _config: &Config) -> UResult<()> {
self.totals.push(total_size);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entry_info_is_dir() {
let entry = EntryInfo {
path: PathBuf::from("/test/dir"),
display_name: OsString::from("dir"),
file_type: None,
metadata: None,
security_context: String::new(),
command_line: false,
must_dereference: false,
};
assert!(!entry.is_dir());
}
#[test]
fn test_entry_info_size() {
let entry = EntryInfo {
path: PathBuf::from("/test/file"),
display_name: OsString::from("file"),
file_type: None,
metadata: None,
security_context: String::new(),
command_line: false,
must_dereference: false,
};
assert_eq!(entry.size(), None);
}
#[test]
fn test_entry_info_file_name() {
let entry = EntryInfo {
path: PathBuf::from("/test/file.txt"),
display_name: OsString::from("file.txt"),
file_type: None,
metadata: None,
security_context: String::new(),
command_line: false,
must_dereference: false,
};
assert_eq!(entry.file_name(), Some("file.txt"));
}
#[test]
fn test_streaming_output_new() {
let collector = StreamingOutput::new();
assert!(collector.entries().is_empty());
assert!(collector.directories().is_empty());
assert!(collector.totals().is_empty());
}
#[test]
fn test_streaming_output_write_entry() {
let mut collector = StreamingOutput::new();
let entry = EntryInfo {
path: PathBuf::from("/test/file"),
display_name: OsString::from("file"),
file_type: None,
metadata: None,
security_context: String::new(),
command_line: false,
must_dereference: false,
};
collector.write_entry(&entry).unwrap();
assert_eq!(collector.entries().len(), 1);
assert_eq!(collector.entries()[0].display_name, OsString::from("file"));
}
#[test]
fn test_streaming_output_clear() {
let mut collector = StreamingOutput::new();
let entry = EntryInfo {
path: PathBuf::from("/test/file"),
display_name: OsString::from("file"),
file_type: None,
metadata: None,
security_context: String::new(),
command_line: false,
must_dereference: false,
};
collector.write_entry(&entry).unwrap();
collector.clear();
assert!(collector.entries().is_empty());
assert!(collector.directories().is_empty());
assert!(collector.totals().is_empty());
}
#[test]
fn test_streaming_output_into_entries() {
let mut collector = StreamingOutput::new();
let entry = EntryInfo {
path: PathBuf::from("/test/file"),
display_name: OsString::from("file"),
file_type: None,
metadata: None,
security_context: String::new(),
command_line: false,
must_dereference: false,
};
collector.write_entry(&entry).unwrap();
let entries = collector.into_entries();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].display_name, OsString::from("file"));
}
#[test]
fn test_streaming_output_flush() {
let mut collector = StreamingOutput::new();
assert!(collector.flush().is_ok());
}
}