Skip to main content

unlit_ecs/
naming.rs

1//! File-naming helpers every save path shares.
2//!
3//! Reading an extension and appending one when a name lacks it are pure string
4//! operations, so they are compiled on every target — unlike the saves that
5//! write a file, which need a filesystem and a random id.
6
7use std::path::Path;
8
9/// The extension `file_name` carries, unless it is empty.
10pub fn extension_of(file_name: &str) -> Option<&str> {
11    Path::new(file_name)
12        .extension()
13        .and_then(|extension| extension.to_str())
14        .filter(|extension| !extension.is_empty())
15}
16
17/// `file_name` with `extension` appended when it carries none, or `None`
18/// when there is no file name to extend.
19pub fn with_extension(file_name: Option<&str>, extension: &str) -> Option<String> {
20    let file_name = file_name?;
21    Some(match extension_of(file_name) {
22        Some(_) => file_name.to_string(),
23        None => format!("{file_name}.{extension}"),
24    })
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30
31    #[test]
32    fn an_extension_is_only_read_when_it_is_present() {
33        assert_eq!(extension_of("a.png"), Some("png"));
34        assert_eq!(extension_of("dir/a.webp"), Some("webp"));
35        assert_eq!(extension_of("a"), None);
36        assert_eq!(extension_of("a."), None);
37    }
38
39    #[test]
40    fn a_missing_extension_is_appended() {
41        assert_eq!(with_extension(None, "webp"), None);
42        assert_eq!(with_extension(Some("a"), "webp").as_deref(), Some("a.webp"));
43        assert_eq!(
44            with_extension(Some("a.png"), "webp").as_deref(),
45            Some("a.png")
46        );
47    }
48}