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#[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
71pub fn manifest_path() -> Result<PathBuf> {
73 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
94pub 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}