code_ranker_graph/
relativize.rs1use crate::level_graph::LevelGraph;
7use code_ranker_plugin_api::{attrs::AttrValue, edge::Edge, graph::Graph, node::Node};
8use std::cmp::Reverse;
9use std::collections::BTreeMap;
10use std::path::Path;
11
12pub fn relativize_level(level: &mut LevelGraph, target: &Path, roots: &BTreeMap<String, String>) {
19 let id_map = relativize_graph_inner(&mut level.nodes, &mut level.edges, target, roots);
20 for cycle in &mut level.cycles {
21 for n in &mut cycle.nodes {
22 if let Some(nn) = id_map.get(n) {
23 *n = nn.clone();
24 }
25 }
26 }
27}
28
29pub fn relativize_graph(graph: &mut Graph, target: &Path, roots: &BTreeMap<String, String>) {
34 relativize_graph_inner(&mut graph.nodes, &mut graph.edges, target, roots);
35}
36
37fn relativize_graph_inner(
38 nodes: &mut [Node],
39 edges: &mut [Edge],
40 target: &Path,
41 roots: &BTreeMap<String, String>,
42) -> BTreeMap<String, String> {
43 let mut id_map: BTreeMap<String, String> = BTreeMap::new();
44 for node in nodes.iter() {
45 if node.kind == "external" {
46 continue; }
48 let new_id = relativize_path(&node.id, target, roots);
49 if new_id != node.id {
50 id_map.insert(node.id.clone(), new_id);
51 }
52 }
53
54 for node in nodes.iter_mut() {
55 if let Some(new_id) = id_map.get(&node.id) {
56 node.id = new_id.clone();
57 }
58 if let Some(parent) = node.parent.as_mut()
59 && let Some(np) = id_map.get(parent)
60 {
61 *parent = np.clone();
62 }
63 if let Some(AttrValue::Str(p)) = node.attrs.get("path") {
64 let rel = relativize_path(p, target, roots);
65 if rel.is_empty() || rel == node.id {
66 node.attrs.remove("path");
67 } else {
68 node.attrs.insert("path".to_string(), AttrValue::Str(rel));
69 }
70 }
71 }
72 for edge in edges.iter_mut() {
73 if let Some(s) = id_map.get(&edge.source) {
74 edge.source = s.clone();
75 }
76 if let Some(t) = id_map.get(&edge.target) {
77 edge.target = t.clone();
78 }
79 }
80 id_map
81}
82
83fn relativize_path(path: &str, target: &Path, roots: &BTreeMap<String, String>) -> String {
84 if path.is_empty() {
85 return path.to_string();
86 }
87 let p = Path::new(path);
88 if let Ok(rel) = p.strip_prefix(target) {
89 return format!("{{target}}/{}", rel.to_string_lossy());
90 }
91 let mut sorted: Vec<_> = roots.iter().collect();
93 sorted.sort_by_key(|(_, root)| Reverse(root.len()));
94 for (name, root) in &sorted {
95 if let Ok(rel) = p.strip_prefix(root.as_str()) {
96 return format!("{{{name}}}/{}", rel.to_string_lossy());
97 }
98 }
99 path.to_string()
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::level_graph::CycleGroup;
106
107 #[test]
108 fn relativize_path_under_target_uses_token() {
109 let got = relativize_path("/p/src/main.rs", Path::new("/p"), &BTreeMap::new());
110 assert_eq!(got, "{target}/src/main.rs");
111 }
112
113 #[test]
114 fn relativize_path_longest_root_wins() {
115 let roots = BTreeMap::from([
116 ("home".to_string(), "/home/u".to_string()),
117 ("registry".to_string(), "/home/u/.cargo".to_string()),
118 ]);
119 let got = relativize_path("/home/u/.cargo/x.rs", Path::new("/p"), &roots);
120 assert_eq!(got, "{registry}/x.rs");
121 }
122
123 #[test]
124 fn relativize_level_rewrites_ids_edges_and_cycles() {
125 use code_ranker_plugin_api::edge::Edge;
126 let mut level = LevelGraph::default();
127 level.nodes.push(Node {
128 id: "/p/src/a.rs".into(),
129 kind: "file".into(),
130 name: "a.rs".into(),
131 parent: None,
132 attrs: Default::default(),
133 });
134 level.nodes.push(Node {
135 id: "ext:serde".into(),
136 kind: "external".into(),
137 name: "serde".into(),
138 parent: None,
139 attrs: Default::default(),
140 });
141 level.edges.push(Edge {
142 source: "/p/src/a.rs".into(),
143 target: "ext:serde".into(),
144 kind: "uses".into(),
145 line: None,
146 attrs: Default::default(),
147 });
148 level.cycles.push(CycleGroup {
149 kind: "mutual".into(),
150 nodes: vec!["/p/src/a.rs".into()],
151 });
152 relativize_level(&mut level, Path::new("/p"), &BTreeMap::new());
153 assert_eq!(level.nodes[0].id, "{target}/src/a.rs");
154 assert_eq!(level.nodes[1].id, "ext:serde");
155 assert_eq!(level.edges[0].source, "{target}/src/a.rs");
156 assert_eq!(level.edges[0].target, "ext:serde");
157 assert_eq!(level.cycles[0].nodes[0], "{target}/src/a.rs");
158 }
159}