use std::path::{Path, PathBuf}; /// Normalizes the path according the common CodeQL specification. Assumes that /// `path` has already been canonicalized using `std::fs::canonicalize`. pub fn normalize_path(path: &Path) -> String { if cfg!(windows) { // The way Rust canonicalizes paths doesn't match the CodeQL spec, so we // have to do a bit of work removing certain prefixes and replacing // backslashes. let mut components: Vec = Vec::new(); for component in path.components() { match component { std::path::Component::Prefix(prefix) => match prefix.kind() { std::path::Prefix::Disk(letter) | std::path::Prefix::VerbatimDisk(letter) => { components.push(format!("{}:", letter as char)); } std::path::Prefix::Verbatim(x) | std::path::Prefix::DeviceNS(x) => { components.push(x.to_string_lossy().to_string()); } std::path::Prefix::UNC(server, share) | std::path::Prefix::VerbatimUNC(server, share) => { components.push(server.to_string_lossy().to_string()); components.push(share.to_string_lossy().to_string()); } }, std::path::Component::Normal(n) => { components.push(n.to_string_lossy().to_string()); } std::path::Component::RootDir => {} std::path::Component::CurDir => {} std::path::Component::ParentDir => {} } } components.join("/") } else { // For other operating systems, we can use the canonicalized path // without modifications. format!("{}", path.display()) } } /// Convert a user-supplied path to an absolute path, and convert it to a verbatim path on Windows. pub fn path_from_string(path: &str) -> PathBuf { let mut path = PathBuf::from(path); // make path absolute if path.is_relative() { path = std::env::current_dir().unwrap().join(path) }; let mut components = path.components(); // make Windows paths verbatim (with `\\?\` prefixes) which allow for extended-length paths. let mut result = match components.next() { None => unreachable!("empty path"), Some(component) => match component { std::path::Component::Prefix(prefix) => match prefix.kind() { std::path::Prefix::Disk(drive) => { let root = format!(r"\\?\{}:\", drive as char); PathBuf::from(root) } std::path::Prefix::UNC(server, share) => { let mut root = std::ffi::OsString::from(r"\\?\UNC\"); root.push(server); root.push(r"\"); root.push(share); PathBuf::from(root) } std::path::Prefix::Verbatim(_) | std::path::Prefix::VerbatimUNC(_, _) | std::path::Prefix::VerbatimDisk(_) | std::path::Prefix::DeviceNS(_) => Path::new(&component).to_path_buf(), }, _ => Path::new(&component).to_path_buf(), }, }; // remove `.` and `..` components for component in components { match component { std::path::Component::CurDir => continue, std::path::Component::ParentDir => { result.pop(); } _ => result.push(component), } } result } pub fn path_for(dir: &Path, path: &Path, ext: &str) -> PathBuf { let mut result = PathBuf::from(dir); for component in path.components() { match component { std::path::Component::Prefix(prefix) => match prefix.kind() { std::path::Prefix::Disk(letter) | std::path::Prefix::VerbatimDisk(letter) => { result.push(format!("{}_", letter as char)) } std::path::Prefix::Verbatim(x) | std::path::Prefix::DeviceNS(x) => { result.push(x); } std::path::Prefix::UNC(server, share) | std::path::Prefix::VerbatimUNC(server, share) => { result.push("unc"); result.push(server); result.push(share); } }, std::path::Component::RootDir => { // skip } std::path::Component::Normal(_) => { result.push(component); } std::path::Component::CurDir => { // skip } std::path::Component::ParentDir => { result.pop(); } } } if !ext.is_empty() { match result.extension() { Some(x) => { let mut new_ext = x.to_os_string(); new_ext.push("."); new_ext.push(ext); result.set_extension(new_ext); } None => { result.set_extension(ext); } } } result }