Skip to main content

provenant/utils/
path.rs

1// SPDX-FileCopyrightText: Provenant contributors
2// SPDX-License-Identifier: Apache-2.0
3
4use std::path::{MAIN_SEPARATOR, Path};
5
6/// Render a filesystem path as a POSIX-style string with `/` separators.
7///
8/// Scan output must use `/` on every platform (ScanCode does, downstream tooling
9/// expects it, and the internal path helpers here split on `/`). On Unix this is
10/// just `to_string_lossy`; on Windows it rewrites the `\` separators the OS uses.
11pub(crate) fn to_posix_string(path: &Path) -> String {
12    path.to_string_lossy().replace(MAIN_SEPARATOR, "/")
13}
14
15pub(crate) fn parent_dir(path: &str) -> &str {
16    path.rsplit_once('/').map_or("", |(parent, _)| parent)
17}
18
19pub(crate) fn parent_dir_for_lookup(path: &str) -> Option<&str> {
20    if path.is_empty() {
21        return None;
22    }
23
24    path.rsplit_once('/').map(|(parent, _)| parent).or(Some(""))
25}
26
27#[cfg(test)]
28mod tests {
29    use super::{parent_dir, parent_dir_for_lookup, to_posix_string};
30
31    #[test]
32    fn to_posix_string_uses_forward_slashes() {
33        // Built from components so it uses the platform separator (\ on Windows,
34        // / on Unix); the result must be POSIX on both.
35        let path: std::path::PathBuf = ["packages", "app", "index.js"].iter().collect();
36        assert_eq!(to_posix_string(&path), "packages/app/index.js");
37    }
38
39    #[test]
40    fn parent_dir_handles_top_level_paths() {
41        assert_eq!(parent_dir("package.json"), "");
42        assert_eq!(parent_dir("packages/app/package.json"), "packages/app");
43    }
44
45    #[test]
46    fn parent_dir_for_lookup_walks_up_to_empty_root() {
47        assert_eq!(parent_dir_for_lookup("packages/app"), Some("packages"));
48        assert_eq!(parent_dir_for_lookup("packages"), Some(""));
49        assert_eq!(parent_dir_for_lookup(""), None);
50    }
51}