Find the longest common directory prefix among a set of paths.
(paths: &[String])
| 628 | |
| 629 | /// Find the longest common directory prefix among a set of paths. |
| 630 | fn common_directory(paths: &[String]) -> String { |
| 631 | if paths.is_empty() { |
| 632 | return String::new(); |
| 633 | } |
| 634 | if paths.len() == 1 { |
| 635 | // Return the parent directory of the single file |
| 636 | return paths[0] |
| 637 | .rsplit_once('/') |
| 638 | .map(|(dir, _)| dir.to_string()) |
| 639 | .unwrap_or_default(); |
| 640 | } |
| 641 | |
| 642 | // Split all paths into components and find the common prefix |
| 643 | let components: Vec<Vec<&str>> = paths.iter().map(|p| p.split('/').collect()).collect(); |
| 644 | |
| 645 | let mut common = Vec::new(); |
| 646 | let min_len = components.iter().map(|c| c.len()).min().unwrap_or(0); |
| 647 | |
| 648 | for i in 0..min_len { |
| 649 | let first = components[0][i]; |
| 650 | if components.iter().all(|c| c[i] == first) { |
| 651 | common.push(first); |
| 652 | } else { |
| 653 | break; |
| 654 | } |
| 655 | } |
| 656 | |
| 657 | // Don't include the filename component — only directories |
| 658 | // If common has components and the last one matches a filename, pop it |
| 659 | if !common.is_empty() { |
| 660 | // If the common prefix equals one of the full paths, it includes |
| 661 | // the filename — pop the last component |
| 662 | let joined = common.join("/"); |
| 663 | if paths.iter().any(|p| p == &joined) { |
| 664 | common.pop(); |
| 665 | } |
| 666 | } |
| 667 | |
| 668 | common.join("/") |
| 669 | } |
| 670 | |
| 671 | /// Build edges connecting a decision node to the surrounding context. |
| 672 | /// |