Skip to content

Commit 92c43db

Browse files
committed
auto merge of #14544 : aturon/rust/issue-14352, r=alexcrichton
Adds a platform-specific function, `split_paths` to the `os` module. This function can be used to parse PATH-like environment variables according to local platform conventions. Closes #14352.
2 parents 60a43f9 + b1fbbf3 commit 92c43db

File tree

1 file changed

+94
-2
lines changed

1 file changed

+94
-2
lines changed

src/libstd/os.rs

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use libc;
3838
use ops::Drop;
3939
use option::{Some, None, Option};
4040
use os;
41-
use path::{Path, GenericPath};
41+
use path::{Path, GenericPath, BytesContainer};
4242
use ptr::RawPtr;
4343
use ptr;
4444
use result::{Err, Ok, Result};
@@ -395,6 +395,63 @@ pub fn unsetenv(n: &str) {
395395
_unsetenv(n);
396396
}
397397

398+
#[cfg(unix)]
399+
/// Parse a string or vector according to the platform's conventions
400+
/// for the `PATH` environment variable. Drops empty paths.
401+
pub fn split_paths<T: BytesContainer>(unparsed: T) -> Vec<Path> {
402+
unparsed.container_as_bytes()
403+
.split(|b| *b == ':' as u8)
404+
.filter(|s| s.len() > 0)
405+
.map(Path::new)
406+
.collect()
407+
}
408+
409+
#[cfg(windows)]
410+
/// Parse a string or vector according to the platform's conventions
411+
/// for the `PATH` environment variable. Drops empty paths.
412+
pub fn split_paths<T: BytesContainer>(unparsed: T) -> Vec<Path> {
413+
// On Windows, the PATH environment variable is semicolon separated. Double
414+
// quotes are used as a way of introducing literal semicolons (since
415+
// c:\some;dir is a valid Windows path). Double quotes are not themselves
416+
// permitted in path names, so there is no way to escape a double quote.
417+
// Quoted regions can appear in arbitrary locations, so
418+
//
419+
// c:\foo;c:\som"e;di"r;c:\bar
420+
//
421+
// Should parse as [c:\foo, c:\some;dir, c:\bar].
422+
//
423+
// (The above is based on testing; there is no clear reference available
424+
// for the grammar.)
425+
426+
let mut parsed = Vec::new();
427+
let mut in_progress = Vec::new();
428+
let mut in_quote = false;
429+
430+
for b in unparsed.container_as_bytes().iter() {
431+
match *b as char {
432+
';' if !in_quote => {
433+
// ignore zero-length path strings
434+
if in_progress.len() > 0 {
435+
parsed.push(Path::new(in_progress.as_slice()));
436+
}
437+
in_progress.truncate(0)
438+
}
439+
'\"' => {
440+
in_quote = !in_quote;
441+
}
442+
_ => {
443+
in_progress.push(*b);
444+
}
445+
}
446+
}
447+
448+
if in_progress.len() > 0 {
449+
parsed.push(Path::new(in_progress));
450+
}
451+
452+
parsed
453+
}
454+
398455
/// A low-level OS in-memory pipe.
399456
pub struct Pipe {
400457
/// A file descriptor representing the reading end of the pipe. Data written
@@ -1502,7 +1559,7 @@ mod tests {
15021559
use c_str::ToCStr;
15031560
use option;
15041561
use os::{env, getcwd, getenv, make_absolute};
1505-
use os::{setenv, unsetenv};
1562+
use os::{split_paths, setenv, unsetenv};
15061563
use os;
15071564
use rand::Rng;
15081565
use rand;
@@ -1754,5 +1811,40 @@ mod tests {
17541811
fs::unlink(&path).unwrap();
17551812
}
17561813

1814+
#[test]
1815+
#[cfg(windows)]
1816+
fn split_paths_windows() {
1817+
fn check_parse(unparsed: &str, parsed: &[&str]) -> bool {
1818+
split_paths(unparsed) ==
1819+
parsed.iter().map(|s| Path::new(*s)).collect()
1820+
}
1821+
1822+
assert!(check_parse("", []));
1823+
assert!(check_parse(r#""""#, []));
1824+
assert!(check_parse(";;", []));
1825+
assert!(check_parse(r"c:\", [r"c:\"]));
1826+
assert!(check_parse(r"c:\;", [r"c:\"]));
1827+
assert!(check_parse(r"c:\;c:\Program Files\",
1828+
[r"c:\", r"c:\Program Files\"]));
1829+
assert!(check_parse(r#"c:\;c:\"foo"\"#, [r"c:\", r"c:\foo\"]));
1830+
assert!(check_parse(r#"c:\;c:\"foo;bar"\;c:\baz"#,
1831+
[r"c:\", r"c:\foo;bar\", r"c:\baz"]));
1832+
}
1833+
1834+
#[test]
1835+
#[cfg(unix)]
1836+
fn split_paths_unix() {
1837+
fn check_parse(unparsed: &str, parsed: &[&str]) -> bool {
1838+
split_paths(unparsed) ==
1839+
parsed.iter().map(|s| Path::new(*s)).collect()
1840+
}
1841+
1842+
assert!(check_parse("", []));
1843+
assert!(check_parse("::", []));
1844+
assert!(check_parse("/", ["/"]));
1845+
assert!(check_parse("/:", ["/"]));
1846+
assert!(check_parse("/:/usr/local", ["/", "/usr/local"]));
1847+
}
1848+
17571849
// More recursive_mkdir tests are in extra::tempfile
17581850
}

0 commit comments

Comments
 (0)