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
140
141
142
use std::io;
use std::path::Path;

use crate::chopper::chopper::Source;
use crate::error::{CliResult, Error};
use crate::source::{csv_factory::CSVFactory, dc_factory::DCFactory, source_factory::SourceFactory};
use crate::source::csv_configs::CSVInputConfig;
use crate::source::decompress;
use crate::transport::{file::FileInput, http::Http, transport_factory::TransportFactory};

pub struct InputFactory {
    transport_factories: Vec<Box<dyn TransportFactory>>,
    source_factories: Vec<Box<dyn SourceFactory>>
}

impl InputFactory {
    pub fn new(csv_input_config: Option<CSVInputConfig>,
               user_source_factories: Option<Vec<Box<dyn SourceFactory>>>,
               user_transport_factories: Option<Vec<Box<dyn TransportFactory>>>) -> CliResult<Self>
    {
        // transport factories
        let mut default_transport_factories = create_default_transport_factories();
        let transport_factories: Vec<Box<dyn TransportFactory>> = match user_transport_factories {
            Some(mut t) => {
                t.append(&mut default_transport_factories);
                t
            },
            None => default_transport_factories
        };

        // source factories
        let csv_input_config = match csv_input_config {
            Some(c) => c,
            None => CSVInputConfig::new_default()?
        };
        let mut default_source_factories = create_default_source_factories(csv_input_config);
        let source_factories = match user_source_factories {
            Some(mut s) => {
                s.append(&mut default_source_factories);
                s
            },
            None => default_source_factories
        };

        Ok(InputFactory { transport_factories, source_factories })
    }
}

impl InputFactory {
    pub fn create_source_from_path(&mut self, path: &str) -> CliResult<Box<dyn Source>> {
        self.create_source(path, None)
    }

    pub fn create_source_from_path_with_file_type(
        &mut self, path: &str, file_type_override: &str) -> CliResult<Box<dyn Source>>
    {
        self.create_source(path, Some(file_type_override))
    }

    pub fn create_source_from_stdin(&mut self, file_type: &str) -> CliResult<Box<dyn Source>> {
        self.create_source_from_reader(Box::new(io::stdin()), file_type)
    }

    fn create_source(&mut self, path: &str, file_extension_override: Option<&str>) -> CliResult<Box<dyn Source>> {
        let mut path = Path::new(path);
        let reader = self.create_io_reader(path)?;
        let file_extension = match file_extension_override {
            Some(x) => x.to_string(),
            None => {
                if path.extension().is_none() {
                    return Err(Error::from(format!("Cannot find file type for [{:?}]. \
                    Please specify file type as file extension or use parameter file_type.", path)))
                };

                let mut extension: String = "".to_string();
                while path.extension().is_some() {
                    let string = path.extension().unwrap().to_str().unwrap();
                    extension = format!(".{}{}", string, extension);
                    path = Path::new(path.file_stem().unwrap());
                }
                extension
            }
        };
        self.create_source_from_reader(Box::new(reader), &file_extension)
    }

    fn create_source_from_reader(&mut self,
                                 mut reader: Box<dyn io::Read>,
                                 file_extension: &str) -> CliResult<Box<dyn Source>>
    {
        // create a dummy path with given file extension
        let mut path = Path::new("dummy");
        let buf = path.with_extension(file_extension);
        path = buf.as_path();

        // check if the file is compressed
        if decompress::is_compressed(&path) {
            reader = decompress::decompress(&path, reader)?;
            path = Path::new(path.file_stem().unwrap());
        }
        // get source from matching source factory
        for sf in &mut self.source_factories {
            if sf.can_create_from(&path) {
                return sf.create_source(reader)
            }
        }
        Err(Error::from(format!("Cannot find source factory for file - {:?}", path)))
    }

    fn create_io_reader(&mut self, path: &Path) -> CliResult<Box<dyn io::Read>> {
        let mut io_reader: Option<Box<dyn io::Read>> = None;
        for _factory in &mut self.transport_factories.iter() {
            match _factory.can_open(path) {
                false => continue,
                true => {
                    io_reader = Some(_factory.open(path)?)
                }
            }
        }
        match io_reader {
            None => {
                let msg = format!("Cannot open file {:?}. \
                        Check if the path is valid and/or if a right factory is provided.", &path);
                let err = io::Error::new(io::ErrorKind::Other, msg);
                Err(Error::Io(err))
            }
            Some(r) => Ok(Box::new(r))
        }
    }
}

pub fn create_default_source_factories(csv_input_config: CSVInputConfig) -> Vec<Box<dyn SourceFactory>> {
    let source_factories: Vec<Box<dyn SourceFactory>>
        = vec![Box::new(CSVFactory::new(csv_input_config)), Box::new(DCFactory)];
    source_factories
}

pub fn create_default_transport_factories() -> Vec<Box<dyn TransportFactory>> {
    let transport_factories: Vec<Box<dyn TransportFactory>>
        = vec![Box::new(FileInput), Box::new(Http)];
    transport_factories
}