Skip to main content

scirs2_core/
platform_compat.rs

1//! Cross-platform compatibility utilities for consistent behavior across
2//! Windows, macOS, Linux, and WebAssembly targets.
3//!
4//! This module provides helper functions that abstract over platform differences
5//! so that the rest of the SciRS2 codebase can remain platform-agnostic.
6//!
7//! # Examples
8//!
9//! ```
10//! use scirs2_core::platform_compat;
11//!
12//! // Portable temporary directory
13//! let tmp = platform_compat::temp_dir();
14//! assert!(tmp.is_absolute());
15//!
16//! // Portable temporary file path
17//! let f = platform_compat::temp_file("my_data.bin");
18//! assert!(f.ends_with("my_data.bin"));
19//!
20//! // CPU count
21//! let n = platform_compat::num_cpus();
22//! assert!(n >= 1);
23//! ```
24
25use std::path::PathBuf;
26
27/// Return the platform's temporary directory.
28///
29/// On Unix this is typically `/tmp`; on Windows it is `%TEMP%` or similar.
30/// Always prefer this over hard-coding `/tmp/`.
31#[inline]
32pub fn temp_dir() -> PathBuf {
33    std::env::temp_dir()
34}
35
36/// Build a [`PathBuf`] pointing to `<temp_dir>/<name>`.
37///
38/// Useful for constructing throwaway file paths in tests and transient
39/// storage configurations.
40#[inline]
41pub fn temp_file(name: &str) -> PathBuf {
42    let mut p = std::env::temp_dir();
43    p.push(name);
44    p
45}
46
47/// Build a [`PathBuf`] pointing to `<temp_dir>/<subdir>/<name>`.
48///
49/// Creates a namespaced temporary path without actually creating the directory.
50#[inline]
51pub fn temp_path(subdir: &str, name: &str) -> PathBuf {
52    let mut p = std::env::temp_dir();
53    p.push(subdir);
54    p.push(name);
55    p
56}
57
58/// Return the number of logical CPUs available on the current machine.
59///
60/// Falls back to `1` if the value cannot be determined (e.g. under some
61/// sandboxed or embedded environments).
62#[inline]
63pub fn num_cpus() -> usize {
64    std::thread::available_parallelism()
65        .map(|n| n.get())
66        .unwrap_or(1)
67}
68
69/// The native path separator character for the current platform.
70///
71/// `'/'` on Unix, `'\\'` on Windows.
72#[inline]
73pub fn path_separator() -> char {
74    std::path::MAIN_SEPARATOR
75}
76
77/// `true` when compiled for a Windows target.
78#[inline]
79pub const fn is_windows() -> bool {
80    cfg!(target_os = "windows")
81}
82
83/// `true` when compiled for a macOS target.
84#[inline]
85pub const fn is_macos() -> bool {
86    cfg!(target_os = "macos")
87}
88
89/// `true` when compiled for a Linux target.
90#[inline]
91pub const fn is_linux() -> bool {
92    cfg!(target_os = "linux")
93}
94
95/// `true` when compiled for a WebAssembly target.
96#[inline]
97pub const fn is_wasm() -> bool {
98    cfg!(target_family = "wasm")
99}
100
101/// `true` when compiled for any Unix-family target (Linux, macOS, BSDs, etc.).
102#[inline]
103pub const fn is_unix() -> bool {
104    cfg!(target_family = "unix")
105}
106
107/// Return the default temporary directory as a [`String`].
108///
109/// Convenience wrapper around [`temp_dir`] for code that stores paths as
110/// `String` fields (e.g. configuration structs).
111pub fn temp_dir_string() -> String {
112    temp_dir()
113        .to_str()
114        .unwrap_or(if cfg!(target_os = "windows") {
115            "C:\\Temp"
116        } else {
117            "/tmp"
118        })
119        .to_string()
120}
121
122/// Join a subdirectory name onto the platform temporary directory and return
123/// the result as a [`String`].
124pub fn temp_subdir_string(subdir: &str) -> String {
125    let mut p = temp_dir();
126    p.push(subdir);
127    p.to_str()
128        .unwrap_or(if cfg!(target_os = "windows") {
129            "C:\\Temp"
130        } else {
131            "/tmp"
132        })
133        .to_string()
134}
135
136// ---------------------------------------------------------------------------
137// Tests
138// ---------------------------------------------------------------------------
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn temp_dir_is_absolute() {
145        assert!(temp_dir().is_absolute());
146    }
147
148    #[test]
149    fn temp_file_ends_with_name() {
150        let p = temp_file("hello.txt");
151        assert!(p.ends_with("hello.txt"));
152    }
153
154    #[test]
155    fn temp_path_contains_subdir() {
156        let p = temp_path("scirs2", "data.bin");
157        assert!(p.ends_with("data.bin"));
158        // The parent should contain "scirs2"
159        let parent = p.parent().expect("should have parent");
160        assert!(parent.ends_with("scirs2"));
161    }
162
163    #[test]
164    fn num_cpus_at_least_one() {
165        assert!(num_cpus() >= 1);
166    }
167
168    #[test]
169    fn path_separator_is_correct() {
170        let sep = path_separator();
171        if cfg!(target_os = "windows") {
172            assert_eq!(sep, '\\');
173        } else {
174            assert_eq!(sep, '/');
175        }
176    }
177
178    #[test]
179    fn platform_detection_consistent() {
180        // At least one platform family should be true
181        let any = is_windows() || is_unix() || is_wasm();
182        assert!(any, "should detect at least one platform family");
183    }
184
185    #[test]
186    fn temp_dir_string_is_nonempty() {
187        assert!(!temp_dir_string().is_empty());
188    }
189
190    #[test]
191    fn temp_subdir_string_contains_subdir() {
192        let s = temp_subdir_string("scirs2_test");
193        assert!(s.contains("scirs2_test"));
194    }
195}