ghee 0.6.1

That thin layer of data change management over the filesystem
Documentation
use anyhow::{Context, Result};
use ghee_lang::{Assignment, Xattr};

use std::{fmt::Formatter, path::PathBuf};

use crate::{list_xattrs, xattr_value};

use super::{rm, set};

#[derive(Copy, Clone, Debug, PartialEq)]
pub enum CopyOrMove {
    Copy,
    Move,
}
impl CopyOrMove {
    pub fn name(&self) -> &str {
        match self {
            Self::Copy => "copy",
            Self::Move => "move",
        }
    }
}

impl std::fmt::Display for CopyOrMove {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
        write!(f, "{}", self.name())
    }
}

pub(crate) fn cp_or_mv(
    src: &PathBuf,
    dest: &PathBuf,
    fields: &Vec<Xattr>,
    verbose: bool,
    kind: CopyOrMove,
) -> Result<()> {
    let fields: Vec<Xattr> = if fields.is_empty() {
        list_xattrs(src)
    } else {
        fields.clone()
    };

    if verbose {
        let attrs: Vec<String> = fields.iter().map(|f| f.to_string()).collect();
        println!(
            "{} {} from {} to {}",
            kind,
            attrs.join(", "),
            src.display(),
            dest.display()
        );
    }

    let assignments = {
        let mut assignments: Vec<Assignment> = Vec::with_capacity(fields.len());

        for field in &fields {
            let value = xattr_value(src, field)
                .with_context(|| format!("Could not retrieve xattr {} value", field))?;
            assignments.push(Assignment::new(field.clone(), value));
        }
        assignments
    };

    set(&vec![dest.clone()], &assignments, false, verbose)
        .context("Could not set the field values on the destination")?;

    if kind == CopyOrMove::Move {
        rm(&vec![src.clone()], &fields, false, false, verbose)
            .context("Could not remove the field values from the source")?;
    }

    Ok(())
}

#[cfg(test)]
mod test {
    use ghee_lang::Value;

    use crate::{test_support::Scenario, xattr_values};

    use super::{cp_or_mv, CopyOrMove};

    #[test]
    fn test_cp() {
        test_cp_or_mv(CopyOrMove::Copy);
    }

    #[test]
    fn test_mv() {
        test_cp_or_mv(CopyOrMove::Move);
    }

    fn test_cp_or_mv(kind: CopyOrMove) {
        let s = Scenario::new(kind.name());

        {
            let values = xattr_values(&s.dir1path1).unwrap();

            assert_eq!(values.len(), 4);
            assert_eq!(values[&s.xattr1], Value::Number(0f64));
            assert_eq!(values[&s.xattr2], Value::Number(1f64));
            assert_eq!(values[&s.xattr3], Value::Number(2f64));
            assert_eq!(values[&s.xattr4], Value::Number(3f64));
        }

        {
            let values = xattr_values(&s.dir1path2).unwrap();

            assert_eq!(values.len(), 4);
            assert_eq!(values[&s.xattr1], Value::Number(10f64));
            assert_eq!(values[&s.xattr2], Value::Number(11f64));
            assert_eq!(values[&s.xattr3], Value::Number(12f64));
            assert_eq!(values[&s.xattr4], Value::Number(13f64));
        }

        // Use the nonindexed xattr4 so we don't create collisions on copy
        cp_or_mv(
            &s.dir1path1,
            &s.dir1path2,
            &vec![s.xattr4.clone()],
            false,
            kind,
        )
        .unwrap();

        {
            let values = xattr_values(&s.dir1path1).unwrap();

            assert_eq!(
                values.len(),
                match kind {
                    CopyOrMove::Copy => 4,
                    CopyOrMove::Move => 3,
                }
            );
            assert_eq!(values[&s.xattr1], Value::Number(0f64));
            assert_eq!(values[&s.xattr2], Value::Number(1f64));
            assert_eq!(values[&s.xattr3], Value::Number(2f64));
            if kind == CopyOrMove::Copy {
                assert_eq!(values[&s.xattr4], Value::Number(3f64));
            }
        }

        {
            let values = xattr_values(&s.dir1path2).unwrap();

            assert_eq!(values.len(), 4);
            assert_eq!(values[&s.xattr1], Value::Number(10f64));
            assert_eq!(values[&s.xattr2], Value::Number(11f64));
            assert_eq!(values[&s.xattr3], Value::Number(12f64));
            assert_eq!(values[&s.xattr4], Value::Number(3f64));
        }
    }
}