1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use rettle::{
    Argument, 
    Pour,
    Tea,
};

use std::any::Any;
use serde::Serialize;
use std::fs::OpenOptions;
use std::path::Path;

///
/// Ingredient params for PourCsTea.
pub struct PourCsvArg {
    /// The filepath to the csv that will be processed.
    filepath: String,
}

impl PourCsvArg {
    ///
    /// Returns a PourCsvArg to be used as params in PourCsTea.
    ///
    /// # Arguments
    ///
    /// * `filepath` - filepath for csv to load.
    pub fn new(filepath: &str) -> PourCsvArg {
        let filepath = String::from(filepath);
        PourCsvArg { filepath }
    }
}

impl Argument for PourCsvArg {
    fn as_any(&self) -> &dyn Any {
        self
    }
}

///
/// Wrapper to simplifiy the creation of the Pour Ingredient to be used in the rettle Pot.
pub struct PourCsTea {}

impl PourCsTea {
    ///
    /// Returns the Pour Ingredient to be added to the `rettle` Pot.
    ///
    /// # Arguments
    ///
    /// * `name` - Ingredient name
    /// * `params` - Params data structure holding the `filepath` for the csv to process
    pub fn new<T: Tea + Send + Sync + Clone + Serialize + 'static>(name: &str, params: PourCsvArg) -> Box<Pour> {
        Box::new(Pour {
            name: String::from(name),
            computation: Box::new(|tea_batch, args| {
                pour_to_csv::<T>(tea_batch, args)
            }),
            params: Some(Box::new(params))
        })
    }
}

///
/// Implements the csv pour, with serialization based on specified data struct.
/// brewery for processing.
///
/// # Arguments
///
/// * `tea_batch` - Vec<Box<dyn Tea + Send>> batch that needs to be output to csv
/// * `args` - Params specifying the filepath of the csv.
fn pour_to_csv<T: Tea + Send + Sync + Clone + Serialize + 'static>(tea_batch: Vec<Box<dyn Tea + Send>>, args: &Option<Box<dyn Argument + Send>>) -> Vec<Box<dyn Tea + Send>> {
    match args {
        None => panic!("Need to pass \"filepath\" params!"),
        Some(box_args) => {
            // Unwrap params.
            let box_args = box_args.as_any().downcast_ref::<PourCsvArg>().unwrap();

            // Open csv file to write data to, panic if fail.
            let headers = !Path::new(&box_args.filepath).exists();
            let file = OpenOptions::new()
                .write(true)
                .append(true)
                .create(true)
                .open(&box_args.filepath)
                .unwrap();

            let mut writer = csv::WriterBuilder::new().has_headers(headers).from_writer(file);

            tea_batch.into_iter()
                .map(|tea| {
                    let tea = tea.as_any().downcast_ref::<T>().unwrap();
                    let same_tea = tea.clone();
                    writer.serialize(&same_tea).unwrap();

                    Box::new(same_tea) as Box<dyn Tea + Send>
                })
                .collect()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{PourCsvArg, PourCsTea};
    use rettle::{
        Tea,
        Pot,
    };
    use serde::Serialize;
    use std::any::Any;

    #[derive(Default, Clone, Debug, Serialize)]
    struct TestCsTea {
        id: i32,
        name: String,
        value: i32
    }

    impl Tea for TestCsTea {
        fn as_any(&self) -> &dyn Any {
            self
        }
    }

    #[test]
    fn create_csv_args() {
        let csv_args = PourCsvArg::new("fixtures/test.csv");
        assert_eq!(csv_args.filepath, "fixtures/test.csv");
    }

    #[test]
    fn create_pour_cstea() {
        let csv_args = PourCsvArg::new("fixtures/test.csv");
        let pour_cstea = PourCsTea::new::<TestCsTea>("test_csv", csv_args);
        let new_pot = Pot::new();
        new_pot.add_ingredient(pour_cstea);
        assert_eq!(new_pot.get_recipe().read().unwrap().len(), 1);
        assert_eq!(new_pot.get_recipe().read().unwrap()[0].get_name(), "test_csv");
    }
}