standarbuild-detect 0.3.0

Detect project kind (Rust, Node, Bun, Deno, Python, Lua, C/C++) AND workspace kind (Cargo, Npm/Pnpm/Yarn/Bun, Deno, Go, Lerna, Nx, Turborepo, Mira) in polyglot monorepos
Documentation
//! POSIX-style path normalisation. Strips Windows verbatim prefixes
//! (`\\?\`), drops `CurDir` segments, and replaces `\` with `/`. Useful when
//! emitting paths into JSON / cross-platform tooling.

use std::path::{Component, Path, PathBuf};

pub fn to_posix(path: &Path) -> String {
	let mut buf = PathBuf::new();
	for comp in path.components() {
		match comp {
			Component::CurDir => continue,
			other => buf.push(other.as_os_str()),
		}
	}
	let s = buf.to_string_lossy();
	let stripped = s.strip_prefix(r"\\?\").unwrap_or(&s);
	stripped.replace('\\', "/")
}

#[cfg(feature = "serde")]
pub fn serialize_path<S>(path: &PathBuf, s: S) -> Result<S::Ok, S::Error>
where
	S: serde::Serializer,
{
	s.serialize_str(&to_posix(path))
}

#[cfg(feature = "serde")]
pub fn serialize_path_opt<S>(path: &Option<PathBuf>, s: S) -> Result<S::Ok, S::Error>
where
	S: serde::Serializer,
{
	match path {
		Some(p) => s.serialize_str(&to_posix(p)),
		None => s.serialize_none(),
	}
}

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn drops_curdir_segments() {
		let p = Path::new("a").join(".").join("b");
		assert_eq!(to_posix(&p), "a/b");
	}

	#[test]
	fn replaces_backslashes() {
		let p = PathBuf::from(r"a\b\c");
		assert_eq!(to_posix(&p), "a/b/c");
	}

	#[test]
	fn strips_windows_verbatim_prefix() {
		let p = PathBuf::from(r"\\?\C:\foo\bar");
		let out = to_posix(&p);
		assert_eq!(out, "C:/foo/bar");
	}

	#[test]
	fn mixed_separators_with_curdir() {
		let p = PathBuf::from(r"C:\Users\me\.\proj/sub");
		assert_eq!(to_posix(&p), "C:/Users/me/proj/sub");
	}

	#[test]
	fn keeps_parent_dir() {
		let p = PathBuf::from("a/../b");
		let out = to_posix(&p);
		assert!(out.contains(".."), "ParentDir should be preserved (got {})", out);
	}

	#[test]
	fn empty_path() {
		let p = PathBuf::new();
		assert_eq!(to_posix(&p), "");
	}

	#[cfg(feature = "serde")]
	#[test]
	fn serialize_path_emits_posix_string() {
		#[derive(serde::Serialize)]
		struct W {
			#[serde(serialize_with = "super::serialize_path")]
			p: PathBuf,
		}
		let w = W { p: PathBuf::from(r"a\b") };
		let json = serde_json::to_string(&w).unwrap();
		assert_eq!(json, r#"{"p":"a/b"}"#);
	}

	#[cfg(feature = "serde")]
	#[test]
	fn serialize_path_opt_some() {
		#[derive(serde::Serialize)]
		struct W {
			#[serde(serialize_with = "super::serialize_path_opt")]
			p: Option<PathBuf>,
		}
		let w = W { p: Some(PathBuf::from(r"a\.\b")) };
		let json = serde_json::to_string(&w).unwrap();
		assert_eq!(json, r#"{"p":"a/b"}"#);
	}

	#[cfg(feature = "serde")]
	#[test]
	fn serialize_path_opt_none() {
		#[derive(serde::Serialize)]
		struct W {
			#[serde(serialize_with = "super::serialize_path_opt")]
			p: Option<PathBuf>,
		}
		let w = W { p: None };
		let json = serde_json::to_string(&w).unwrap();
		assert_eq!(json, r#"{"p":null}"#);
	}
}