#[cfg(windows)]
mod directory_scan;
#[cfg(unix)]
mod unix;
#[cfg(windows)]
mod windows;
#[cfg(unix)]
pub use unix::{
EntryName, canonicalize, case_sensitive, entry_kind, exact_name, is_link_refusal,
open_directory, open_entry, open_no_follow, open_nonblocking, read_dir,
};
#[cfg(windows)]
pub use windows::{
EntryName, canonicalize, case_sensitive, entry_kind, exact_name, is_link_refusal,
open_directory, open_entry, open_no_follow, open_nonblocking, read_dir,
};
pub fn is_absolute(path: &std::path::Path) -> bool {
path.is_absolute()
}
pub fn logical_below(root: &std::path::Path, path: &str) -> Option<String> {
let path = std::path::Path::new(path);
if path.is_absolute() {
logical_from_native(path.strip_prefix(root).ok()?)
} else {
path.to_str().map(str::to_owned)
}
}
pub fn split_root(path: &std::path::Path) -> Option<(&std::path::Path, Vec<&std::ffi::OsStr>)> {
use std::path::Component;
let root = path.ancestors().last()?;
path.components()
.filter_map(|component| match component {
Component::Prefix(_) | Component::RootDir => None,
Component::Normal(name) => Some(Some(name)),
Component::CurDir | Component::ParentDir => Some(None),
})
.collect::<Option<Vec<_>>>()
.map(|names| (root, names))
}
pub fn link_target(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> {
std::fs::read_link(path)
}
pub const ALIASED_NAMES: bool = cfg!(any(windows, target_os = "macos"));
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FileKind {
File,
Directory,
Symlink,
Other,
}
pub fn current_directory() -> std::io::Result<std::path::PathBuf> {
canonicalize(&std::env::current_dir()?)
}
pub fn path_text(path: &std::path::Path) -> String {
let text = path.to_string_lossy();
#[cfg(windows)]
{
use std::path::{Component, Prefix};
let text = text.replace('\\', "/");
const VERBATIM: &str = "//?/";
const VERBATIM_UNC: &str = "//?/UNC/";
match path.components().next() {
Some(Component::Prefix(prefix)) => match prefix.kind() {
Prefix::VerbatimDisk(_) => text[VERBATIM.len()..].to_owned(),
Prefix::VerbatimUNC(_, _) => format!("//{}", &text[VERBATIM_UNC.len()..]),
_ => text,
},
_ => text,
}
}
#[cfg(unix)]
{
text.into_owned()
}
}
pub fn logical_from_native(path: &std::path::Path) -> Option<String> {
use std::path::Component;
path.components()
.map(|component| match component {
Component::Normal(name) => name.to_str(),
_ => None,
})
.collect::<Option<Vec<_>>>()
.map(|names| names.join("/"))
}
pub fn scoped_relative(path: &std::path::Path) -> std::io::Result<String> {
let path = path.to_str().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Artifact paths must be UTF-8.",
)
})?;
if path.contains(['\0', '\\'])
|| std::path::Path::new(path).is_absolute()
|| path.split('/').any(|part| part == "." || part == "..")
{
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Artifact path must be relative to its declared root.",
));
}
Ok(path
.split('/')
.filter(|part| !part.is_empty())
.collect::<Vec<_>>()
.join("/"))
}
#[cfg(test)]
mod tests {
use super::path_text;
#[cfg(unix)]
#[test]
fn literal_backslash_in_a_file_name_is_not_a_separator() {
let root = crate::test_os::tempdir();
let path = root.path().join(r"input\name");
std::fs::write(&path, "data").unwrap();
assert_eq!(
path_text(std::path::Path::new(path.file_name().unwrap())),
r"input\name"
);
}
#[cfg(windows)]
#[test]
fn native_separators_are_rendered_as_slashes() {
assert_eq!(
path_text(std::path::Path::new(r"C:\input\name")),
"C:/input/name"
);
}
#[cfg(windows)]
#[test]
fn verbatim_paths_render_without_the_native_prefix() {
assert_eq!(
path_text(std::path::Path::new(r"\\?\C:\input\name")),
"C:/input/name"
);
assert_eq!(
path_text(std::path::Path::new(r"\\?\UNC\server\share\input")),
"//server/share/input"
);
}
}