101 lines
2.9 KiB
Rust
101 lines
2.9 KiB
Rust
//! Pure project-selection logic — no I/O, no async, no side effects.
|
|
//!
|
|
//! All functions here are deterministic and depend only on their arguments.
|
|
|
|
/// Promote a project path to the front of the known-projects list.
|
|
///
|
|
/// Removes any existing occurrence of `path` and inserts it at position 0,
|
|
/// so the most-recently-opened project is always first.
|
|
pub fn promote_to_front(mut projects: Vec<String>, path: &str) -> Vec<String> {
|
|
projects.retain(|p| p != path);
|
|
projects.insert(0, path.to_string());
|
|
projects
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
/// Extract the display name for a project from its filesystem path.
|
|
///
|
|
/// Returns the last non-empty path component, or `None` for root or empty input.
|
|
pub fn project_name_from_path(path: &str) -> Option<&str> {
|
|
path.trim_end_matches('/')
|
|
.rsplit('/')
|
|
.find(|s| !s.is_empty())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn promote_to_front_inserts_new_path_at_position_zero() {
|
|
let result = promote_to_front(vec!["/a".to_string(), "/b".to_string()], "/c");
|
|
assert_eq!(result, vec!["/c", "/a", "/b"]);
|
|
}
|
|
|
|
#[test]
|
|
fn promote_to_front_moves_existing_entry_to_front() {
|
|
let result = promote_to_front(
|
|
vec!["/a".to_string(), "/b".to_string(), "/c".to_string()],
|
|
"/b",
|
|
);
|
|
assert_eq!(result, vec!["/b", "/a", "/c"]);
|
|
}
|
|
|
|
#[test]
|
|
fn promote_to_front_is_idempotent_when_already_first() {
|
|
let result = promote_to_front(vec!["/a".to_string(), "/b".to_string()], "/a");
|
|
assert_eq!(result, vec!["/a", "/b"]);
|
|
}
|
|
|
|
#[test]
|
|
fn promote_to_front_handles_empty_list() {
|
|
let result = promote_to_front(vec![], "/new");
|
|
assert_eq!(result, vec!["/new"]);
|
|
}
|
|
|
|
#[test]
|
|
fn promote_to_front_deduplicates_single_entry() {
|
|
let result = promote_to_front(vec!["/a".to_string()], "/a");
|
|
assert_eq!(result, vec!["/a"]);
|
|
}
|
|
|
|
#[test]
|
|
fn project_name_from_path_extracts_last_component() {
|
|
assert_eq!(
|
|
project_name_from_path("/home/user/myproject"),
|
|
Some("myproject")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn project_name_from_path_handles_trailing_slash() {
|
|
assert_eq!(
|
|
project_name_from_path("/home/user/myproject/"),
|
|
Some("myproject")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn project_name_from_path_returns_none_for_root() {
|
|
assert_eq!(project_name_from_path("/"), None);
|
|
}
|
|
|
|
#[test]
|
|
fn project_name_from_path_returns_none_for_empty() {
|
|
assert_eq!(project_name_from_path(""), None);
|
|
}
|
|
|
|
#[test]
|
|
fn project_name_from_path_handles_single_component() {
|
|
assert_eq!(project_name_from_path("myproject"), Some("myproject"));
|
|
}
|
|
|
|
#[test]
|
|
fn project_name_from_path_handles_deep_path() {
|
|
assert_eq!(
|
|
project_name_from_path("/a/b/c/d/project-name"),
|
|
Some("project-name")
|
|
);
|
|
}
|
|
}
|