Skip to content

Commit df3c8f1

Browse files
committed
input: add --quote-style option
1 parent fd7fb5c commit df3c8f1

2 files changed

Lines changed: 97 additions & 9 deletions

File tree

src/cmd/input.rs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,17 @@ input options:
3333
--quote <arg> The quote character to use. [default: "]
3434
--escape <arg> The escape character to use. When not specified,
3535
quotes are escaped by doubling them.
36-
--no-quoting Disable quoting completely.
37-
Otherwise, input uses csv::QuoteStyle::NonNumeric,
38-
which puts quotes around all fields that are non-numeric.
39-
Namely, when writing a field that doesn't parse as a valid
40-
float or integer, quotes will be used.
41-
This makes CSV files more portable.
36+
--no-quoting Disable quoting completely when reading CSV data.
37+
--quote-style <arg> The quoting style to use when writing CSV data.
38+
Possible values: all, necessary, nonnumeric and never.
39+
All: Quotes all fields.
40+
Necessary: Quotes fields only when necessary - when fields
41+
contain a quote, delimiter or record terminator.
42+
Quotes are also necessary when writing an empty record
43+
(which is indistinguishable from a record with one empty field).
44+
NonNumeric: Quotes all fields that are non-numeric.
45+
Never: Never write quotes. Even if it produces invalid CSV.
46+
[default: necessary]
4247
--skip-lines <arg> The number of preamble lines to skip.
4348
--auto-skip Sniffs a CSV for preamble lines and automatically
4449
skips them. Takes precedence over --skip-lines option.
@@ -90,6 +95,7 @@ struct Args {
9095
flag_quote: Delimiter,
9196
flag_escape: Option<Delimiter>,
9297
flag_no_quoting: bool,
98+
flag_quote_style: String,
9399
flag_skip_lines: Option<u64>,
94100
flag_skip_lastlines: Option<u64>,
95101
flag_auto_skip: bool,
@@ -135,16 +141,28 @@ pub fn run(argv: &[&str]) -> CliResult<()> {
135141
if args.flag_auto_skip {
136142
std::env::remove_var("QSV_SNIFF_PREAMBLE");
137143
}
138-
let wconfig = Config::new(&args.flag_output);
144+
let mut wconfig = Config::new(&args.flag_output);
139145

140146
if let Some(escape) = args.flag_escape {
141147
rconfig = rconfig.escape(Some(escape.as_byte())).double_quote(false);
142148
}
143149
if args.flag_no_quoting {
144150
rconfig = rconfig.quoting(false);
145-
} else {
146-
rconfig = rconfig.quote_style(csv::QuoteStyle::NonNumeric);
147151
}
152+
wconfig = wconfig.quote_style(match args.flag_quote_style.as_str() {
153+
"necessary" => csv::QuoteStyle::Necessary,
154+
"all" => csv::QuoteStyle::Always,
155+
"nonnumeric" => csv::QuoteStyle::NonNumeric,
156+
"never" => csv::QuoteStyle::Never,
157+
_ => {
158+
return fail_incorrectusage_clierror!(
159+
"Invalid --quote-style option: {}. Valid values: all, necessary, nonnumeric, \
160+
never.",
161+
args.flag_quote_style
162+
);
163+
},
164+
});
165+
148166
if args.flag_auto_skip || args.flag_skip_lines.is_some() || args.flag_skip_lastlines.is_some() {
149167
rconfig = rconfig.flexible(true);
150168
}

tests/test_input.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,76 @@ fn test_input_autoskip() {
6969
assert_eq!(got, expected);
7070
}
7171

72+
#[test]
73+
fn test_input_quotestyle_nonnumeric() {
74+
let wrk = Workdir::new("input_quotestyle_nonnumeric");
75+
wrk.create(
76+
"testdata.csv",
77+
vec![
78+
svec!["column1", "float column", "int column", "description"],
79+
svec!["a", "1.0", "1", "this is a string"],
80+
svec!["c", "3.5", "3", "this is another string"],
81+
svec!["e", "3.14", "42", "this is a third string"],
82+
],
83+
);
84+
let mut cmd = wrk.command("input");
85+
cmd.args(["--quote-style", "nonnumeric"])
86+
.arg("testdata.csv");
87+
88+
let got: String = wrk.stdout(&mut cmd);
89+
let expected = r#""column1","float column","int column","description"
90+
"a",1.0,1,"this is a string"
91+
"c",3.5,3,"this is another string"
92+
"e",3.14,42,"this is a third string""#;
93+
assert_eq!(got, expected);
94+
}
95+
96+
#[test]
97+
fn test_input_quotestyle_necessary() {
98+
let wrk = Workdir::new("input_quotestyle_necessary");
99+
wrk.create(
100+
"testdata.csv",
101+
vec![
102+
svec!["column1", "float column", "int column", "description"],
103+
svec!["a", "1.0", "1", "1,234,5678 - number with commas"],
104+
svec!["c", "3.5", "3", "this is another string"],
105+
svec!["e", "3.14", "42", "this is a third string"],
106+
],
107+
);
108+
let mut cmd = wrk.command("input");
109+
cmd.args(["--quote-style", "necessary"]).arg("testdata.csv");
110+
111+
let got: String = wrk.stdout(&mut cmd);
112+
let expected = r#"column1,float column,int column,description
113+
a,1.0,1,"1,234,5678 - number with commas"
114+
c,3.5,3,this is another string
115+
e,3.14,42,this is a third string"#;
116+
assert_eq!(got, expected);
117+
}
118+
119+
#[test]
120+
fn test_input_quotestyle_all() {
121+
let wrk = Workdir::new("input_quotestyle_all");
122+
wrk.create(
123+
"testdata.csv",
124+
vec![
125+
svec!["column1", "float column", "int column", "description"],
126+
svec!["a", "1.0", "1", "1,234,5678 - number with commas"],
127+
svec!["c", "3.5", "3", "this is another string"],
128+
svec!["e", "3.14", "42", "this is a third string"],
129+
],
130+
);
131+
let mut cmd = wrk.command("input");
132+
cmd.args(["--quote-style", "all"]).arg("testdata.csv");
133+
134+
let got: String = wrk.stdout(&mut cmd);
135+
let expected = r#""column1","float column","int column","description"
136+
"a","1.0","1","1,234,5678 - number with commas"
137+
"c","3.5","3","this is another string"
138+
"e","3.14","42","this is a third string""#;
139+
assert_eq!(got, expected);
140+
}
141+
72142
#[test]
73143
fn test_input_skip_one_line() {
74144
let wrk = Workdir::new("input_skip_one_line");

0 commit comments

Comments
 (0)