cargo_rhack/cmd/
edit.rs

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/// Start hacking a crate
14#[derive(Parser, Debug)]
15pub struct Edit {
16    crate_name: String,
17
18    /// Verbose output.
19    #[clap(short, long)]
20    verbose: bool,
21}
22
23impl Cmd for Edit {
24    fn run(&self) -> Result<()> {
25        // Determine the destination directory and the source directory.
26        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    // Gives back the local path to the directory holding the given crate.
47    #[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            // FIXME: unused result
70            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    // Copy the given src to the given dst recursively.
86    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        // TODO: Delete dir if the destination dir already exists.
94        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            // Generate a relative path
101            let src: PathBuf = working_path.components().skip(input_root).collect();
102
103            // Create a destination if missing
104            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    //  Update [patch.crates-io] section in Cargo.toml
138    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        // Insert [patch.crates-io] table if it doesn't exist.
149        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}