filetools/
naming.rs

1//! Functions that generate PathBuf filenames
2//!
3//! # Examples
4//!
5//! ```
6//! use std::path::PathBuf;
7//! use filetools::naming;
8//!
9//! // Generates the name `test.pdf`
10//! let custom_name = naming::generate_name("test", "pdf");
11//!
12//! // Name will be suffixed by the current time it was generated
13//! // E.g. `test_[Timestamp].pdf`
14//! let timestamped_name = naming::generate_timestamped_name("test", "pdf");
15//!
16//! // Random name is a UUIDv4 string suffixed by the extension
17//! // E.g. `00762527-012a-43c1-a673-cad9bc5eef64.pdf`
18//! let random_name = naming::generate_uuid4_name("pdf");
19//!
20//! // N-digit name is a number prefixed by X zeros (e.g. 0005.pdf)
21//! let n_digit_name = naming::generate_n_digit_name(5, 4, "pdf");
22//! ```
23//!
24
25use chrono::prelude::*;
26use std::path::PathBuf;
27use uuid::Uuid;
28
29/// Helper for makeing extensions
30///
31/// Literally just preprends a .
32fn make_extension(ext: impl AsRef<str>) -> String {
33    if ext.as_ref().is_empty() {
34        return String::new();
35    }
36
37    format!(".{}", ext.as_ref())
38}
39
40/// Generates a `PathBuf` from a given and extension
41///
42/// # Example
43///
44/// ```rust
45/// use filetools::naming::generate_name;
46///
47/// // Will generate the name `test.json`
48/// let name = generate_name("test", "json");
49/// ```
50pub fn generate_name(name: &str, ext: &str) -> PathBuf {
51    PathBuf::from(format!("{}{}", name, make_extension(ext)))
52}
53
54/// Generates a `PathBuf` from a name and extention with a default timestamp of "DD_MM_YY_HHMMSS"
55/// If `fname` is empty, just uses the timestamp and extension
56///
57/// # Example
58///
59/// ```rust
60/// use filetools::naming::generate_timestamped_name;
61///
62/// // Will generate the name `some_file_[Timestamp].pdf`
63/// let ts_with_filename = generate_timestamped_name("some_file", ".pdf");
64///
65/// // Will generate the name `[Timestamp].txt`
66/// let ts_no_filename = generate_timestamped_name("", ".txt");
67/// ```
68pub fn generate_timestamped_name(fname: &str, ext: &str) -> PathBuf {
69    let dt = UTC::now().format("%d_%m_%Y_%Hh%Mm%Ss");
70
71    if fname.is_empty() {
72        return PathBuf::from(format!("{}{}", dt, make_extension(ext)));
73    }
74
75    PathBuf::from(format!("{}_{}{}", fname, dt, make_extension(ext)))
76}
77
78/// Generates a random UUIDv4 `PathBuf`
79///
80/// # Example
81///
82/// ```rust
83/// use filetools::naming::generate_uuid4_name;
84///
85/// // Will generate a UUIDv4 name (e.g. `b1faa2c3-d25c-43bb-b578-9f259d7aabaf.log`)
86/// let name = generate_uuid4_name("log");
87/// ```
88pub fn generate_uuid4_name(ext: &str) -> PathBuf {
89    let unique = Uuid::new_v4();
90
91    PathBuf::from(format!("{}{}", unique, make_extension(ext)))
92}
93
94/// Generates a `PathBuf` from a `number` prefixed by `n_digits` zeros.
95///
96/// If `ext` is empty, will just return the filled number.
97///
98/// # Example
99///
100/// ```rust
101/// use filetools::naming::generate_n_digit_name;
102///
103/// // Will generate the name `0005.json`
104/// let name = generate_n_digit_name(5, 4, "json");
105///
106/// // Will generate the name `000128.log`
107/// let another_name = generate_n_digit_name(128, 6, "log");
108/// ```
109pub fn generate_n_digit_name(number: usize, fill: usize, ext: &str) -> PathBuf {
110    PathBuf::from(format!(
111        "{:0fill$}{}",
112        number,
113        make_extension(ext),
114        fill = fill
115    ))
116}
117
118#[cfg(test)]
119mod naming_tests {
120    use super::*;
121    use regex::Regex;
122    use std::path::PathBuf;
123
124    #[test]
125    fn generates_expected_name() {
126        assert_eq!(generate_name("test", "pdf"), PathBuf::from("test.pdf"));
127        assert_eq!(
128            generate_name("another", "txt"),
129            PathBuf::from("another.txt")
130        );
131        assert_eq!(generate_name("main", "c"), PathBuf::from("main.c"));
132        assert_eq!(generate_name("app", "js"), PathBuf::from("app.js"));
133        assert_eq!(
134            generate_name("somephotothing", "H4AC"),
135            PathBuf::from("somephotothing.H4AC")
136        );
137    }
138
139    #[test]
140    // Don't judge me on regex...
141    fn generates_timestamped_name_ok() {
142        let ts_re = Regex::new(r"(.*)_\d{2}_\d{2}_\d{4}_\d{2}h\d{2}m\d{2}s").unwrap();
143        let ts_name = generate_timestamped_name("with_filename", "txt");
144
145        // Pathbuf checks need the full path component
146        let ts_name = ts_name.to_str().unwrap();
147        assert!(ts_name.starts_with("with_filename"));
148        assert!(ts_re.is_match(ts_name));
149        assert!(ts_name.ends_with(".txt"));
150
151        let no_prefix_re = Regex::new(r"\d{2}_\d{2}_\d{4}_\d{2}h\d{2}m\d{2}s").unwrap();
152        let no_prefix = generate_timestamped_name("", "pdf");
153
154        let no_prefix = no_prefix.to_str().unwrap();
155        assert!(no_prefix.ends_with("pdf"));
156        assert!(no_prefix_re.is_match(no_prefix));
157    }
158
159    #[test]
160    fn checks_random_names_are_ok() {
161        let uuid_re =
162            Regex::new(r"[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}").unwrap();
163        let rn = generate_uuid4_name("json");
164        let rn_name = rn.to_str().unwrap();
165        assert!(uuid_re.is_match(rn_name));
166        assert!(rn_name.ends_with(".json"));
167    }
168}