1use std::{
2 fs::{self, File},
3 io::Write,
4 path::{Path, PathBuf},
5};
6
7use anyhow::{anyhow, format_err, Result};
8use clap::Parser;
9use log::{debug, info};
10use shank_gov_idl::{extract_idl, manifest::Manifest, ParseIdlOpts};
11
12pub const VERSION: &str = env!("CARGO_PKG_VERSION");
13
14#[derive(Debug, Parser)]
15#[clap(version = VERSION)]
16pub struct Opts {
17 #[clap(subcommand)]
18 pub command: Command,
19}
20
21#[derive(Debug, Parser)]
22pub enum Command {
23 Idl {
24 #[clap(short, long, default_value = "idl")]
26 out_dir: String,
27
28 #[clap(short = 'r', long)]
30 crate_root: Option<String>,
31
32 #[clap(short = 'p', long)]
34 program_id: Option<String>,
35 },
36}
37
38pub fn entry(opts: Opts) -> Result<()> {
39 match opts.command {
40 Command::Idl {
41 out_dir,
42 crate_root,
43 program_id,
44 } => idl(out_dir, crate_root, program_id),
45 }
46}
47
48pub fn try_resolve_path(p: Option<String>, label: &str) -> Result<PathBuf> {
49 let p = match p {
50 Some(crate_root) => Ok(Path::new(&crate_root).to_path_buf()),
51 None => {
52 debug!("No {} provided, assuming in current dir", label);
53 std::env::current_dir()
54 }
55 }?;
56
57 let p = if p.is_absolute() {
58 Ok(p)
59 } else {
60 debug!("{} is relative, resolving from current dir", label);
61 std::env::current_dir().map(|x| x.join(p))
62 }?;
63
64 Ok(p)
65}
66
67pub fn idl(
68 out_dir: String,
69 crate_root: Option<String>,
70 program_id: Option<String>,
71) -> Result<()> {
72 let crate_root = try_resolve_path(crate_root, "crate_root")?;
74 let out_dir = try_resolve_path(Some(out_dir), "out_dir")?;
75 fs::create_dir_all(&out_dir).map_err(|err| {
76 format_err!(
77 "Unable to create out_dir ({}), {}",
78 &out_dir.display(),
79 err
80 )
81 })?;
82
83 let cargo_toml = crate_root.join("Cargo.toml");
85 if !cargo_toml.exists() {
86 return Err(anyhow!(
87 "Did not find Cargo.toml at the path: {}",
88 crate_root.display()
89 ));
90 }
91 let manifest = Manifest::from_path(&cargo_toml)?;
92 let lib_rel_path = manifest
93 .lib_rel_path()
94 .ok_or(anyhow!("Program needs to be a lib"))?;
95
96 let lib_full_path_str = crate_root.join(lib_rel_path);
97 let lib_full_path =
98 lib_full_path_str.to_str().ok_or(anyhow!("Invalid Path"))?;
99
100 let opts = ParseIdlOpts {
102 program_address_override: program_id,
103 ..ParseIdlOpts::default()
104 };
105 let idl = extract_idl(lib_full_path, opts)?
106 .ok_or(anyhow!("No IDL could be extracted"))?;
107 let idl_json = idl.try_into_json()?;
108 let y = idl_json.split("],\n \"errors\": [").collect::<Vec<&str>>();
109 let mut final_json = y[0].to_string();
110
111 final_json.push_str("
112 ,
113 {
114 \"name\": \"UnixTimestamp\",
115 \"type\": {
116 \"kind\": \"alias\",
117 \"value\": \"i64\"
118 }
119 },
120 {
121 \"name\": \"Slot\",
122 \"type\": {
123 \"kind\": \"alias\",
124 \"value\": \"u64\"
125 }
126 }
127 ],
128 \"errors\": [");
129
130 final_json.push_str(y[1]);
131
132 let name = manifest.lib_name()?;
134 let idl_json_path = out_dir.join(format!("{}.json", name));
135 let mut idl_json_file = File::create(&idl_json_path)?;
136 info!("Writing IDL to {}", &idl_json_path.display());
137
138 idl_json_file.write_all(final_json.as_bytes())?;
139
140 Ok(())
141}