1#[derive(Debug, PartialEq, Eq)]
18pub enum RegisterOutcome {
19 Inserted,
21 AlreadyRegistered,
23 AnchorNotFound,
25 TargetMissing,
27 Skipped,
31}
32
33pub fn insert_chain_call_after_anchor(
46 contents: &mut String,
47 anchor_substring: &str,
48 new_call: &str,
49) -> bool {
50 let lines: Vec<&str> = contents.lines().collect();
51 let pos = lines
52 .iter()
53 .enumerate()
54 .rev()
55 .find(|(_, l)| l.contains(anchor_substring))
56 .map(|(i, _)| i);
57 let Some(i) = pos else {
58 return false;
59 };
60
61 let anchor = lines[i];
62 let anchor_ws: String = anchor.chars().take_while(|c| c.is_whitespace()).collect();
63 let trimmed = anchor.trim_start();
64 let indent = if trimmed.starts_with('.') {
65 anchor_ws
66 } else {
67 format!("{anchor_ws} ")
68 };
69 let new_line = format!("{indent}{new_call}");
70
71 let mut new_lines: Vec<String> = lines.iter().map(|s| (*s).to_string()).collect();
72 new_lines.insert(i + 1, new_line);
73 *contents = new_lines.join("\n");
74 if !contents.ends_with('\n') {
75 contents.push('\n');
76 }
77 true
78}
79
80pub fn insert_use_after_last_use(contents: &mut String, new_use: &str) -> bool {
84 let lines: Vec<&str> = contents.lines().collect();
85 let pos = lines
86 .iter()
87 .enumerate()
88 .rev()
89 .find(|(_, l)| l.trim_start().starts_with("use "))
90 .map(|(i, _)| i);
91 let Some(i) = pos else {
92 return false;
93 };
94
95 let mut new_lines: Vec<String> = lines.iter().map(|s| (*s).to_string()).collect();
96 new_lines.insert(i + 1, new_use.to_string());
97 *contents = new_lines.join("\n");
98 if !contents.ends_with('\n') {
99 contents.push('\n');
100 }
101 true
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn chain_insert_after_opener_indents_plus_four() {
110 let mut src = "bootstrap()\n .listen(addr)\n .await\n".to_string();
111 let inserted =
112 insert_chain_call_after_anchor(&mut src, "bootstrap()", ".module(foo::define())");
113 assert!(inserted);
114 assert!(
116 src.contains("\n .module(foo::define())\n .listen"),
117 "got: {src}"
118 );
119 }
120
121 #[test]
122 fn chain_insert_after_chain_element_keeps_same_indent() {
123 let mut src =
124 "bootstrap()\n .module(a::define())\n .listen(addr)\n .await\n".to_string();
125 let inserted = insert_chain_call_after_anchor(&mut src, ".module(", ".module(b::define())");
126 assert!(inserted);
127 assert!(
130 src.contains(" .module(a::define())\n .module(b::define())\n .listen"),
131 "got: {src}"
132 );
133 }
134
135 #[test]
136 fn chain_insert_returns_false_when_anchor_missing() {
137 let mut src = "fn main() {}\n".to_string();
138 let inserted = insert_chain_call_after_anchor(&mut src, "bootstrap()", ".module(x)");
139 assert!(!inserted);
140 assert_eq!(src, "fn main() {}\n");
141 }
142
143 #[test]
144 fn chain_insert_picks_last_anchor_when_multiple() {
145 let mut src =
146 "bootstrap()\n .module(a)\n .module(b)\n .listen(addr)\n".to_string();
147 insert_chain_call_after_anchor(&mut src, ".module(", ".module(c)");
148 assert!(
150 src.contains(" .module(b)\n .module(c)\n .listen"),
151 "got: {src}"
152 );
153 }
154
155 #[test]
156 fn use_insert_after_last_use_line() {
157 let mut src = "use std::sync::Arc;\nuse kick_rs::*;\n\nfn main() {}\n".to_string();
158 let inserted = insert_use_after_last_use(&mut src, "use foo::Foo;");
159 assert!(inserted);
160 assert!(
162 src.contains("use kick_rs::*;\nuse foo::Foo;\n\nfn main()"),
163 "got: {src}"
164 );
165 }
166
167 #[test]
168 fn use_insert_returns_false_when_no_use_lines() {
169 let mut src = "fn main() {}\n".to_string();
170 let inserted = insert_use_after_last_use(&mut src, "use foo::Foo;");
171 assert!(!inserted);
172 assert_eq!(src, "fn main() {}\n");
173 }
174}