huskies: merge 606_story_extract_project_service
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
//! 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")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user