joatmon/fs/
paths.rs

1// The MIT License (MIT)
2//
3// Copyright (c) 2020-3 Richard Cook
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy of
6// this software and associated documentation files (the "Software"), to deal in
7// the Software without restriction, including without limitation the rights to
8// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9// the Software, and to permit persons to whom the Software is furnished to do so,
10// subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21//
22use chrono::{DateTime, SecondsFormat, Utc};
23use std::ffi::OsString;
24use std::path::{Path, PathBuf};
25
26#[must_use]
27pub fn label_file_name(path: &Path, label: &str) -> Option<PathBuf> {
28    let mut file_name = OsString::new();
29
30    if let Some(s) = path.file_stem() {
31        file_name.push(s);
32    } else {
33        return None;
34    }
35
36    file_name.push("-");
37    file_name.push(label);
38
39    if let Some(s) = path.extension() {
40        file_name.push(".");
41        file_name.push(s);
42    }
43
44    Some(path.with_file_name(file_name))
45}
46
47#[must_use]
48pub fn file_name_safe_timestamp(dt: &DateTime<Utc>) -> String {
49    dt.to_rfc3339_opts(SecondsFormat::Millis, true)
50        .replace(['-', ':', '.'], "")
51}
52
53#[cfg(test)]
54mod tests {
55    use super::{file_name_safe_timestamp, label_file_name};
56    use chrono::{TimeZone, Utc};
57    use rstest::rstest;
58    use std::path::PathBuf;
59
60    #[rstest]
61    #[case(Some(PathBuf::from("/aaa/bbb/ccc-ddd.txt")), "/aaa/bbb/ccc.txt", "ddd")]
62    #[case(Some(PathBuf::from("/aaa/bbb/ccc-ddd")), "/aaa/bbb/ccc", "ddd")]
63    #[case(Some(PathBuf::from("ccc-ddd.txt")), "ccc.txt", "ddd")]
64    #[case(Some(PathBuf::from("ccc-ddd")), "ccc", "ddd")]
65    fn label_file_name_basics(
66        #[case] expected_path: Option<PathBuf>,
67        #[case] path: PathBuf,
68        #[case] label: &str,
69    ) {
70        assert_eq!(expected_path, label_file_name(&path, label));
71    }
72
73    #[test]
74    fn file_name_safe_timestamp_basics() {
75        let dt = Utc
76            .with_ymd_and_hms(2019, 3, 17, 16, 43, 0)
77            .single()
78            .expect("must be valid");
79        assert_eq!("20190317T164300000Z", file_name_safe_timestamp(&dt));
80    }
81}