Skip to main content

vtcode_commons/
slug.rs

1//! Human-readable slug generator for plan file names
2//!
3//! Generates memorable identifiers by combining random adjectives and nouns,
4//! producing slugs like "gentle-harbor" or "cosmic-wizard".
5//!
6//! Based on OpenCode's slug utility pattern for plan mode file naming.
7
8use rand::Rng;
9
10/// Adjectives for slug generation (30 options)
11const ADJECTIVES: &[&str] = &[
12    "brave", "calm", "clever", "cosmic", "crisp", "curious", "eager", "gentle", "glowing", "happy",
13    "hidden", "jolly", "kind", "lucky", "mighty", "misty", "neon", "nimble", "playful", "proud",
14    "quick", "quiet", "shiny", "silent", "stellar", "sunny", "swift", "tidy", "witty", "bright",
15];
16
17/// Nouns for slug generation (32 options)
18const NOUNS: &[&str] = &[
19    "cabin", "cactus", "canyon", "circuit", "comet", "eagle", "engine", "falcon", "forest",
20    "garden", "harbor", "island", "knight", "lagoon", "meadow", "moon", "mountain", "nebula",
21    "orchid", "otter", "panda", "pixel", "planet", "river", "rocket", "sailor", "squid", "star",
22    "tiger", "wizard", "wolf", "stream",
23];
24
25/// Create a human-readable slug by combining a random adjective with a random noun.
26///
27/// # Examples
28///
29/// ```
30/// use vtcode_commons::slug;
31///
32/// let slug = slug::create();
33/// // Returns something like "gentle-harbor", "cosmic-wizard", etc.
34/// assert!(slug.contains('-'));
35/// ```
36pub fn create() -> String {
37    let mut rng = rand::rng();
38    let adj_idx = rng.random_range(0..ADJECTIVES.len());
39    let noun_idx = rng.random_range(0..NOUNS.len());
40
41    format!("{}-{}", ADJECTIVES[adj_idx], NOUNS[noun_idx])
42}
43
44/// Create a timestamped slug with a human-readable suffix.
45///
46/// Format: `{timestamp_millis}-{adjective}-{noun}`
47///
48/// # Examples
49///
50/// ```
51/// use vtcode_commons::slug;
52///
53/// let slug = slug::create_timestamped();
54/// // Returns something like "1768330644696-gentle-harbor"
55/// ```
56pub fn create_timestamped() -> String {
57    let timestamp = std::time::SystemTime::now()
58        .duration_since(std::time::UNIX_EPOCH)
59        .map(|d| d.as_millis())
60        .unwrap_or(0);
61
62    format!("{}-{}", timestamp, create())
63}
64
65/// Create a slug with a custom prefix.
66///
67/// # Examples
68///
69/// ```
70/// use vtcode_commons::slug;
71///
72/// let slug = slug::create_with_prefix("plan");
73/// // Returns something like "plan-gentle-harbor"
74/// ```
75pub fn create_with_prefix(prefix: &str) -> String {
76    format!("{}-{}", prefix, create())
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn test_create_slug() {
85        let slug = create();
86        assert!(slug.contains('-'));
87        let parts: Vec<&str> = slug.split('-').collect();
88        assert_eq!(parts.len(), 2);
89        assert!(ADJECTIVES.contains(&parts[0]));
90        assert!(NOUNS.contains(&parts[1]));
91    }
92
93    #[test]
94    fn test_create_timestamped() {
95        let slug = create_timestamped();
96        let parts: Vec<&str> = slug.split('-').collect();
97        assert_eq!(parts.len(), 3);
98        assert!(parts[0].parse::<u128>().is_ok());
99    }
100
101    #[test]
102    fn test_create_with_prefix() {
103        let slug = create_with_prefix("plan");
104        assert!(slug.starts_with("plan-"));
105        let parts: Vec<&str> = slug.split('-').collect();
106        assert_eq!(parts.len(), 3);
107        assert_eq!(parts[0], "plan");
108    }
109
110    #[test]
111    fn test_uniqueness() {
112        let slugs: Vec<String> = (0..100).map(|_| create()).collect();
113        let unique_count = slugs.iter().collect::<std::collections::HashSet<_>>().len();
114        assert!(unique_count > 50, "Expected mostly unique slugs");
115    }
116}