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}