1use std::path::Path;
2use std::path::PathBuf;
3use std::process::Command;
4
5use owo_colors::OwoColorize;
6use rootcause::report;
7
8const BINS: &[&str] = &[
11 "agg",
12 "catl",
13 "fkr",
14 "gbm",
15 "gch",
16 "gcu",
17 "ghl",
18 "idt",
19 "muxr",
20 "muxr-server",
21 "oe",
22 "rmr",
23 "frs",
24 "strgci",
25 "tec",
26 "try",
27 "vpg",
28 "yghfl",
29 "yhfp",
30];
31const REMOVED_BINS: &[&str] = &["ags", "aiya"];
33const LIBS: &[(&str, &str)] = &[("libnvrim.dylib", "nvrim.so")];
35const BINS_DEFAULT_PATH: &[&str] = &[".local", "bin"];
37const NVIM_LIBS_DEFAULT_PATH: &[&str] = &[".config", "nvim", "lua"];
39
40pub fn run(args: &mut Vec<String>) -> rootcause::Result<()> {
41 let is_debug = drop_element(args, "--debug");
42 let bins_path = args.first().cloned().map_or_else(
43 || ytil_sys::dir::build_home_path(BINS_DEFAULT_PATH),
44 |supplied_bins_path| Ok(PathBuf::from(supplied_bins_path)),
45 )?;
46 let cargo_target_path = args.get(1).cloned().map_or_else(
47 || {
48 std::env::var("CARGO_MANIFEST_DIR").map(|cargo_manifest_dir| {
49 let mut x = PathBuf::from(cargo_manifest_dir);
50 remove_last_n_dirs(&mut x, 2);
51 x.join("target")
52 })
53 },
54 |x| Ok(PathBuf::from(x)),
55 )?;
56 let nvim_libs_path = args.get(2).cloned().map_or_else(
57 || ytil_sys::dir::build_home_path(NVIM_LIBS_DEFAULT_PATH),
58 |supplied_nvim_libs_path| Ok(PathBuf::from(supplied_nvim_libs_path)),
59 )?;
60
61 let (cargo_target_location, build_profile) = if is_debug {
62 (cargo_target_path.join("debug"), None)
63 } else {
64 (cargo_target_path.join("release"), Some("--release"))
65 };
66
67 ytil_cmd::silent_cmd("cargo").args(["fmt"]).status()?.exit_ok()?;
68
69 if !is_debug {
71 ytil_cmd::silent_cmd("cargo")
72 .args(["clippy", "--all-targets", "--all-features", "--", "-D", "warnings"])
73 .status()?
74 .exit_ok()?;
75 }
76
77 ytil_cmd::silent_cmd("cargo")
78 .args([Some("build"), build_profile].into_iter().flatten())
79 .status()?
80 .exit_ok()?;
81
82 for bin in REMOVED_BINS {
83 remove_legacy_bin(&bins_path.join(bin))?;
84 }
85
86 for bin in BINS {
87 cp(&cargo_target_location.join(bin), &bins_path.join(bin))?;
88 }
89
90 for (source_lib_name, target_lib_name) in LIBS {
91 cp(
92 &cargo_target_location.join(source_lib_name),
93 &nvim_libs_path.join(target_lib_name),
94 )?;
95 }
96
97 Command::new(bins_path.join("gbm")).arg("install").status()?.exit_ok()?;
98
99 Ok(())
100}
101
102fn remove_last_n_dirs(path: &mut PathBuf, n: usize) {
104 for _ in 0..n {
105 if !path.pop() {
106 return;
107 }
108 }
109}
110
111fn drop_element<T, U: ?Sized>(vec: &mut Vec<T>, target: &U) -> bool
114where
115 T: PartialEq<U>,
116{
117 let Some(idx) = vec.iter().position(|x| x == target) else {
118 return false;
119 };
120 vec.swap_remove(idx);
121 true
122}
123
124fn remove_legacy_bin(path: &Path) -> rootcause::Result<()> {
125 let metadata = match std::fs::symlink_metadata(path) {
126 Ok(metadata) => metadata,
127 Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
128 Err(error) => {
129 return Err(
130 report!("cannot inspect legacy binary").attach(format!("path={} error={error}", path.display()))
131 );
132 }
133 };
134 let file_type = metadata.file_type();
135 if !file_type.is_file() && !file_type.is_symlink() {
136 return Err(report!("refusing to remove non-file legacy binary").attach(format!("path={}", path.display())));
137 }
138
139 std::fs::remove_file(path).map_err(|error| {
140 report!("cannot remove legacy binary").attach(format!("path={} error={error}", path.display()))
141 })?;
142 println!("{} {}", "Removed".red().bold(), path.display());
143 Ok(())
144}
145
146fn cp(from: &Path, to: &Path) -> rootcause::Result<()> {
153 ytil_sys::file::atomic_cp(from, to)?;
154 println!("{} {} to {}", "Copied".green().bold(), from.display(), to.display());
155 Ok(())
156}
157
158#[cfg(test)]
159mod tests {
160 use std::path::PathBuf;
161
162 use rstest::rstest;
163 use test_that::prelude::*;
164
165 use crate::local::drop_element;
166 use crate::local::remove_last_n_dirs;
167 use crate::local::remove_legacy_bin;
168
169 #[test]
170 fn test_drop_element_returns_true_and_removes_the_element_from_the_vec() {
171 let mut input = vec![42, 7];
172 assert_that!(drop_element(&mut input, &7), eq(true));
173 assert_eq!(input, vec![42]);
174 }
175
176 #[test]
177 fn test_drop_element_returns_false_and_does_nothing_to_a_non_empty_vec() {
178 let mut input = vec![42, 7];
179 assert_that!(drop_element(&mut input, &3), eq(false));
180 assert_eq!(input, vec![42, 7]);
181 }
182
183 #[test]
184 fn test_drop_element_returns_false_and_does_nothing_to_an_empty_vec() {
185 let mut input: Vec<usize> = vec![];
186 assert_that!(drop_element(&mut input, &3), eq(false));
187 assert_that!(input, empty());
188 }
189
190 #[rstest]
191 #[case::no_dirs_removed(PathBuf::from("/home/user/docs"), 0, PathBuf::from("/home/user/docs"))]
192 #[case::remove_one_dir(PathBuf::from("/home/user/docs"), 1, PathBuf::from("/home/user"))]
193 #[case::remove_more_than_exist(PathBuf::from("/home/user"), 5, PathBuf::from("/"))]
194 #[case::root_path(PathBuf::from("/"), 1, PathBuf::from("/"))]
195 #[case::empty_path(PathBuf::new(), 1, PathBuf::new())]
196 fn test_remove_last_n_dirs_when_requested_count_varies_updates_path(
197 #[case] mut initial: PathBuf,
198 #[case] n: usize,
199 #[case] expected: PathBuf,
200 ) {
201 remove_last_n_dirs(&mut initial, n);
202 assert_that!(initial, eq(expected));
203 }
204
205 #[rstest]
206 #[case::missing(false)]
207 #[case::file(true)]
208 fn test_remove_legacy_bin_handles_missing_and_file_paths(#[case] create_file: bool) {
209 let dir = tempfile::tempdir().expect("tempdir should be created");
210 let path = dir.path().join("ags");
211 if create_file {
212 std::fs::write(&path, "legacy binary").expect("legacy binary should be created");
213 }
214
215 assert_that!(remove_legacy_bin(&path), ok(eq(())));
216 assert_that!(path.exists(), eq(false));
217 }
218
219 #[test]
220 fn test_remove_legacy_bin_refuses_directory() {
221 let dir = tempfile::tempdir().expect("tempdir should be created");
222 let path = dir.path().join("aiya");
223 std::fs::create_dir(&path).expect("legacy directory should be created");
224
225 assert_that!(remove_legacy_bin(&path), err(anything()));
226 assert_that!(path.is_dir(), eq(true));
227 }
228}