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