[READ-ONLY] Mirror of https://github.com/probablykasper/csv-pipeline.
0

Configure Feed

Select the types of activity you want to include in your feed.

Add rename_cols method

Kasper (Jan 11, 2023, 12:10 AM +0100) 421046d3 e51bf766

+129 -44
+19 -11
src/headers.rs
··· 1 - use crate::Row; 1 + use crate::{Error, Row}; 2 2 use csv::StringRecordIter; 3 3 use std::collections::HashMap; 4 4 ··· 8 8 row: Row, 9 9 } 10 10 impl Headers { 11 + pub fn new() -> Self { 12 + Headers { 13 + indexes: HashMap::new(), 14 + row: Row::new(), 15 + } 16 + } 17 + 18 + /// Returns false if the field already exists 11 19 pub fn push_field(&mut self, name: &str) -> bool { 12 20 if self.indexes.contains_key(name) { 13 21 return false; ··· 34 42 pub fn get_row(&self) -> &Row { 35 43 &self.row 36 44 } 37 - } 38 - impl From<Row> for Headers { 39 - fn from(row: Row) -> Headers { 40 - Headers { 41 - indexes: row 42 - .iter() 43 - .enumerate() 44 - .map(|(index, entry)| (entry.to_string(), index)) 45 - .collect(), 46 - row, 45 + 46 + pub fn from_row(row: Row) -> Result<Self, Error> { 47 + let mut header = Headers::new(); 48 + for field in &row { 49 + let added = header.push_field(field); 50 + if !added { 51 + return Err(Error::DuplicateColumn(field.to_string())); 52 + } 47 53 } 54 + Ok(header) 48 55 } 49 56 } 57 + 50 58 impl<'a> IntoIterator for &'a Headers { 51 59 type Item = &'a str; 52 60 type IntoIter = StringRecordIter<'a>;
+9 -13
src/lib.rs
··· 1 1 mod headers; 2 2 mod pipeline; 3 3 mod pipeline_iterators; 4 - pub mod target; 4 + mod target; 5 5 6 6 pub use headers::Headers; 7 7 pub use pipeline::{Pipeline, PipelineIter}; 8 + pub use target::{PathTarget, StderrTarget, StdoutTarget, StringTarget, Target}; 8 9 pub type Row = csv::StringRecord; 9 10 pub type RowResult = Result<Row, Error>; 10 11 11 12 #[derive(Debug)] 12 13 pub enum Error { 13 - Example, 14 14 Csv(csv::Error), 15 15 Io(std::io::Error), 16 16 MissingColumn(String), 17 + DuplicateColumn(String), 17 18 } 18 19 impl From<csv::Error> for Error { 19 20 fn from(error: csv::Error) -> Error { ··· 29 30 30 31 #[test] 31 32 fn test_pipeline() { 32 - let mut csv_str = String::new(); 33 - let mut pipeline = Pipeline::from_path("test/Countries.csv") 33 + let csv_str = Pipeline::from_path("test/Countries.csv") 34 + .unwrap() 34 35 .add_col("Language", |headers, row| { 35 36 match headers.get_field(row, "Country") { 36 - Some("Norway") => Ok("Norwegian".to_string()), 37 - _ => Ok("Unknown".to_string()), 37 + Some("Norway") => Ok("Norwegian"), 38 + _ => Ok("Unknown"), 38 39 } 39 40 }) 40 41 .map_col("Country", |id_str| Ok(id_str.to_uppercase())) 41 - .flush(target::StringTarget::new(&mut csv_str)) 42 - .build(); 43 - 44 - while let Some(err) = pipeline.next_error() { 45 - eprintln!("Error: {err:?}"); 46 - } 47 - drop(pipeline); 42 + .collect_into_string() 43 + .unwrap(); 48 44 49 45 assert_eq!( 50 46 csv_str,
+96 -17
src/pipeline.rs
··· 1 1 use super::headers::Headers; 2 2 use crate::pipeline_iterators::{AddCol, Flush, MapCol, MapRow}; 3 3 use crate::target::Target; 4 - use crate::{Error, Row, RowResult}; 4 + use crate::{Error, Row, RowResult, StringTarget}; 5 5 use csv::{Reader, ReaderBuilder, StringRecordsIntoIter}; 6 + use std::borrow::BorrowMut; 6 7 use std::fs::File; 7 8 use std::io; 8 9 use std::path::Path; ··· 13 14 } 14 15 15 16 impl<'a> Pipeline<'a> { 16 - pub fn from_reader(mut reader: Reader<File>) -> Self { 17 + pub fn from_reader(mut reader: Reader<File>) -> Result<Self, Error> { 17 18 let headers_row = reader.headers().unwrap().clone(); 18 19 let row_iterator = RowIter::from_records(reader.into_records()); 19 - Pipeline { 20 - headers: Headers::from(headers_row), 20 + Ok(Pipeline { 21 + headers: Headers::from_row(headers_row)?, 21 22 iterator: Box::new(row_iterator), 22 - } 23 + }) 23 24 } 24 25 25 26 /// Create a pipeline from a CSV or TSV file. 26 - pub fn from_path<P: AsRef<Path>>(file_path: P) -> Self { 27 + pub fn from_path<P: AsRef<Path>>(file_path: P) -> Result<Self, Error> { 27 28 let ext = file_path.as_ref().extension().unwrap_or_default(); 28 29 let delimiter = match ext.to_string_lossy().as_ref() { 29 30 "tsv" => b'\t', ··· 44 45 /// ``` 45 46 /// use csv_pipeline::Pipeline; 46 47 /// 47 - /// Pipeline::from_path("test/Countries.csv") 48 - /// .add_col("Language", |headers, row| { 49 - /// Ok("".to_string()) 48 + /// Pipeline::from_path("test/AB.csv") 49 + /// .unwrap() 50 + /// .add_col("C", |headers, row| { 51 + /// Ok("1") 50 52 /// }); 51 53 /// ``` 52 54 pub fn add_col<F>(mut self, name: &str, get_value: F) -> Self 53 55 where 54 - F: FnMut(&Headers, &Row) -> Result<String, Error> + 'a, 56 + F: FnMut(&Headers, &Row) -> Result<&'a str, Error> + 'a, 55 57 { 56 58 self.headers.push_field(name); 57 59 self.iterator = Box::new(AddCol { ··· 69 71 /// ``` 70 72 /// use csv_pipeline::Pipeline; 71 73 /// 72 - /// Pipeline::from_path("test/Countries.csv") 74 + /// let csv = Pipeline::from_path("test/AB.csv") 75 + /// .unwrap() 73 76 /// .map(|headers, row| { 74 - /// Ok(row.into_iter().map(|field| field.to_uppercase()).collect()) 75 - /// }); 77 + /// Ok(row.into_iter().map(|field| field.to_string() + "0").collect()) 78 + /// }) 79 + /// .collect_into_string() 80 + /// .unwrap(); 81 + /// 82 + /// assert_eq!(csv, "A,B\n10,20\n" 83 + /// ); 76 84 /// ``` 77 85 pub fn map<F>(mut self, get_row: F) -> Self 78 86 where ··· 93 101 /// ``` 94 102 /// use csv_pipeline::Pipeline; 95 103 /// 96 - /// Pipeline::from_path("test/Countries.csv") 97 - /// .map_col("Country", |field| { 98 - /// Ok(field.to_uppercase()) 99 - /// }); 104 + /// let csv = Pipeline::from_path("test/Countries.csv") 105 + /// .unwrap() 106 + /// .map_col("Country", |field| Ok(field.to_uppercase())) 107 + /// .collect_into_string() 108 + /// .unwrap(); 109 + /// 110 + /// assert_eq!( 111 + /// csv, 112 + /// "ID,Country\n\ 113 + /// 1,NORWAY\n\ 114 + /// 2,TUVALU\n" 115 + /// ); 100 116 /// ``` 101 117 pub fn map_col<F>(mut self, col: &str, get_value: F) -> Self 102 118 where ··· 111 127 self 112 128 } 113 129 130 + /// Write to the specified [`Target`]. 131 + /// 132 + /// ## Example 133 + /// 134 + /// ``` 135 + /// use csv_pipeline::{Pipeline, StringTarget}; 136 + /// 137 + /// let mut csv = String::new(); 138 + /// Pipeline::from_path("test/AB.csv") 139 + /// .unwrap() 140 + /// .flush(StringTarget::new(&mut csv)) 141 + /// .run() 142 + /// .unwrap(); 143 + /// 144 + /// assert_eq!(csv, "A,B\n1,2\n"); 145 + /// ``` 114 146 pub fn flush(mut self, target: impl Target + 'a) -> Self { 115 147 let flush = Flush::new(self.iterator, target, self.headers.clone()); 116 148 self.iterator = Box::new(flush); 117 149 self 118 150 } 119 151 152 + /// Panics if a new name already exists 153 + /// 154 + /// ## Example 155 + /// 156 + /// ``` 157 + /// use csv_pipeline::{Pipeline, StringTarget}; 158 + /// 159 + /// let csv = Pipeline::from_path("test/AB.csv") 160 + /// .unwrap() 161 + /// .rename_cols(|i, name| { 162 + /// match name { 163 + /// "A" => "X", 164 + /// name => name, 165 + /// } 166 + /// }) 167 + /// .collect_into_string() 168 + /// .unwrap(); 169 + /// 170 + /// assert_eq!(csv, "X,B\n1,2\n"); 171 + /// ``` 172 + pub fn rename_cols<R>(mut self, mut get_name: R) -> Self 173 + where 174 + R: FnMut(usize, &str) -> &str, 175 + { 176 + let mut new_headers = Headers::new(); 177 + for (i, name) in self.headers.into_iter().enumerate().borrow_mut() { 178 + let new_name = get_name(i, name); 179 + match new_headers.push_field(new_name) { 180 + true => (), 181 + false => panic!("New column name already exists"), 182 + } 183 + } 184 + self.headers = new_headers; 185 + self 186 + } 187 + 120 188 /// Turn the pipeline into an iterator. 121 189 /// You can also do this using `pipeline.into_iter()`. 122 190 pub fn build(self) -> PipelineIter<'a> { ··· 124 192 headers: self.headers, 125 193 iterator: Box::new(self.iterator), 126 194 } 195 + } 196 + 197 + /// Shorthand for `.build().run()`. 198 + pub fn run(self) -> Result<(), Error> { 199 + self.build().run() 200 + } 201 + 202 + pub fn collect_into_string(self) -> Result<String, Error> { 203 + let mut csv = String::new(); 204 + self.flush(StringTarget::new(&mut csv)).run()?; 205 + Ok(csv) 127 206 } 128 207 } 129 208 impl<'a> IntoIterator for Pipeline<'a> {
+3 -3
src/pipeline_iterators.rs
··· 2 2 use crate::target::Target; 3 3 use crate::{Error, Row, RowResult}; 4 4 5 - pub struct AddCol<I, F: FnMut(&Headers, &Row) -> Result<String, Error>> { 5 + pub struct AddCol<'a, I, F: FnMut(&Headers, &Row) -> Result<&'a str, Error>> { 6 6 pub iterator: I, 7 7 pub f: F, 8 8 pub headers: Headers, 9 9 } 10 - impl<I, F> Iterator for AddCol<I, F> 10 + impl<'a, I, F> Iterator for AddCol<'a, I, F> 11 11 where 12 12 I: Iterator<Item = RowResult>, 13 - F: FnMut(&Headers, &Row) -> Result<String, Error>, 13 + F: FnMut(&Headers, &Row) -> Result<&'a str, Error>, 14 14 { 15 15 type Item = RowResult; 16 16
+2
test/AB.csv
··· 1 + A,B 2 + 1,2