Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| extern crate remove_dir_all; | |
| use std::char::from_u32; | |
| use std::env; | |
| use std::error; | |
| use std::ffi::{OsStr, OsString}; | |
| use std::fmt; | |
| use std::fs; | |
| use std::io::Write; | |
| use std::io; | |
| use std::path::Path; | |
| use std::process::{Command, ExitStatus}; | |
| use std::str; | |
| use rand::random; | |
| pub fn ensure_dir_exists<P: AsRef<Path>, F: FnOnce(&Path)>( | |
| path: P, | |
| callback: F, | |
| ) -> io::Result<bool> { | |
| if !is_directory(path.as_ref()) { | |
| callback(path.as_ref()); | |
| fs::create_dir_all(path.as_ref()).map(|()| true) | |
| } else { | |
| Ok(false) | |
| } | |
| } | |
| pub fn is_directory<P: AsRef<Path>>(path: P) -> bool { | |
| fs::metadata(path).ok().as_ref().map(fs::Metadata::is_dir) == Some(true) | |
| } | |
| pub fn is_file<P: AsRef<Path>>(path: P) -> bool { | |
| fs::metadata(path).ok().as_ref().map(fs::Metadata::is_file) == Some(true) | |
| } | |
| pub fn path_exists<P: AsRef<Path>>(path: P) -> bool { | |
| fs::metadata(path).is_ok() | |
| } | |
| pub fn random_string(length: usize) -> String { | |
| let chars = b"abcdefghijklmnopqrstuvwxyz0123456789_"; | |
| (0..length) | |
| .map(|_| from_u32(chars[random::<usize>() % chars.len()] as u32).unwrap()) | |
| .collect() | |
| } | |
| pub fn if_not_empty<S: PartialEq<str>>(s: S) -> Option<S> { | |
| if s == *"" { | |
| None | |
| } else { | |
| Some(s) | |
| } | |
| } | |
| pub fn write_file(path: &Path, contents: &str) -> io::Result<()> { | |
| let mut file = fs::OpenOptions::new() | |
| .write(true) | |
| .truncate(true) | |
| .create(true) | |
| .open(path)?; | |
| io::Write::write_all(&mut file, contents.as_bytes())?; | |
| file.sync_data()?; | |
| Ok(()) | |
| } | |
| pub fn read_file(path: &Path) -> io::Result<String> { | |
| let mut file = fs::OpenOptions::new().read(true).open(path)?; | |
| let mut contents = String::new(); | |
| io::Read::read_to_string(&mut file, &mut contents)?; | |
| Ok(contents) | |
| } | |
| pub fn filter_file<F: FnMut(&str) -> bool>( | |
| src: &Path, | |
| dest: &Path, | |
| mut filter: F, | |
| ) -> io::Result<usize> { | |
| let src_file = fs::File::open(src)?; | |
| let dest_file = fs::File::create(dest)?; | |
| let mut reader = io::BufReader::new(src_file); | |
| let mut writer = io::BufWriter::new(dest_file); | |
| let mut removed = 0; | |
| for result in io::BufRead::lines(&mut reader) { | |
| let line = result?; | |
| if filter(&line) { | |
| writeln!(&mut writer, "{}", &line)?; | |
| } else { | |
| removed += 1; | |
| } | |
| } | |
| writer.flush()?; | |
| Ok(removed) | |
| } | |
| pub fn match_file<T, F: FnMut(&str) -> Option<T>>(src: &Path, mut f: F) -> io::Result<Option<T>> { | |
| let src_file = fs::File::open(src)?; | |
| let mut reader = io::BufReader::new(src_file); | |
| for result in io::BufRead::lines(&mut reader) { | |
| let line = result?; | |
| if let Some(r) = f(&line) { | |
| return Ok(Some(r)); | |
| } | |
| } | |
| Ok(None) | |
| } | |
| pub fn append_file(dest: &Path, line: &str) -> io::Result<()> { | |
| let mut dest_file = fs::OpenOptions::new() | |
| .write(true) | |
| .append(true) | |
| .create(true) | |
| .open(dest)?; | |
| writeln!(&mut dest_file, "{}", line)?; | |
| dest_file.sync_data()?; | |
| Ok(()) | |
| } | |
| pub fn tee_file<W: io::Write>(path: &Path, w: &mut W) -> io::Result<()> { | |
| let mut file = fs::OpenOptions::new().read(true).open(path)?; | |
| let buffer_size = 0x10000; | |
| let mut buffer = vec![0u8; buffer_size]; | |
| loop { | |
| let bytes_read = io::Read::read(&mut file, &mut buffer)?; | |
| if bytes_read != 0 { | |
| io::Write::write_all(w, &mut buffer[0..bytes_read])?; | |
| } else { | |
| return Ok(()); | |
| } | |
| } | |
| } | |
| pub fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> { | |
| #[cfg(windows)] | |
| fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> { | |
| // std's symlink uses Windows's symlink function, which requires | |
| // admin. We can create directory junctions the hard way without | |
| // though. | |
| symlink_junction_inner(src, dest) | |
| } | |
| #[cfg(not(windows))] | |
| fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> { | |
| ::std::os::unix::fs::symlink(src, dest) | |
| } | |
| let _ = remove_dir(dest); | |
| symlink_dir_inner(src, dest) | |
| } | |
| // Creating a directory junction on windows involves dealing with reparse | |
| // points and the DeviceIoControl function, and this code is a skeleton of | |
| // what can be found here: | |
| // | |
| // http://www.flexhex.com/docs/articles/hard-links.phtml | |
| // | |
| // Copied from std | |
| #[cfg(windows)] | |
| #[allow(non_snake_case)] | |
| fn symlink_junction_inner(target: &Path, junction: &Path) -> io::Result<()> { | |
| use winapi::shared::minwindef::*; | |
| use winapi::um::fileapi::*; | |
| use winapi::um::ioapiset::*; | |
| use winapi::um::winbase::*; | |
| use winapi::um::winioctl::FSCTL_SET_REPARSE_POINT; | |
| use winapi::um::winnt::*; | |
| use std::ptr; | |
| use std::os::windows::ffi::OsStrExt; | |
| const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; | |
| #[repr(C)] | |
| pub struct REPARSE_MOUNTPOINT_DATA_BUFFER { | |
| pub ReparseTag: DWORD, | |
| pub ReparseDataLength: DWORD, | |
| pub Reserved: WORD, | |
| pub ReparseTargetLength: WORD, | |
| pub ReparseTargetMaximumLength: WORD, | |
| pub Reserved1: WORD, | |
| pub ReparseTarget: WCHAR, | |
| } | |
| // We're using low-level APIs to create the junction, and these are more picky about paths. | |
| // For example, forward slashes cannot be used as a path separator, so we should try to | |
| // canonicalize the path first. | |
| let target = fs::canonicalize(target)?; | |
| fs::create_dir(junction)?; | |
| let path = windows::to_u16s(junction)?; | |
| unsafe { | |
| let h = CreateFileW( | |
| path.as_ptr(), | |
| GENERIC_WRITE, | |
| FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, | |
| 0 as *mut _, | |
| OPEN_EXISTING, | |
| FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, | |
| ptr::null_mut(), | |
| ); | |
| let mut data = [0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; | |
| let db = data.as_mut_ptr() as *mut REPARSE_MOUNTPOINT_DATA_BUFFER; | |
| let buf = &mut (*db).ReparseTarget as *mut WCHAR; | |
| let mut i = 0; | |
| // FIXME: this conversion is very hacky | |
| let v = br"\??\"; | |
| let v = v.iter().map(|x| *x as u16); | |
| for c in v.chain(target.as_os_str().encode_wide().skip(4)) { | |
| *buf.offset(i) = c; | |
| i += 1; | |
| } | |
| *buf.offset(i) = 0; | |
| i += 1; | |
| (*db).ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; | |
| (*db).ReparseTargetMaximumLength = (i * 2) as WORD; | |
| (*db).ReparseTargetLength = ((i - 1) * 2) as WORD; | |
| (*db).ReparseDataLength = (*db).ReparseTargetLength as DWORD + 12; | |
| let mut ret = 0; | |
| let res = DeviceIoControl( | |
| h as *mut _, | |
| FSCTL_SET_REPARSE_POINT, | |
| data.as_ptr() as *mut _, | |
| (*db).ReparseDataLength + 8, | |
| ptr::null_mut(), | |
| 0, | |
| &mut ret, | |
| ptr::null_mut(), | |
| ); | |
| if res == 0 { | |
| Err(io::Error::last_os_error()) | |
| } else { | |
| Ok(()) | |
| } | |
| } | |
| } | |
| pub fn hardlink(src: &Path, dest: &Path) -> io::Result<()> { | |
| let _ = fs::remove_file(dest); | |
| fs::hard_link(src, dest) | |
| } | |
| #[derive(Debug)] | |
| pub enum CommandError { | |
| Io(io::Error), | |
| Status(ExitStatus), | |
| } | |
| pub type CommandResult<T> = ::std::result::Result<T, CommandError>; | |
| impl error::Error for CommandError { | |
| fn description(&self) -> &str { | |
| use self::CommandError::*; | |
| match *self { | |
| Io(_) => "could not execute command", | |
| Status(_) => "command exited with unsuccessful status", | |
| } | |
| } | |
| fn cause(&self) -> Option<&error::Error> { | |
| use self::CommandError::*; | |
| match *self { | |
| Io(ref e) => Some(e), | |
| Status(_) => None, | |
| } | |
| } | |
| } | |
| impl fmt::Display for CommandError { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match *self { | |
| CommandError::Io(ref e) => write!(f, "Io: {}", e), | |
| CommandError::Status(ref s) => write!(f, "Status: {}", s), | |
| } | |
| } | |
| } | |
| pub fn cmd_status(cmd: &mut Command) -> CommandResult<()> { | |
| cmd.status().map_err(CommandError::Io).and_then(|s| { | |
| if s.success() { | |
| Ok(()) | |
| } else { | |
| Err(CommandError::Status(s)) | |
| } | |
| }) | |
| } | |
| pub fn remove_dir(path: &Path) -> io::Result<()> { | |
| if fs::symlink_metadata(path)?.file_type().is_symlink() { | |
| if cfg!(windows) { | |
| fs::remove_dir(path) | |
| } else { | |
| fs::remove_file(path) | |
| } | |
| } else { | |
| // Again because remove_dir all doesn't delete write-only files on windows, | |
| // this is a custom implementation, more-or-less copied from cargo. | |
| // cc rust-lang/rust#31944 | |
| // cc https://github.com/rust-lang/cargo/blob/master/tests/support/paths.rs#L52 | |
| remove_dir_all::remove_dir_all(path) | |
| } | |
| } | |
| pub fn copy_dir(src: &Path, dest: &Path) -> io::Result<()> { | |
| fs::create_dir(dest)?; | |
| for entry in src.read_dir()? { | |
| let entry = entry?; | |
| let kind = entry.file_type()?; | |
| let src = entry.path(); | |
| let dest = dest.join(entry.file_name()); | |
| if kind.is_dir() { | |
| copy_dir(&src, &dest)?; | |
| } else { | |
| fs::copy(&src, &dest)?; | |
| } | |
| } | |
| Ok(()) | |
| } | |
| pub fn prefix_arg<S: AsRef<OsStr>>(name: &str, s: S) -> OsString { | |
| let mut arg = OsString::from(name); | |
| arg.push(s); | |
| arg | |
| } | |
| pub fn has_cmd(cmd: &str) -> bool { | |
| let cmd = format!("{}{}", cmd, env::consts::EXE_SUFFIX); | |
| let path = env::var_os("PATH").unwrap_or(OsString::new()); | |
| env::split_paths(&path) | |
| .map(|p| p.join(&cmd)) | |
| .any(|p| p.exists()) | |
| } | |
| pub fn find_cmd<'a>(cmds: &[&'a str]) -> Option<&'a str> { | |
| cmds.into_iter().map(|&s| s).filter(|&s| has_cmd(s)).next() | |
| } | |
| pub fn open_browser(path: &Path) -> io::Result<bool> { | |
| #[cfg(not(windows))] | |
| fn inner(path: &Path) -> io::Result<bool> { | |
| use std::process::Stdio; | |
| use std::env; | |
| let env_browser = env::var_os("BROWSER").map(|b| env::split_paths(&b).collect::<Vec<_>>()); | |
| let env_commands = env_browser | |
| .as_ref() | |
| .map(|cmds| cmds.iter().by_ref().filter_map(|b| b.to_str()).collect()) | |
| .unwrap_or(vec![]); | |
| let commands = [ | |
| "xdg-open", | |
| "open", | |
| "firefox", | |
| "chromium", | |
| "sensible-browser", | |
| ]; | |
| if let Some(cmd) = find_cmd(&env_commands).or(find_cmd(&commands)) { | |
| Command::new(cmd) | |
| .arg(path) | |
| .stdin(Stdio::null()) | |
| .stdout(Stdio::null()) | |
| .stderr(Stdio::null()) | |
| .spawn() | |
| .map(|_| true) | |
| } else { | |
| Ok(false) | |
| } | |
| } | |
| #[cfg(windows)] | |
| fn inner(path: &Path) -> io::Result<bool> { | |
| use winapi::ctypes; | |
| use winapi::shared::windef::HWND; | |
| use winapi::shared::ntdef::LPCWSTR; | |
| use winapi::shared::minwindef::HINSTANCE; | |
| use std::ptr; | |
| // FIXME: When winapi has this function, use their version | |
| extern "system" { | |
| pub fn ShellExecuteW( | |
| hwnd: HWND, | |
| lpOperation: LPCWSTR, | |
| lpFile: LPCWSTR, | |
| lpParameters: LPCWSTR, | |
| lpDirectory: LPCWSTR, | |
| nShowCmd: ctypes::c_int, | |
| ) -> HINSTANCE; | |
| } | |
| const SW_SHOW: ctypes::c_int = 5; | |
| let path = windows::to_u16s(path)?; | |
| let operation = windows::to_u16s("open")?; | |
| let result = unsafe { | |
| ShellExecuteW( | |
| ptr::null_mut(), | |
| operation.as_ptr(), | |
| path.as_ptr(), | |
| ptr::null(), | |
| ptr::null(), | |
| SW_SHOW, | |
| ) | |
| }; | |
| Ok(result as usize > 32) | |
| } | |
| inner(path) | |
| } | |
| #[cfg(windows)] | |
| pub mod windows { | |
| use winapi::um::{combaseapi, shlobj, shtypes}; | |
| use winapi::shared::guiddef::GUID; | |
| use std::io; | |
| use std::path::PathBuf; | |
| use std::ptr; | |
| use std::slice; | |
| use std::ffi::{OsStr, OsString}; | |
| use std::os::windows::ffi::{OsStrExt, OsStringExt}; | |
| #[allow(non_upper_case_globals)] | |
| pub const FOLDERID_LocalAppData: GUID = GUID { | |
| Data1: 0xF1B32785, | |
| Data2: 0x6FBA, | |
| Data3: 0x4FCF, | |
| Data4: [0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91], | |
| }; | |
| #[allow(non_upper_case_globals)] | |
| pub const FOLDERID_Profile: GUID = GUID { | |
| Data1: 0x5E6C858F, | |
| Data2: 0x0E22, | |
| Data3: 0x4760, | |
| Data4: [0x9A, 0xFE, 0xEA, 0x33, 0x17, 0xB6, 0x71, 0x73], | |
| }; | |
| pub fn get_special_folder(id: &shtypes::KNOWNFOLDERID) -> io::Result<PathBuf> { | |
| let mut path = ptr::null_mut(); | |
| let result; | |
| unsafe { | |
| let code = shlobj::SHGetKnownFolderPath(id, 0, ptr::null_mut(), &mut path); | |
| if code == 0 { | |
| let mut length = 0usize; | |
| while *path.offset(length as isize) != 0 { | |
| length += 1; | |
| } | |
| let slice = slice::from_raw_parts(path, length); | |
| result = Ok(OsString::from_wide(slice).into()); | |
| } else { | |
| result = Err(io::Error::from_raw_os_error(code)); | |
| } | |
| combaseapi::CoTaskMemFree(path as *mut _); | |
| } | |
| result | |
| } | |
| pub fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> { | |
| fn inner(s: &OsStr) -> io::Result<Vec<u16>> { | |
| let mut maybe_result: Vec<u16> = s.encode_wide().collect(); | |
| if maybe_result.iter().any(|&u| u == 0) { | |
| return Err(io::Error::new( | |
| io::ErrorKind::InvalidInput, | |
| "strings passed to WinAPI cannot contain NULs", | |
| )); | |
| } | |
| maybe_result.push(0); | |
| Ok(maybe_result) | |
| } | |
| inner(s.as_ref()) | |
| } | |
| } |