artifactize_tools/platform/
files.rs1#[cfg(windows)]
4mod directory_scan;
5#[cfg(unix)]
6mod unix;
7#[cfg(windows)]
8mod windows;
9
10#[cfg(unix)]
11pub use unix::{
12 EntryName, canonicalize, case_sensitive, entry_kind, exact_name, is_link_refusal,
13 open_directory, open_entry, open_no_follow, open_nonblocking, read_dir,
14};
15#[cfg(windows)]
16pub use windows::{
17 EntryName, canonicalize, case_sensitive, entry_kind, exact_name, is_link_refusal,
18 open_directory, open_entry, open_no_follow, open_nonblocking, read_dir,
19};
20
21pub fn is_absolute(path: &std::path::Path) -> bool {
25 path.is_absolute()
26}
27
28pub fn logical_below(root: &std::path::Path, path: &str) -> Option<String> {
31 let path = std::path::Path::new(path);
32 if path.is_absolute() {
33 logical_from_native(path.strip_prefix(root).ok()?)
34 } else {
35 path.to_str().map(str::to_owned)
36 }
37}
38
39pub fn split_root(path: &std::path::Path) -> Option<(&std::path::Path, Vec<&std::ffi::OsStr>)> {
40 use std::path::Component;
41 let root = path.ancestors().last()?;
42 path.components()
43 .filter_map(|component| match component {
44 Component::Prefix(_) | Component::RootDir => None,
45 Component::Normal(name) => Some(Some(name)),
46 Component::CurDir | Component::ParentDir => Some(None),
47 })
48 .collect::<Option<Vec<_>>>()
49 .map(|names| (root, names))
50}
51
52pub fn link_target(path: &std::path::Path) -> std::io::Result<std::path::PathBuf> {
55 std::fs::read_link(path)
56}
57
58pub const ALIASED_NAMES: bool = cfg!(any(windows, target_os = "macos"));
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum FileKind {
65 File,
66 Directory,
67 Symlink,
68 Other,
70}
71
72pub fn current_directory() -> std::io::Result<std::path::PathBuf> {
74 canonicalize(&std::env::current_dir()?)
75}
76
77pub fn path_text(path: &std::path::Path) -> String {
79 let text = path.to_string_lossy();
80 #[cfg(windows)]
81 {
82 use std::path::{Component, Prefix};
83 let text = text.replace('\\', "/");
84 const VERBATIM: &str = "//?/";
87 const VERBATIM_UNC: &str = "//?/UNC/";
88 match path.components().next() {
89 Some(Component::Prefix(prefix)) => match prefix.kind() {
90 Prefix::VerbatimDisk(_) => text[VERBATIM.len()..].to_owned(),
91 Prefix::VerbatimUNC(_, _) => format!("//{}", &text[VERBATIM_UNC.len()..]),
92 _ => text,
93 },
94 _ => text,
95 }
96 }
97 #[cfg(unix)]
98 {
99 text.into_owned()
100 }
101}
102
103pub fn logical_from_native(path: &std::path::Path) -> Option<String> {
106 use std::path::Component;
107 path.components()
108 .map(|component| match component {
109 Component::Normal(name) => name.to_str(),
110 _ => None,
111 })
112 .collect::<Option<Vec<_>>>()
113 .map(|names| names.join("/"))
114}
115
116pub fn scoped_relative(path: &std::path::Path) -> std::io::Result<String> {
119 let path = path.to_str().ok_or_else(|| {
120 std::io::Error::new(
121 std::io::ErrorKind::InvalidInput,
122 "Artifact paths must be UTF-8.",
123 )
124 })?;
125 if path.contains(['\0', '\\'])
126 || std::path::Path::new(path).is_absolute()
127 || path.split('/').any(|part| part == "." || part == "..")
128 {
129 return Err(std::io::Error::new(
130 std::io::ErrorKind::InvalidInput,
131 "Artifact path must be relative to its declared root.",
132 ));
133 }
134 Ok(path
135 .split('/')
136 .filter(|part| !part.is_empty())
137 .collect::<Vec<_>>()
138 .join("/"))
139}
140
141#[cfg(test)]
142mod tests {
143 use super::path_text;
144
145 #[cfg(unix)]
147 #[test]
148 fn literal_backslash_in_a_file_name_is_not_a_separator() {
149 let root = crate::test_os::tempdir();
150 let path = root.path().join(r"input\name");
151 std::fs::write(&path, "data").unwrap();
152 assert_eq!(
153 path_text(std::path::Path::new(path.file_name().unwrap())),
154 r"input\name"
155 );
156 }
157
158 #[cfg(windows)]
160 #[test]
161 fn native_separators_are_rendered_as_slashes() {
162 assert_eq!(
163 path_text(std::path::Path::new(r"C:\input\name")),
164 "C:/input/name"
165 );
166 }
167
168 #[cfg(windows)]
170 #[test]
171 fn verbatim_paths_render_without_the_native_prefix() {
172 assert_eq!(
173 path_text(std::path::Path::new(r"\\?\C:\input\name")),
174 "C:/input/name"
175 );
176 assert_eq!(
177 path_text(std::path::Path::new(r"\\?\UNC\server\share\input")),
178 "//server/share/input"
179 );
180 }
181}