1use super::{load_manifest, manifest_path, rhack_dir, Cmd, PATCH_TABLE_NAME, REGISTRY_TABLE_NAME};
2
3use std::collections::HashMap;
4use std::fs;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7
8use anyhow::{anyhow, Result};
9use clap::Parser;
10use serde::Deserialize;
11use toml_edit::{value, Item, Table};
12
13#[derive(Parser, Debug)]
15pub struct Edit {
16 crate_name: String,
17
18 #[clap(short, long)]
20 verbose: bool,
21}
22
23impl Cmd for Edit {
24 fn run(&self) -> Result<()> {
25 let src = self.crate_local_path()?;
27 let mut dst =
28 PathBuf::from(src.file_name().ok_or_else(|| {
29 anyhow!("failed to determine the file name of the source directory")
30 })?);
31
32 dst = rhack_dir().join(dst);
33
34 match self.copy_dir(&src, &dst) {
35 Ok(()) => (),
36 Err(err) => return Err(anyhow!("failed to copy {:?} to {:?}: {}", src, dst, err)),
37 }
38
39 self.update_manifest(&dst)?;
40 println!("{:?} => {:?}", &self.crate_name, dst);
41 Ok(())
42 }
43}
44
45impl Edit {
46 #[allow(unused_results)]
48 fn crate_local_path(&self) -> Result<PathBuf> {
49 #[derive(Deserialize)]
50 struct Metadata {
51 packages: Vec<Package>,
52 }
53
54 #[derive(Deserialize)]
55 struct Package {
56 name: String,
57 manifest_path: String,
58 }
59
60 let out = Command::new("cargo").arg("metadata").output();
61 let out = match out {
62 Ok(o) => o,
63 Err(err) => return Err(anyhow!("failed to run \"cargo metadata\": {:#}", err)),
64 };
65 let metadata: Metadata = serde_json::from_slice(&out.stdout)?;
66
67 let mut packages = HashMap::new();
68 for p in metadata.packages {
69 packages.insert(p.name, p.manifest_path);
71 }
72
73 let Some(manifest_path) = packages.get(&self.crate_name) else {
74 return Err(anyhow!("the given crate is not used in this project"));
75 };
76
77 let manifest_path = PathBuf::from(manifest_path);
78 let path = manifest_path
79 .parent()
80 .ok_or_else(|| anyhow!("failed to determine the parent of manifest"))?;
81
82 Ok(path.to_path_buf())
83 }
84
85 fn copy_dir<U: AsRef<Path>, V: AsRef<Path>>(
87 &self,
88 from: U,
89 to: V,
90 ) -> Result<(), std::io::Error> {
91 let mut stack = vec![PathBuf::from(from.as_ref())];
92
93 let output_root = PathBuf::from(to.as_ref());
95 let input_root = PathBuf::from(from.as_ref()).components().count();
96
97 while let Some(working_path) = stack.pop() {
98 self.debug(&format!("\nprocess: {:?}", &working_path));
99
100 let src: PathBuf = working_path.components().skip(input_root).collect();
102
103 let dest = if src.components().count() == 0 {
105 output_root.clone()
106 } else {
107 output_root.join(&src)
108 };
109 if fs::metadata(&dest).is_err() {
110 self.debug(&format!(" mkdir: {dest:?}"));
111 fs::create_dir_all(&dest)?;
112 }
113
114 for entry in fs::read_dir(working_path)? {
115 let entry = entry?;
116 let path = entry.path();
117 if path.is_dir() {
118 stack.push(path);
119 } else {
120 match path.file_name() {
121 Some(filename) => {
122 let dest_path = dest.join(filename);
123 self.debug(&format!(" copy: {:?} -> {:?}", &path, &dest_path));
124 _ = fs::copy(&path, &dest_path)?;
125 }
126 None => {
127 println!("failed to copy: {path:?}");
128 }
129 }
130 }
131 }
132 }
133
134 Ok(())
135 }
136
137 fn update_manifest(&self, new_path: &Path) -> Result<()> {
139 let manifest_path = match manifest_path() {
140 Ok(p) => p,
141 Err(err) => return Err(err),
142 };
143 let mut manifest = match load_manifest(&manifest_path) {
144 Ok(m) => m,
145 Err(err) => return Err(err),
146 };
147
148 if matches!(
150 manifest.get_key_value(format!("{PATCH_TABLE_NAME}.{REGISTRY_TABLE_NAME}").as_str()),
151 Some((_, Item::None))
152 ) {
153 let mut t = Table::new();
154 t.set_implicit(true);
155 t.fmt();
156
157 manifest[PATCH_TABLE_NAME] = Item::Table(t);
158 manifest[PATCH_TABLE_NAME][REGISTRY_TABLE_NAME] = Item::Table(Table::new());
159 };
160
161 manifest[PATCH_TABLE_NAME][REGISTRY_TABLE_NAME][&self.crate_name]["path"] = value(
162 new_path
163 .to_str()
164 .ok_or_else(|| anyhow!("failed to convert to str"))?,
165 );
166
167 match fs::write(&manifest_path, manifest.to_string()) {
168 Ok(()) => (),
169 Err(err) => {
170 return Err(anyhow!(
171 "failed to write to {}: {:#}",
172 &manifest_path.display(),
173 err
174 ))
175 }
176 }
177
178 Ok(())
179 }
180
181 fn debug(&self, msg: &str) {
182 if self.verbose {
183 println!("{msg}");
184 }
185 }
186}