use std::path::Path;
use uuid::Uuid;
const ADJECTIVES: &[&str] = &[
"quiet", "brave", "silent", "swift", "calm", "bold", "bright", "clever", "gentle", "happy",
"keen", "lively", "merry", "noble", "proud", "rapid", "sharp", "sleek", "steady", "sunny",
"warm", "wise", "agile", "amber", "azure", "crisp", "deep", "eager", "fancy", "fierce",
"frosty", "golden", "humble", "icy", "jolly", "lucky", "mellow", "mighty", "misty", "nimble",
"polar", "royal", "rustic", "shiny", "snowy", "solar", "stark", "vivid", "witty", "zesty",
];
const NOUNS: &[&str] = &[
"falcon", "river", "crane", "otter", "maple", "comet", "harbor", "ember", "willow", "canyon",
"meadow", "boulder", "cedar", "delta", "fjord", "glade", "harvest", "island", "jungle",
"lagoon", "marsh", "nebula", "oasis", "pine", "quartz", "ridge", "summit", "tundra", "valley",
"anchor", "badger", "cobra", "dolphin", "eagle", "ferret", "gibbon", "heron", "ibis", "jaguar",
"koala", "lynx", "marten", "newt", "osprey", "puffin", "raven", "sparrow", "tiger", "viper",
"walrus",
];
const PREFIX: &str = "tmpm-";
pub fn name_from_uuid(uuid: &Uuid) -> String {
let value = uuid.as_u128();
let adj = ADJECTIVES[(value % ADJECTIVES.len() as u128) as usize];
let noun = NOUNS[((value / ADJECTIVES.len() as u128) % NOUNS.len() as u128) as usize];
format!("{PREFIX}{adj}-{noun}")
}
const MAX_FOLDER_LEN: usize = 20;
const DIR_FALLBACK: &str = "tmpm-session";
pub fn name_from_dir(path: &Path) -> String {
let basename = path
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default();
let mut slug = String::with_capacity(basename.len());
for ch in basename.chars() {
if ch.is_ascii_alphanumeric() {
slug.extend(ch.to_lowercase());
} else {
slug.push('-');
}
}
let mut collapsed = String::with_capacity(slug.len());
let mut prev_dash = true; for ch in slug.chars() {
if ch == '-' {
if !prev_dash {
collapsed.push('-');
}
prev_dash = true;
} else {
collapsed.push(ch);
prev_dash = false;
}
}
while collapsed.ends_with('-') {
collapsed.pop();
}
if collapsed.len() > MAX_FOLDER_LEN {
collapsed.truncate(MAX_FOLDER_LEN);
while collapsed.ends_with('-') {
collapsed.pop();
}
}
if collapsed.is_empty() {
DIR_FALLBACK.to_string()
} else {
format!("{PREFIX}{collapsed}")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deterministic() {
let id = Uuid::parse_str("367c6c51-1025-419c-b6d6-be9a753e8914").unwrap();
assert_eq!(name_from_uuid(&id), name_from_uuid(&id));
}
#[test]
fn format_matches() {
for _ in 0..200 {
let name = name_from_uuid(&Uuid::new_v4());
let rest = name.strip_prefix("tmpm-").expect("tmpm- prefix");
let mut parts = rest.split('-');
let adj = parts.next().expect("adjective");
let noun = parts.next().expect("noun");
assert!(parts.next().is_none(), "exactly two words: {name}");
assert!(ADJECTIVES.contains(&adj), "adjective from list: {adj}");
assert!(NOUNS.contains(&noun), "noun from list: {noun}");
assert!(name.len() <= 25, "name under 25 chars: {name}");
}
}
#[test]
fn distinct_uuids_distinct_names() {
let mut names = std::collections::HashSet::new();
for _ in 0..500 {
names.insert(name_from_uuid(&Uuid::new_v4()));
}
assert!(
names.len() > 400,
"expected mostly-distinct names: {}",
names.len()
);
}
#[test]
fn known_uuid_is_stable() {
assert_eq!(name_from_uuid(&Uuid::nil()), "tmpm-quiet-falcon");
}
#[test]
fn name_from_dir_basic() {
assert_eq!(
name_from_dir(Path::new("/Users/masa/Projects/trusty-mpm")),
"tmpm-trusty-mpm"
);
}
#[test]
fn name_from_dir_sanitizes() {
assert_eq!(
name_from_dir(Path::new("/home/foo/my project")),
"tmpm-my-project"
);
assert_eq!(name_from_dir(Path::new("/srv/my_api_v2")), "tmpm-my-api-v2");
assert_eq!(name_from_dir(Path::new("/x/MixedCase")), "tmpm-mixedcase");
}
#[test]
fn name_from_dir_collapses_and_trims() {
assert_eq!(
name_from_dir(Path::new("/x/--weird__ name--")),
"tmpm-weird-name"
);
assert_eq!(name_from_dir(Path::new("/x/...dots...")), "tmpm-dots");
}
#[test]
fn name_from_dir_truncates() {
let name = name_from_dir(Path::new("/x/this-is-a-very-long-folder-name"));
let slug = name.strip_prefix("tmpm-").expect("tmpm- prefix");
assert!(slug.len() <= 20, "slug under 20 chars: {slug}");
assert!(!slug.ends_with('-'), "no trailing dash: {slug}");
assert_eq!(name, "tmpm-this-is-a-very-long");
}
#[test]
fn name_from_dir_empty_fallback() {
assert_eq!(name_from_dir(Path::new("/")), "tmpm-session");
assert_eq!(name_from_dir(Path::new("/x/----")), "tmpm-session");
assert_eq!(name_from_dir(Path::new("")), "tmpm-session");
}
}