use crate::GLOBAL_CONFIG;
use crate::config::generate::DedupBy;
use crate::config::generate::{BulkExclusion, Config, FormattedMode, PrintMode, RawMode};
use crate::data::paths::{PHANTOM_DATE, PHANTOM_SIZE, PathData};
use crate::filesystem::mounts::IsFilterDir;
use crate::library::utility::PaintString;
use crate::library::utility::{DateFormat, date_string, display_human_size};
use crate::lookup::versions::ProximateDatasetAndOptAlts;
use nu_ansi_term::AnsiGenericString;
use std::borrow::Cow;
use std::fmt::Debug;
use std::ops::Deref;
use std::sync::LazyLock;
use terminal_size::{Height, Width, terminal_size};
use time::UtcOffset;
pub const PRETTY_FIXED_WIDTH_PADDING: &str = " ";
pub const PRETTY_FIXED_WIDTH_PADDING_LEN_X2: usize = 4;
pub const NOT_SO_PRETTY_FIXED_WIDTH_PADDING: &str = "\t";
pub const QUOTATION_MARKS_LEN: usize = 2;
static PHANTOM_DATE_PAD_STR: LazyLock<String> = LazyLock::new(|| {
format!(
"{:<width$}",
"",
width = date_string(
GLOBAL_CONFIG.requested_utc_offset,
&PHANTOM_DATE,
DateFormat::Display
)
.chars()
.count()
)
});
static PHANTOM_SIZE_PAD_STR: LazyLock<String> = LazyLock::new(|| {
format!(
"{:<width$}",
"",
width = display_human_size(PHANTOM_SIZE).chars().count()
)
});
#[derive(Debug, Clone)]
pub struct DisplaySet<'a> {
inner: [Vec<&'a PathData>; 2],
}
impl<'a> From<(Vec<&'a PathData>, Vec<&'a PathData>)> for DisplaySet<'a> {
#[inline(always)]
fn from((keys, values): (Vec<&'a PathData>, Vec<&'a PathData>)) -> Self {
Self {
inner: [values, keys],
}
}
}
impl<'a> Deref for DisplaySet<'a> {
type Target = [Vec<&'a PathData>; 2];
fn deref(&self) -> &Self::Target {
&self.inner
}
}
#[derive(Debug, Clone)]
pub enum DisplaySetType {
IsLive,
IsSnap,
}
impl From<usize> for DisplaySetType {
#[inline]
fn from(value: usize) -> Self {
match value {
0usize => DisplaySetType::IsSnap,
1usize => DisplaySetType::IsLive,
_ => unreachable!(),
}
}
}
impl DisplaySetType {
#[inline(always)]
pub fn filter_bulk_exclusions(&self, bulk_exclusion: &BulkExclusion) -> bool {
match &self {
DisplaySetType::IsLive if matches!(bulk_exclusion, BulkExclusion::NoLive) => false,
DisplaySetType::IsSnap if matches!(bulk_exclusion, BulkExclusion::NoSnap) => false,
_ => true,
}
}
}
impl<'a> DisplaySet<'a> {
#[inline(always)]
pub fn format(&self, config: &Config, padding_collection: &PaddingCollection) -> String {
let mut border: Cow<'_, str> = padding_collection.fancy_border_string().into();
self.iter()
.enumerate()
.map(|(idx, snap_or_live_set)| (DisplaySetType::from(idx), snap_or_live_set))
.filter(|(display_set_type, _snap_or_live_set)| {
if let Some(bulk_exclusion) = &config.opt_bulk_exclusion {
return display_set_type.filter_bulk_exclusions(bulk_exclusion);
}
true
})
.fold(
String::new(),
|mut display_set_buffer, (display_set_type, snap_or_live_set)| {
let mut component_buffer: String = snap_or_live_set
.iter()
.map(|path_data| {
path_data.format(config, &display_set_type, padding_collection)
})
.collect();
if let PrintMode::Formatted(FormattedMode::NotPretty) = config.print_mode {
display_set_buffer += &component_buffer;
return display_set_buffer;
}
match &display_set_type {
DisplaySetType::IsSnap => {
if component_buffer.is_empty() {
let live_path_data = self.inner[1][0];
let warning = live_path_data.warn_on_empty_snaps(config);
let warning_len = warning.chars().count();
let border_len = border.chars().count();
if warning_len > border_len {
let diff = warning_len - border_len;
let mut new_border = border.trim_end().to_string();
new_border += &format!("{:─<diff$}\n", "");
border = new_border.into();
}
component_buffer = warning.to_string();
}
display_set_buffer += &border;
display_set_buffer += &component_buffer;
display_set_buffer += &border;
}
DisplaySetType::IsLive => {
display_set_buffer += &component_buffer;
display_set_buffer += &border;
}
}
display_set_buffer
},
)
}
pub fn into_inner(self) -> [Vec<&'a PathData>; 2] {
self.inner
}
}
impl PathData {
#[inline(always)]
pub fn format(
&self,
config: &Config,
display_set_type: &DisplaySetType,
padding_collection: &PaddingCollection,
) -> String {
let (raw_size, display_date) = match self.opt_path_metadata() {
Some(metadata) => {
let size = Cow::Owned(display_human_size(metadata.size()));
let display_date = Cow::Owned(date_string(
config.requested_utc_offset,
&metadata.mtime(),
DateFormat::Display,
));
(size, display_date)
}
None => (
Cow::Borrowed(&*PHANTOM_SIZE_PAD_STR),
Cow::Borrowed(&*PHANTOM_DATE_PAD_STR),
),
};
let (display_size, display_path, display_padding) = match &config.print_mode {
PrintMode::Formatted(FormattedMode::NotPretty) => {
let path = self.path().to_string_lossy();
let padding = NOT_SO_PRETTY_FIXED_WIDTH_PADDING;
(raw_size, path, padding)
}
PrintMode::Formatted(FormattedMode::Default) => {
let size = Cow::Owned(format!(
"{:>width$}",
raw_size,
width = padding_collection.size_padding_len()
));
let path = {
let painted_path_str = match display_set_type {
DisplaySetType::IsLive => self.paint_string(),
DisplaySetType::IsSnap => {
let path_buf = &self.path();
AnsiGenericString::from(path_buf.to_string_lossy())
}
};
Cow::Owned(format!(
"\"{:<width$}\"",
painted_path_str,
width = padding_collection.size_padding_len()
))
};
let padding = PRETTY_FIXED_WIDTH_PADDING;
(size, path, padding)
}
_ => unreachable!(),
};
format!(
"{}{}{}{}{}\n",
display_date, display_padding, display_size, display_padding, display_path
)
}
fn warn_on_empty_snaps(&self, config: &Config) -> &str {
match ProximateDatasetAndOptAlts::new(config, self).ok() {
None => "WARN: Could not determine live path's most proximate dataset.\n",
_ if self.path().ancestors().any(|mount| mount.is_filter_dir()) => {
"WARN: Most proximate dataset for path is an unsupported filesystem.\n"
}
_ if self.opt_path_metadata().is_none() => "WARN: Input file may have never existed.\n",
Some(prox_opt_alts)
if config.opt_omit_ditto
&& prox_opt_alts
.into_search_bundles()
.flat_map(|relative_path_and_snap_mounts| {
relative_path_and_snap_mounts.version_search(&DedupBy::Disable)
})
.count()
!= 0 =>
{
"WARN: Omit ditto enabled. Omitting the only snapshot version available.\n"
}
_ => "WARN: No snapshot version exists for the specified file.\n",
}
}
#[inline(always)]
pub fn raw_format(
&self,
raw_mode: &RawMode,
delimiter: char,
requested_utc_offset: UtcOffset,
) -> String {
match raw_mode {
RawMode::Csv => match self.opt_path_metadata() {
Some(md) => {
let date =
date_string(requested_utc_offset, &md.mtime(), DateFormat::Timestamp);
let size = md.size();
format!(
"{},{},\"{}\"{}",
date,
size,
self.path().to_string_lossy(),
delimiter
)
}
None => {
format!(",,\"{}\"{}", self.path().to_string_lossy(), delimiter)
}
},
RawMode::Newline | RawMode::Zero => {
format!("{}{}", self.path().to_string_lossy(), delimiter)
}
}
}
}
pub struct PaddingCollection {
size_padding_len: usize,
fancy_border_string: String,
}
impl PaddingCollection {
#[inline(always)]
pub fn new(config: &Config, display_set: &DisplaySet) -> PaddingCollection {
let (size_padding_len, fancy_border_len) = display_set.iter().flatten().fold(
(0usize, 0usize),
|(mut size_padding_len, mut fancy_border_len), path_data| {
let metadata = path_data.metadata_infallible();
let (display_date, display_size, display_path) = {
let date = date_string(
config.requested_utc_offset,
&metadata.mtime(),
DateFormat::Display,
);
let size = format!(
"{:>width$}",
display_human_size(metadata.size()),
width = size_padding_len
);
let path = path_data.path().to_string_lossy();
(date, size, path)
};
let display_size_len = display_human_size(metadata.size()).chars().count();
let formatted_line_len = display_date.chars().count()
+ display_size.chars().count()
+ display_path.chars().count()
+ PRETTY_FIXED_WIDTH_PADDING_LEN_X2
+ QUOTATION_MARKS_LEN;
size_padding_len = display_size_len.max(size_padding_len);
fancy_border_len = formatted_line_len.max(fancy_border_len);
(size_padding_len, fancy_border_len)
},
);
let fancy_border_string: String = Self::get_fancy_border_string(fancy_border_len);
PaddingCollection {
size_padding_len,
fancy_border_string,
}
}
fn size_padding_len(&self) -> usize {
self.size_padding_len
}
fn fancy_border_string(&self) -> &str {
self.fancy_border_string.as_ref()
}
#[inline(always)]
fn get_fancy_border_string(fancy_border_len: usize) -> String {
if let Some((Width(width), Height(_height))) = terminal_size() {
let width_as_usize = width as usize;
if width_as_usize < fancy_border_len {
return format!("{:─<width_as_usize$}\n", "");
}
}
format!("{:─<fancy_border_len$}\n", "")
}
}