···11# Changelog
2233+## Next
44+- Add `Target` struct helper for creating targets, and hide the targets in the `target` module.
55+36## 0.1.0 - 2023 Jan 11
47- Initial release
+29
README.md
···55[](https://crates.io/crates/csv-pipeline)
66[](https://docs.rs/csv-pipeline)
7788+## Basic Example
99+```rs
1010+use csv_pipeline::{Pipeline, Transformer};
1111+1212+// First create a pipeline from a CSV file path
1313+let csv = Pipeline::from_path("test/Countries.csv")
1414+ .unwrap()
1515+ // Add a column with values computed from a closure
1616+ .add_col("Language", |headers, row| {
1717+ match headers.get_field(row, "Country") {
1818+ Some("Norway") => Ok("Norwegian".into()),
1919+ _ => Ok("Unknown".into()),
2020+ }
2121+ })
2222+ // Make the "Country" column uppercase
2323+ .rename_col("Country", "COUNTRY")
2424+ .map_col("COUNTRY", |id_str| Ok(id_str.to_uppercase()))
2525+ // Collect the csv into a string
2626+ .collect_into_string()
2727+ .unwrap();
2828+2929+assert_eq!(
3030+ csv,
3131+ "ID,COUNTRY,Language\n\
3232+ 1,NORWAY,Norwegian\n\
3333+ 2,TUVALU,Unknown\n"
3434+);
3535+```
3636+837## Dev Instructions
9381039### Get started
+1
src/headers.rs
···22use csv::StringRecordIter;
33use std::collections::BTreeMap;
4455+/// The headers of a CSV file
56#[derive(Debug, Clone, PartialEq)]
67pub struct Headers {
78 indexes: BTreeMap<String, usize>,
+24-4
src/lib.rs
···11//! CSV processing library inspired by [csvsc](https://crates.io/crates/csvsc)
22//!
33-//! ## Quickstart
33+//! ## Get started
44//!
55//! The first thing you need is to create a [`Pipeline`]. This can be done by calling [`Pipeline::from_reader`] with a [`csv::Reader`], or [`Pipeline::from_path`] with a path.
66//!
···1111//! Finally, you probably want to run the pipeline. There are a few options:
1212//! - [`Pipeline::build`] gives you a [`PipelineIter`] which you can iterate through
1313//! - [`Pipeline::run`] runs through the pipeline until it finds an error, or the end
1414-//! - [`Pipeline::collect_into_string`] runs the pipeline and returns the csv as a `Result<String, Error>`. Can be a convenient alternative to flushing to a [`StringTarget`].
1414+//! - [`Pipeline::collect_into_string`] runs the pipeline and returns the csv as a `Result<String, Error>`. Can be a convenient alternative to flushing to a [`StringTarget`](target::StringTarget).
1515//!
1616//! ## Basic Example
1717//!
···8484//! ```
8585//!
86868787+use std::path::PathBuf;
8888+8789mod headers;
8890mod pipeline;
8991mod pipeline_iterators;
9090-mod target;
9192mod transform;
92939394pub use headers::Headers;
9495pub use pipeline::{Pipeline, PipelineIter};
9595-pub use target::{PathTarget, StderrTarget, StdoutTarget, StringTarget, Target};
9696pub use transform::Transformer;
97979898+pub mod target;
9999+/// Helper for building a target to flush data into
100100+pub struct Target {}
101101+impl Target {
102102+ pub fn path<P: Into<PathBuf>>(path: P) -> target::PathTarget {
103103+ target::PathTarget::new(path)
104104+ }
105105+ pub fn stdout() -> target::StdoutTarget {
106106+ target::StdoutTarget::new()
107107+ }
108108+ pub fn stderr() -> target::StderrTarget {
109109+ target::StderrTarget::new()
110110+ }
111111+ pub fn string<'a>(s: &'a mut String) -> target::StringTarget {
112112+ target::StringTarget::new(s)
113113+ }
114114+}
115115+116116+/// Alias of [`csv::StringRecord`]
98117pub type Row = csv::StringRecord;
118118+/// Alias of `Result<Row, Error>`
99119pub type RowResult = Result<Row, Error>;
100120101121#[derive(Debug)]
+4-2
src/pipeline.rs
···22use crate::pipeline_iterators::{
33 AddCol, Flush, MapCol, MapRow, TransformInto, Validate, ValidateCol,
44};
55-use crate::target::Target;
55+use crate::target::{StringTarget, Target};
66use crate::transform::Transform;
77-use crate::{Error, Row, RowResult, StringTarget};
77+use crate::{Error, Row, RowResult};
88use csv::{Reader, ReaderBuilder, StringRecordsIntoIter};
99use std::borrow::BorrowMut;
1010use std::collections::BTreeMap;
1111use std::io;
1212use std::path::Path;
13131414+/// The main thing
1415pub struct Pipeline<'a> {
1516 pub headers: Headers,
1617 iterator: Box<dyn Iterator<Item = RowResult> + 'a>,
···303304 }
304305}
305306307307+/// A pipeline you can iterate through. You can get one using [`Pipeline::build`].
306308pub struct PipelineIter<'a> {
307309 pub headers: Headers,
308310 pub iterator: Box<dyn Iterator<Item = RowResult> + 'a>,
+6-1
src/transform.rs
···33use std::collections::hash_map::DefaultHasher;
44use std::hash::{Hash, Hasher};
5566+/// For grouping and reducing rows.
67pub trait Transform {
78 /// Add the row to the hasher to group this row separately from others
89 fn hash(
···2425 fn value(&self) -> String;
2526}
26272828+/// A struct for building a [`Transform`], which you can use with [`Pipeline::transform_into`](crate::Pipeline::transform_into).
2729pub struct Transformer {
2830 name: String,
2931 from_col: String,
···3537 from_col: col_name.to_string(),
3638 }
3739 }
4040+ /// Specify which column the transform should be based on
3841 pub fn from_col(mut self, col_name: &str) -> Self {
3942 self.from_col = col_name.to_string();
4043 self
4144 }
4545+ /// Keep the unique values from this column
4246 pub fn keep_unique(self) -> Box<dyn Transform> {
4347 Box::new(KeepUnique {
4448 name: self.name,
···4650 value: "".to_string(),
4751 })
4852 }
5353+ /// Reduce the values from this column into a single value using a closure
4954 pub fn reduce<'a, R, V>(self, reduce: R, init: V) -> Box<dyn Transform + 'a>
5055 where
5156 R: FnMut(V, &str) -> Result<V, Error> + 'a,
···120125 }
121126}
122127123123-pub fn compute_hash<'a>(
128128+pub(crate) fn compute_hash<'a>(
124129 transformers: &Vec<Box<dyn Transform + 'a>>,
125130 headers: &Headers,
126131 row: &Row,