2025-06-23 20:27:16 +02:00
|
|
|
use crate::errors::{NyxError, NyxResult};
|
2025-06-24 20:27:06 +02:00
|
|
|
use std::path::{Path, PathBuf};
|
2025-06-16 16:46:22 +02:00
|
|
|
|
2025-06-23 20:27:16 +02:00
|
|
|
/// Determine `<project-name, path/to/<project>.sqlite>`.
|
2025-06-24 20:27:06 +02:00
|
|
|
pub fn get_project_info(project_path: &Path, config_dir: &Path) -> NyxResult<(String, PathBuf)> {
|
|
|
|
|
let project_name = project_path
|
|
|
|
|
.file_name()
|
|
|
|
|
.and_then(|n| n.to_str())
|
|
|
|
|
.ok_or_else(|| NyxError::Other("Unable to determine project name".into()))?;
|
2025-06-23 20:27:16 +02:00
|
|
|
|
2025-06-24 20:27:06 +02:00
|
|
|
let db_name = sanitize_project_name(project_name);
|
2025-06-28 17:36:14 +02:00
|
|
|
let db_path = config_dir.join(format!("{db_name}.sqlite"));
|
2025-06-23 20:27:16 +02:00
|
|
|
|
2025-06-24 20:27:06 +02:00
|
|
|
Ok((project_name.to_owned(), db_path))
|
2025-06-16 16:46:22 +02:00
|
|
|
}
|
|
|
|
|
|
2025-06-23 20:27:16 +02:00
|
|
|
pub fn sanitize_project_name(name: &str) -> String {
|
2025-06-24 20:27:06 +02:00
|
|
|
name.to_lowercase()
|
|
|
|
|
.chars()
|
|
|
|
|
.map(|c| match c {
|
|
|
|
|
' ' | '\t' | '\n' | '\r' => '_',
|
|
|
|
|
c if c.is_alphanumeric() || c == '_' || c == '-' => c,
|
|
|
|
|
_ => '_',
|
|
|
|
|
})
|
|
|
|
|
.collect::<String>()
|
|
|
|
|
.split('_')
|
|
|
|
|
.filter(|s| !s.is_empty())
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join("_")
|
|
|
|
|
}
|
2025-06-24 23:18:01 +02:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn sanitize_project_name_is_idempotent_and_lossless_enough() {
|
2025-06-24 23:38:32 +02:00
|
|
|
let samples = [
|
|
|
|
|
("My Project", "my_project"),
|
|
|
|
|
("Hello-World", "hello-world"),
|
|
|
|
|
("mixed_case", "mixed_case"),
|
|
|
|
|
("tabs\tspaces\n", "tabs_spaces"),
|
|
|
|
|
(" multiple ", "multiple"),
|
|
|
|
|
("weird@$*chars", "weird_chars"),
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (input, expected) in samples {
|
2025-06-28 17:36:14 +02:00
|
|
|
assert_eq!(sanitize_project_name(input), expected, "input: {input}");
|
2025-06-24 23:38:32 +02:00
|
|
|
assert_eq!(sanitize_project_name(expected), expected);
|
|
|
|
|
}
|
2025-06-24 23:18:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn get_project_info_uses_sanitized_name_in_sqlite_path() {
|
2025-06-24 23:38:32 +02:00
|
|
|
let tmp = tempfile::tempdir().unwrap();
|
|
|
|
|
let root = tmp.path();
|
2025-06-24 23:18:01 +02:00
|
|
|
|
2025-06-24 23:38:32 +02:00
|
|
|
let project_dir = root.join("Example Project");
|
|
|
|
|
std::fs::create_dir(&project_dir).unwrap();
|
2025-06-24 23:18:01 +02:00
|
|
|
|
2025-06-24 23:38:32 +02:00
|
|
|
let (project_name, db_path) =
|
|
|
|
|
get_project_info(&project_dir, root).expect("should detect project");
|
2025-06-24 23:18:01 +02:00
|
|
|
|
2025-06-24 23:38:32 +02:00
|
|
|
assert_eq!(project_name, "Example Project");
|
|
|
|
|
assert_eq!(db_path, root.join("example_project.sqlite"));
|
|
|
|
|
}
|