diff --git a/.github/config-schema.json b/.github/config-schema.json index 735003a19..9662e2548 100644 --- a/.github/config-schema.json +++ b/.github/config-schema.json @@ -5783,6 +5783,10 @@ } ] }, + "require_repo": { + "default": false, + "type": "boolean" + }, "shell": { "default": [], "allOf": [ diff --git a/Cargo.lock b/Cargo.lock index 1d99e419a..8d4092719 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2808,7 +2808,7 @@ dependencies = [ "urlencoding", "versions", "which", - "windows 0.47.0", + "windows 0.48.0", "winres", "yaml-rust", ] @@ -3447,11 +3447,11 @@ dependencies = [ [[package]] name = "windows" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2649ff315bee4c98757f15dac226efe3d81927adbb6e882084bb1ee3e0c330a7" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.47.0", + "windows-targets 0.48.0", ] [[package]] @@ -3508,17 +3508,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f8996d3f43b4b2d44327cd71b7b0efd1284ab60e6e9d0e8b630e18555d87d3e" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm 0.47.0", - "windows_aarch64_msvc 0.47.0", - "windows_i686_gnu 0.47.0", - "windows_i686_msvc 0.47.0", - "windows_x86_64_gnu 0.47.0", - "windows_x86_64_gnullvm 0.47.0", - "windows_x86_64_msvc 0.47.0", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -3529,9 +3529,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831d567d53d4f3cb1db332b68e6e2b6260228eb4d99a777d8b2e8ed794027c90" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" @@ -3553,9 +3553,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a42d54a417c60ce4f0e31661eed628f0fa5aca73448c093ec4d45fab4c51cdf" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" @@ -3577,9 +3577,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1925beafdbb22201a53a483db861a5644123157c1c3cee83323a2ed565d71e3" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" @@ -3601,9 +3601,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8ef8f2f1711b223947d9b69b596cf5a4e452c930fb58b6fc3fdae7d0ec6b31" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" @@ -3625,9 +3625,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acaa0c2cf0d2ef99b61c308a0c3dbae430a51b7345dedec470bd8f53f5a3642" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" @@ -3637,9 +3637,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a0628f71be1d11e17ca4a0e9e15b3a5180f6fbf1c2d55e3ba3f850378052c1" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" @@ -3661,9 +3661,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.47.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6e62c256dc6d40b8c8707df17df8d774e60e39db723675241e7c15e910bce7" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" diff --git a/Cargo.toml b/Cargo.toml index 984e12f17..2611454e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ features = ["preserve_order", "indexmap"] deelevate = "0.2.0" [target.'cfg(windows)'.dependencies.windows] -version = "0.47.0" +version = "0.48.0" features = [ "Win32_Foundation", "Win32_UI_Shell", diff --git a/docs/config/README.md b/docs/config/README.md index 118baf63f..32d3d3afb 100644 --- a/docs/config/README.md +++ b/docs/config/README.md @@ -4346,6 +4346,7 @@ Format strings can also contain shell specific prompt sequences, e.g. | ------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `command` | `''` | The command whose output should be printed. The command will be passed on stdin to the shell. | | `when` | `false` | Either a boolean value (`true` or `false`, without quotes) or a string shell command used as a condition to show the module. In case of a string, the module will be shown if the command returns a `0` status code. | +| `require_repo` | `false` | If `true`, the module will only be shown in paths containing a (git) repository. This option alone is not sufficient display condition in absence of other options. | | `shell` | | [See below](#custom-command-shell) | | `description` | `''` | The description of the module that is shown when running `starship explain`. | | `detect_files` | `[]` | The files that will be searched in the working directory for a match. | diff --git a/src/configs/custom.rs b/src/configs/custom.rs index c4026f54d..e5c13b240 100644 --- a/src/configs/custom.rs +++ b/src/configs/custom.rs @@ -14,6 +14,7 @@ pub struct CustomConfig<'a> { pub symbol: &'a str, pub command: &'a str, pub when: Either, + pub require_repo: bool, pub shell: VecOr<&'a str>, pub description: &'a str, pub style: &'a str, @@ -38,6 +39,7 @@ impl<'a> Default for CustomConfig<'a> { symbol: "", command: "", when: Either::First(false), + require_repo: false, shell: VecOr::default(), description: "", style: "green bold", diff --git a/src/context.rs b/src/context.rs index 0994a8e55..b0bfc0fd5 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,7 +1,7 @@ use crate::config::{ModuleConfig, StarshipConfig}; use crate::configs::StarshipRootConfig; use crate::module::Module; -use crate::utils::{create_command, exec_timeout, read_file, CommandOutput}; +use crate::utils::{create_command, exec_timeout, read_file, CommandOutput, PathExt}; use crate::modules; use crate::utils::{self, home_dir}; @@ -248,6 +248,16 @@ impl<'a> Context<'a> { }) } + /// Begins an ancestor scan at the current directory, see [`ScanAncestors`] for available + /// methods. + pub fn begin_ancestor_scan(&'a self) -> ScanAncestors<'a> { + ScanAncestors { + path: &self.current_dir, + files: &[], + folders: &[], + } + } + /// Will lazily get repo root and branch when a module requests it. pub fn get_repo(&self) -> Result<&Repo, Box> { self.repo @@ -607,6 +617,48 @@ impl<'a> ScanDir<'a> { } } +/// Scans the ancestors of a given path until a directory containing one of the given files or +/// folders is found. +pub struct ScanAncestors<'a> { + path: &'a Path, + files: &'a [&'a str], + folders: &'a [&'a str], +} + +impl<'a> ScanAncestors<'a> { + #[must_use] + pub const fn set_files(mut self, files: &'a [&'a str]) -> Self { + self.files = files; + self + } + + #[must_use] + pub const fn set_folders(mut self, folders: &'a [&'a str]) -> Self { + self.folders = folders; + self + } + + /// Scans upwards starting from the initial path until a directory containing one of the given + /// files or folders is found. + /// + /// The scan does not cross device boundaries. + pub fn scan(&self) -> Option<&'a Path> { + let initial_device_id = self.path.device_id(); + for dir in self.path.ancestors() { + if initial_device_id != dir.device_id() { + break; + } + + if self.files.iter().any(|name| dir.join(name).is_file()) + || self.folders.iter().any(|name| dir.join(name).is_dir()) + { + return Some(dir); + } + } + None + } +} + fn get_current_branch(repository: &Repository) -> Option { let name = repository.head_name().ok()??; let shorthand = name.shorten(); diff --git a/src/modules/aws.rs b/src/modules/aws.rs index c9400d96f..60fe32894 100644 --- a/src/modules/aws.rs +++ b/src/modules/aws.rs @@ -204,6 +204,29 @@ fn has_defined_credentials( Some(section.contains_key("aws_access_key_id")) } +// https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html#cli-configure-files-settings +fn has_source_profile( + context: &Context, + aws_profile: Option<&Profile>, + aws_config: &AwsConfigFile, + aws_creds: &AwsCredsFile, +) -> Option { + let config = get_config(context, aws_config)?; + + let config_section = get_profile_config(config, aws_profile)?; + let source_profile = config_section + .get("source_profile") + .map(std::borrow::ToOwned::to_owned); + + let has_credential_process = + has_credential_process_or_sso(context, source_profile.as_ref(), aws_config, aws_creds) + .unwrap_or(false); + let has_credentials = + has_defined_credentials(context, source_profile.as_ref(), aws_creds).unwrap_or(false); + + Some(has_credential_process || has_credentials) +} + pub fn module<'a>(context: &'a Context) -> Option> { let mut module = context.new_module("aws"); let config: AwsConfig = AwsConfig::try_load(module.config); @@ -216,10 +239,12 @@ pub fn module<'a>(context: &'a Context) -> Option> { return None; } - // only display if credential_process is defined or has valid credentials + // only display in the presence of credential_process, source_profile or valid credentials if !config.force_display && !has_credential_process_or_sso(context, aws_profile.as_ref(), &aws_config, &aws_creds) .unwrap_or(false) + && !has_source_profile(context, aws_profile.as_ref(), &aws_config, &aws_creds) + .unwrap_or(false) && !has_defined_credentials(context, aws_profile.as_ref(), &aws_creds).unwrap_or(false) { return None; @@ -1042,4 +1067,92 @@ sso_role_name = assert_eq!(expected, actual); } + + #[test] + fn source_profile_set() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let config_path = dir.path().join("config"); + let credential_path = dir.path().join("credentials"); + let mut config = File::create(&config_path)?; + config.write_all( + "[profile astronauts] +source_profile = starship +" + .as_bytes(), + )?; + let mut credentials = File::create(&credential_path)?; + credentials.write_all( + "[starship] +aws_access_key_id=dummy +aws_secret_access_key=dummy +" + .as_bytes(), + )?; + + let actual = ModuleRenderer::new("aws") + .env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref()) + .env( + "AWS_CREDENTIALS_FILE", + credential_path.to_string_lossy().as_ref(), + ) + .env("AWS_PROFILE", "astronauts") + .collect(); + let expected = Some(format!( + "on {}", + Color::Yellow.bold().paint("☁️ astronauts ") + )); + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn source_profile_not_exists() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let config_path = dir.path().join("config"); + let mut config = File::create(&config_path)?; + config.write_all( + "[profile astronauts] +source_profile = starship +" + .as_bytes(), + )?; + + let actual = ModuleRenderer::new("aws") + .env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref()) + .env("AWS_PROFILE", "astronauts") + .collect(); + let expected = None; + + assert_eq!(expected, actual); + dir.close() + } + + #[test] + fn source_profile_uses_credential_process() -> io::Result<()> { + let dir = tempfile::tempdir()?; + let config_path = dir.path().join("config"); + let mut config = File::create(&config_path)?; + config.write_all( + "[profile starship] +credential_process = /opt/bin/awscreds-retriever --username starship + +[profile astronauts] +source_profile = starship +" + .as_bytes(), + )?; + + let actual = ModuleRenderer::new("aws") + .env("AWS_CONFIG_FILE", config_path.to_string_lossy().as_ref()) + .env("AWS_PROFILE", "astronauts") + .collect(); + let expected = Some(format!( + "on {}", + Color::Yellow.bold().paint("☁️ astronauts ") + )); + + assert_eq!(expected, actual); + dir.close() + } } diff --git a/src/modules/custom.rs b/src/modules/custom.rs index e224958a4..f3dde1185 100644 --- a/src/modules/custom.rs +++ b/src/modules/custom.rs @@ -34,6 +34,10 @@ pub fn module<'a>(name: &str, context: &'a Context) -> Option> { } } + if config.require_repo && context.get_repo().is_err() { + return None; + } + // Note: Forward config if `Module` ends up needing `config` let mut module = Module::new(&format!("custom.{name}"), config.description, None); @@ -294,7 +298,7 @@ fn handle_shell(command: &mut Command, shell: &str, shell_args: &[&str]) -> bool mod tests { use super::*; - use crate::test::ModuleRenderer; + use crate::test::{fixture_repo, FixtureProvider, ModuleRenderer}; use nu_ansi_term::Color; use std::fs::File; use std::io; @@ -721,4 +725,40 @@ mod tests { let expected = None; assert_eq!(expected, actual); } + + #[test] + fn test_render_require_repo_not_in() -> io::Result<()> { + let repo_dir = tempfile::tempdir()?; + + let actual = ModuleRenderer::new("custom.test") + .path(repo_dir.path()) + .config(toml::toml! { + [custom.test] + when = true + require_repo = true + format = "test" + }) + .collect(); + let expected = None; + assert_eq!(expected, actual); + repo_dir.close() + } + + #[test] + fn test_render_require_repo_in() -> io::Result<()> { + let repo_dir = fixture_repo(FixtureProvider::Git)?; + + let actual = ModuleRenderer::new("custom.test") + .path(repo_dir.path()) + .config(toml::toml! { + [custom.test] + when = true + require_repo = true + format = "test" + }) + .collect(); + let expected = Some("test".to_string()); + assert_eq!(expected, actual); + repo_dir.close() + } } diff --git a/src/modules/fossil_branch.rs b/src/modules/fossil_branch.rs index da4dabbdb..7cc4d331b 100644 --- a/src/modules/fossil_branch.rs +++ b/src/modules/fossil_branch.rs @@ -22,15 +22,11 @@ pub fn module<'a>(context: &'a Context) -> Option> { } else { ".fslckout" }; - - let is_checkout = context - .try_begin_scan()? + // See if we're in a check-out by scanning upwards for a directory containing the checkout_db file + context + .begin_ancestor_scan() .set_files(&[checkout_db]) - .is_match(); - - if !is_checkout { - return None; - } + .scan()?; let len = if config.truncation_length <= 0 { log::warn!( @@ -143,6 +139,18 @@ mod tests { tempdir.close() } + #[test] + fn test_fossil_branch_subdir() -> io::Result<()> { + let tempdir = fixture_repo(FixtureProvider::Fossil)?; + let checkout_dir = tempdir.path(); + expect_fossil_branch_with_config( + &checkout_dir.join("subdir"), + None, + &[Expect::BranchName("topic-branch"), Expect::NoTruncation], + ); + tempdir.close() + } + #[test] fn test_fossil_branch_configured() -> io::Result<()> { let tempdir = fixture_repo(FixtureProvider::Fossil)?; diff --git a/src/modules/hg_branch.rs b/src/modules/hg_branch.rs index 8a9f84ae2..c4c4a2a30 100644 --- a/src/modules/hg_branch.rs +++ b/src/modules/hg_branch.rs @@ -1,4 +1,4 @@ -use std::io::{Error, ErrorKind}; +use std::io::Error; use std::path::Path; use super::utils::truncate::truncate_text; @@ -6,7 +6,6 @@ use super::{Context, Module, ModuleConfig}; use crate::configs::hg_branch::HgBranchConfig; use crate::formatter::StringFormatter; -use crate::modules::utils::path::PathExt; use crate::utils::read_file; /// Creates a module with the Hg bookmark or branch in the current directory @@ -32,7 +31,7 @@ pub fn module<'a>(context: &'a Context) -> Option> { config.truncation_length as usize }; - let repo_root = get_hg_repo_root(context).ok()?; + let repo_root = context.begin_ancestor_scan().set_folders(&[".hg"]).scan()?; let branch_name = get_hg_current_bookmark(repo_root).unwrap_or_else(|_| { get_hg_branch_name(repo_root).unwrap_or_else(|_| String::from("default")) }); @@ -73,20 +72,6 @@ pub fn module<'a>(context: &'a Context) -> Option> { Some(module) } -fn get_hg_repo_root<'a>(ctx: &'a Context) -> Result<&'a Path, Error> { - let dir = ctx.current_dir.as_path(); - let dev_id = dir.device_id(); - for root_dir in dir.ancestors() { - if dev_id != root_dir.device_id() { - break; - } - if root_dir.join(".hg").is_dir() { - return Ok(root_dir); - } - } - Err(Error::new(ErrorKind::Other, "No .hg found!")) -} - fn get_hg_branch_name(hg_root: &Path) -> Result { match read_file(hg_root.join(".hg").join("branch")) { Ok(b) => Ok(b.trim().to_string()), diff --git a/src/modules/pulumi.rs b/src/modules/pulumi.rs index f5e54a360..65a89b2a8 100644 --- a/src/modules/pulumi.rs +++ b/src/modules/pulumi.rs @@ -81,24 +81,28 @@ pub fn module<'a>(context: &'a Context) -> Option> { } } -/// Parse the output of `pulumi version` into just the version string. +/// Parse and sanitize the output of `pulumi version` into just the version string. /// /// Normally, this just means returning it. When Pulumi is being developed, it /// can return results like `3.12.0-alpha.1630554544+f89e9a29.dirty`, which we /// don't want to see. Instead we display that as `3.12.0-alpha`. fn parse_version(version: &str) -> &str { + let new_version = version.strip_prefix('v').unwrap_or(version); + + let sanitized_version = new_version.trim_end(); + let mut periods = 0; - for (i, c) in version.as_bytes().iter().enumerate() { + for (i, c) in sanitized_version.as_bytes().iter().enumerate() { if *c == b'.' { if periods == 2 { - return &version[0..i]; + return &sanitized_version[0..i]; } else { periods += 1; } } } // We didn't hit 3 periods, so we just return the whole string. - version + sanitized_version } /// Find a file describing a Pulumi package in the current directory (or any parent directory). @@ -212,20 +216,53 @@ mod tests { #[test] fn pulumi_version_release() { - let input = "3.12.0"; - assert_eq!(parse_version(input), input); + let expected = "3.12.0"; + let inputs: [&str; 6] = [ + "v3.12.0\r\n", + "v3.12.0\n", + "v3.12.0", + "3.12.0\r\n", + "3.12.0\n", + "3.12.0", + ]; + + for input in inputs.iter() { + assert_eq!(parse_version(input), expected); + } } #[test] fn pulumi_version_prerelease() { - let input = "3.12.0-alpha"; - assert_eq!(parse_version(input), input); + let expected = "3.12.0-alpha"; + let inputs: [&str; 6] = [ + "v3.12.0-alpha\r\n", + "v3.12.0-alpha\n", + "v3.12.0-alpha", + "3.12.0-alpha\r\n", + "3.12.0-alpha\n", + "3.12.0-alpha", + ]; + + for input in inputs.iter() { + assert_eq!(parse_version(input), expected); + } } #[test] fn pulumi_version_dirty() { - let input = "3.12.0-alpha.1630554544+f89e9a29.dirty"; - assert_eq!(parse_version(input), "3.12.0-alpha"); + let expected = "3.12.0-alpha"; + let inputs: [&str; 6] = [ + "v3.12.0-alpha.1630554544+f89e9a29.dirty\r\n", + "v3.12.0-alpha.1630554544+f89e9a29.dirty\n", + "v3.12.0-alpha.1630554544+f89e9a29.dirty", + "3.12.0-alpha.1630554544+f89e9a29.dirty\r\n", + "3.12.0-alpha.1630554544+f89e9a29.dirty\n", + "3.12.0-alpha.1630554544+f89e9a29.dirty", + ]; + + for input in inputs.iter() { + assert_eq!(parse_version(input), expected); + } } #[test] diff --git a/src/modules/utils/path.rs b/src/modules/utils/path.rs index 5f5ad4847..2bbb02b51 100644 --- a/src/modules/utils/path.rs +++ b/src/modules/utils/path.rs @@ -13,8 +13,6 @@ pub trait PathExt { /// E.g. `\\?\UNC\server\share\foo` => `\foo` /// E.g. `/foo/bar` => `/foo/bar` fn without_prefix(&self) -> &Path; - /// Get device / volume info - fn device_id(&self) -> Option; } #[cfg(windows)] @@ -82,11 +80,6 @@ impl PathExt for Path { let (_, path) = normalize::normalize_path(self); path } - - fn device_id(&self) -> Option { - // Maybe it should use unimplemented! - Some(42u64) - } } // NOTE: Windows path prefixes are only parsed on Windows. @@ -107,24 +100,6 @@ impl PathExt for Path { fn without_prefix(&self) -> &Path { self } - - #[cfg(target_os = "linux")] - fn device_id(&self) -> Option { - use std::os::linux::fs::MetadataExt; - match self.metadata() { - Ok(m) => Some(m.st_dev()), - Err(_) => None, - } - } - - #[cfg(all(unix, not(target_os = "linux")))] - fn device_id(&self) -> Option { - use std::os::unix::fs::MetadataExt; - match self.metadata() { - Ok(m) => Some(m.dev()), - Err(_) => None, - } - } } #[cfg(test)] diff --git a/src/test/mod.rs b/src/test/mod.rs index 48bb51ad3..23b0d6fb3 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -182,6 +182,7 @@ pub fn fixture_repo(provider: FixtureProvider) -> io::Result { ".fslckout" }; let path = tempfile::tempdir()?; + fs::create_dir(path.path().join("subdir"))?; fs::OpenOptions::new() .create(true) .write(true) diff --git a/src/utils.rs b/src/utils.rs index 54b9e7393..aad9f297a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -644,6 +644,40 @@ pub fn encode_to_hex(slice: &[u8]) -> String { String::from_utf8(dst).unwrap() } +pub trait PathExt { + /// Get device / volume info + fn device_id(&self) -> Option; +} + +#[cfg(windows)] +impl PathExt for Path { + fn device_id(&self) -> Option { + // Maybe it should use unimplemented! + Some(42u64) + } +} + +#[cfg(not(windows))] +impl PathExt for Path { + #[cfg(target_os = "linux")] + fn device_id(&self) -> Option { + use std::os::linux::fs::MetadataExt; + match self.metadata() { + Ok(m) => Some(m.st_dev()), + Err(_) => None, + } + } + + #[cfg(all(unix, not(target_os = "linux")))] + fn device_id(&self) -> Option { + use std::os::unix::fs::MetadataExt; + match self.metadata() { + Ok(m) => Some(m.dev()), + Err(_) => None, + } + } +} + #[cfg(test)] mod tests { use super::*;