Skip to content

Commit 12183aa

Browse files
Split arguments from --doctest-compilation-args like a shell would
1 parent 0ad2fe8 commit 12183aa

File tree

1 file changed

+64
-8
lines changed

1 file changed

+64
-8
lines changed

src/librustdoc/doctest.rs

+64-8
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,46 @@ pub(crate) struct GlobalTestOptions {
5050
pub(crate) args_file: PathBuf,
5151
}
5252

53+
/// Function used to split command line arguments just like a shell would.
54+
fn split_args(args: &str) -> Vec<String> {
55+
let mut out = Vec::new();
56+
let mut iter = args.chars();
57+
let mut current = String::new();
58+
59+
while let Some(c) = iter.next() {
60+
if c == '\\' {
61+
if let Some(c) = iter.next() {
62+
// If it's escaped, even a quote or a whitespace will be ignored.
63+
current.push(c);
64+
}
65+
} else if c == '"' || c == '\'' {
66+
while let Some(new_c) = iter.next() {
67+
if new_c == c {
68+
break;
69+
} else if new_c == '\\' {
70+
if let Some(c) = iter.next() {
71+
// If it's escaped, even a quote will be ignored.
72+
current.push(c);
73+
}
74+
} else {
75+
current.push(new_c);
76+
}
77+
}
78+
} else if " \n\t\r".contains(c) {
79+
if !current.is_empty() {
80+
out.push(current.clone());
81+
current.clear();
82+
}
83+
} else {
84+
current.push(c);
85+
}
86+
}
87+
if !current.is_empty() {
88+
out.push(current);
89+
}
90+
out
91+
}
92+
5393
pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) -> Result<(), String> {
5494
let mut file = File::create(file_path)
5595
.map_err(|error| format!("failed to create args file: {error:?}"))?;
@@ -79,14 +119,7 @@ pub(crate) fn generate_args_file(file_path: &Path, options: &RustdocOptions) ->
79119
}
80120

81121
for compilation_args in &options.doctest_compilation_args {
82-
for flag in compilation_args
83-
.split_whitespace()
84-
.map(|flag| flag.trim())
85-
.filter(|flag| !flag.is_empty())
86-
{
87-
// Very simple parsing implementation. Might be a good idea to correctly handle strings.
88-
content.push(flag.to_string());
89-
}
122+
content.extend(split_args(compilation_args));
90123
}
91124

92125
let content = content.join("\n");
@@ -1003,6 +1036,29 @@ fn doctest_run_fn(
10031036
Ok(())
10041037
}
10051038

1039+
#[cfg(test)]
1040+
#[test]
1041+
fn check_split_args() {
1042+
fn compare(input: &str, expected: &[&str]) {
1043+
let output = split_args(input);
1044+
let expected = expected.iter().map(|s| s.to_string()).collect::<Vec<_>>();
1045+
assert_eq!(expected, output, "test failed for {input:?}");
1046+
}
1047+
1048+
compare("'a' \"b\"c", &["a", "bc"]);
1049+
compare("'a' \"b \"c d", &["a", "b c", "d"]);
1050+
compare("'a' \"b\\\"c\"", &["a", "b\"c"]);
1051+
compare("'a\"'", &["a\""]);
1052+
compare("\"a'\"", &["a'"]);
1053+
compare("\\ a", &[" a"]);
1054+
compare("\\\\", &["\\"]);
1055+
compare("a'", &["a"]);
1056+
compare("a ", &["a"]);
1057+
compare("a b", &["a", "b"]);
1058+
compare("a\n\t \rb", &["a", "b"]);
1059+
compare("a\n\t1 \rb", &["a", "1", "b"]);
1060+
}
1061+
10061062
#[cfg(test)] // used in tests
10071063
impl DocTestVisitor for Vec<usize> {
10081064
fn visit_test(&mut self, _test: String, _config: LangString, rel_line: MdRelLine) {

0 commit comments

Comments
 (0)