cargo_rhack/
cmd.rs

1mod edit;
2mod undo;
3
4use std::fs;
5use std::path::PathBuf;
6use std::process::Command;
7use std::{env, str::FromStr};
8
9use anyhow::{anyhow, Result};
10use clap::{Parser, Subcommand};
11use serde_json::Value;
12use toml_edit::DocumentMut;
13
14pub use edit::Edit;
15pub use undo::Undo;
16
17const DEFAULT_RHACK_DIR_NAME: &str = ".rhack";
18const RHACK_DIR_ENV_KEY: &str = "RHACK_DIR";
19const PATCH_TABLE_NAME: &str = "patch";
20const REGISTRY_TABLE_NAME: &str = "crates-io";
21
22pub trait Cmd {
23    fn run(&self) -> Result<()>;
24}
25
26#[derive(Debug, Parser)]
27#[clap(about, bin_name = "cargo", author, version)]
28#[command(propagate_version = true)]
29pub struct Cli {
30    #[arg(hide = true)]
31    cargo: String,
32    #[command(subcommand)]
33    command: Commands,
34}
35
36#[derive(Subcommand, Debug)]
37pub enum Commands {
38    Edit(Edit),
39    Undo(Undo),
40}
41
42impl Cmd for Cli {
43    fn run(&self) -> Result<()> {
44        self.command.run()
45    }
46}
47
48impl Cmd for Commands {
49    fn run(&self) -> Result<()> {
50        match self {
51            Self::Edit(cmd) => cmd.run(),
52            Self::Undo(cmd) => cmd.run(),
53        }
54    }
55}
56
57// Gives back user-difined rhack dir path. If none, the default will be given.
58#[must_use]
59pub fn rhack_dir() -> PathBuf {
60    env::var(RHACK_DIR_ENV_KEY).map_or_else(
61        |_| {
62            let Some(home_dir) = home::home_dir() else {
63                panic!("failed to find home directory")
64            };
65            home_dir.join(DEFAULT_RHACK_DIR_NAME)
66        },
67        PathBuf::from,
68    )
69}
70
71// Gives back the the path to Cargo.toml reffered from the working directory.
72pub fn manifest_path() -> Result<PathBuf> {
73    // Run "cargo locate-project" to find out Cargo.toml file's location.
74    // See: https://doc.rust-lang.org/cargo/commands/cargo-locate-project.html
75    let out = Command::new("cargo")
76        .arg("locate-project")
77        .arg("--workspace")
78        .output();
79
80    let out = match out {
81        Ok(o) => o,
82        Err(err) => return Err(anyhow!("failed to run \"cargo locate-project\": {:#}", err)),
83    };
84    let out: Value = serde_json::from_slice(&out.stdout)?;
85    let Ok(path) = PathBuf::from_str(match out["root"].as_str() {
86        Some(it) => it,
87        None => return Err(anyhow!("could convert to path")),
88    }) else {
89        return Err(anyhow!("unexpected response from \"cargo locate-project\""));
90    };
91    Ok(path)
92}
93
94// Gives back the parsed Cargo.toml placed at the working directory.
95pub fn load_manifest(manifest_path: &PathBuf) -> Result<DocumentMut> {
96    let manifest = match fs::read_to_string(manifest_path) {
97        Ok(b) => b,
98        Err(err) => {
99            return Err(anyhow!(
100                "failed to read from {}: {:#}",
101                &manifest_path.display(),
102                err
103            ))
104        }
105    };
106    match manifest.parse::<DocumentMut>() {
107        Ok(m) => Ok(m),
108        Err(err) => Err(anyhow!(
109            "failed to parse {}: {:#}",
110            &manifest_path.display(),
111            err
112        )),
113    }
114}