use std::path::PathBuf;
pub fn parse_worktree_output(output: &str) -> Vec<(PathBuf, String)> {
let blocks = split_into_worktree_blocks(output);
blocks
.into_iter()
.filter_map(parse_worktree_block)
.collect()
}
pub fn split_into_worktree_blocks(output: &str) -> Vec<Vec<&str>> {
let mut blocks = Vec::new();
let mut current_block = Vec::new();
for line in output.lines() {
if line.starts_with("worktree ") && !current_block.is_empty() {
blocks.push(current_block);
current_block = vec![line];
} else if !line.is_empty() {
current_block.push(line);
}
}
if !current_block.is_empty() {
blocks.push(current_block);
}
blocks
}
pub fn parse_worktree_block(block: Vec<&str>) -> Option<(PathBuf, String)> {
let path = block
.iter()
.find(|line| line.starts_with("worktree "))
.map(|line| PathBuf::from(line.trim_start_matches("worktree ")))?;
let branch = block
.iter()
.find(|line| line.starts_with("branch "))
.map(|line| line.trim_start_matches("branch refs/heads/").to_string())?;
Some((path, branch))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_worktree_output() {
let output = r#"worktree /home/user/project/.prodigy/worktrees/test-session
HEAD abc123def456
branch refs/heads/test-branch
worktree /home/user/project/.prodigy/worktrees/another-session
HEAD 789012ghi345
branch refs/heads/another-branch
worktree /home/user/project
HEAD xyz789mno123
branch refs/heads/main"#;
let entries = parse_worktree_output(output);
assert_eq!(entries.len(), 3);
assert_eq!(
entries[0].0,
PathBuf::from("/home/user/project/.prodigy/worktrees/test-session")
);
assert_eq!(entries[0].1, "test-branch");
assert_eq!(
entries[1].0,
PathBuf::from("/home/user/project/.prodigy/worktrees/another-session")
);
assert_eq!(entries[1].1, "another-branch");
assert_eq!(entries[2].0, PathBuf::from("/home/user/project"));
assert_eq!(entries[2].1, "main");
}
#[test]
fn test_parse_worktree_output_empty() {
let output = "";
let entries = parse_worktree_output(output);
assert_eq!(entries.len(), 0);
}
#[test]
fn test_parse_worktree_output_single_entry() {
let output = r#"worktree /path/to/worktree
HEAD abc123
branch refs/heads/feature"#;
let entries = parse_worktree_output(output);
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].0, PathBuf::from("/path/to/worktree"));
assert_eq!(entries[0].1, "feature");
}
#[test]
fn test_parse_worktree_output_missing_branch() {
let output = r#"worktree /path/to/worktree
HEAD abc123"#;
let entries = parse_worktree_output(output);
assert_eq!(entries.len(), 0);
}
#[test]
fn test_split_into_worktree_blocks() {
let output = r#"worktree /path/one
HEAD abc123
branch refs/heads/feature-one
worktree /path/two
HEAD def456
branch refs/heads/feature-two"#;
let blocks = split_into_worktree_blocks(output);
assert_eq!(blocks.len(), 2);
assert_eq!(blocks[0].len(), 3);
assert_eq!(blocks[0][0], "worktree /path/one");
assert_eq!(blocks[0][1], "HEAD abc123");
assert_eq!(blocks[0][2], "branch refs/heads/feature-one");
assert_eq!(blocks[1].len(), 3);
assert_eq!(blocks[1][0], "worktree /path/two");
}
#[test]
fn test_split_into_worktree_blocks_empty() {
let output = "";
let blocks = split_into_worktree_blocks(output);
assert_eq!(blocks.len(), 0);
}
#[test]
fn test_split_into_worktree_blocks_single() {
let output = r#"worktree /single/path
HEAD xyz789
branch refs/heads/main"#;
let blocks = split_into_worktree_blocks(output);
assert_eq!(blocks.len(), 1);
assert_eq!(blocks[0].len(), 3);
}
#[test]
fn test_parse_worktree_block_valid() {
let block = vec![
"worktree /test/path",
"HEAD abc123",
"branch refs/heads/test-branch",
];
let result = parse_worktree_block(block);
assert!(result.is_some());
let (path, branch) = result.unwrap();
assert_eq!(path, PathBuf::from("/test/path"));
assert_eq!(branch, "test-branch");
}
#[test]
fn test_parse_worktree_block_missing_path() {
let block = vec!["HEAD abc123", "branch refs/heads/test-branch"];
let result = parse_worktree_block(block);
assert!(result.is_none());
}
#[test]
fn test_parse_worktree_block_missing_branch() {
let block = vec!["worktree /test/path", "HEAD abc123"];
let result = parse_worktree_block(block);
assert!(result.is_none());
}
#[test]
fn test_parse_worktree_block_extra_fields() {
let block = vec![
"worktree /test/path",
"HEAD abc123",
"branch refs/heads/test-branch",
"extra field that should be ignored",
];
let result = parse_worktree_block(block);
assert!(result.is_some());
let (path, branch) = result.unwrap();
assert_eq!(path, PathBuf::from("/test/path"));
assert_eq!(branch, "test-branch");
}
}