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_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(long)]
31 out_filename: Option<String>,
32
33 #[clap(short = 'r', long)]
35 crate_root: Option<String>,
36
37 #[clap(short = 'p', long)]
39 program_id: Option<String>,
40 },
41}
42
43pub fn entry(opts: Opts) -> Result<()> {
44 match opts.command {
45 Command::Idl {
46 out_dir,
47 out_filename,
48 crate_root,
49 program_id,
50 } => idl(out_dir, out_filename, crate_root, program_id),
51 }
52}
53
54pub fn try_resolve_path(p: Option<String>, label: &str) -> Result<PathBuf> {
55 let p = match p {
56 Some(crate_root) => Ok(Path::new(&crate_root).to_path_buf()),
57 None => {
58 debug!("No {} provided, assuming in current dir", label);
59 std::env::current_dir()
60 }
61 }?;
62
63 let p = if p.is_absolute() {
64 Ok(p)
65 } else {
66 debug!("{} is relative, resolving from current dir", label);
67 std::env::current_dir().map(|x| x.join(p))
68 }?;
69
70 Ok(p)
71}
72
73pub fn idl(
74 out_dir: String,
75 out_filename: Option<String>,
76 crate_root: Option<String>,
77 program_id: Option<String>,
78) -> Result<()> {
79 let crate_root = try_resolve_path(crate_root, "crate_root")?;
81 let out_dir = try_resolve_path(Some(out_dir), "out_dir")?;
82 fs::create_dir_all(&out_dir).map_err(|err| {
83 format_err!(
84 "Unable to create out_dir ({}), {}",
85 &out_dir.display(),
86 err
87 )
88 })?;
89
90 let cargo_toml = crate_root.join("Cargo.toml");
92 if !cargo_toml.exists() {
93 return Err(anyhow!(
94 "Did not find Cargo.toml at the path: {}",
95 crate_root.display()
96 ));
97 }
98 let manifest = Manifest::from_path(&cargo_toml)?;
99 let lib_rel_path = manifest
100 .lib_rel_path()
101 .ok_or(anyhow!("Program needs to be a lib"))?;
102
103 let lib_full_path_str = crate_root.join(lib_rel_path);
104 let lib_full_path =
105 lib_full_path_str.to_str().ok_or(anyhow!("Invalid Path"))?;
106
107 let opts = ParseIdlOpts {
109 program_address_override: program_id,
110 ..ParseIdlOpts::default()
111 };
112 let idl = extract_idl(lib_full_path, opts)?
113 .ok_or(anyhow!("No IDL could be extracted"))?;
114 let idl_json = idl.try_into_json()?;
115
116 let out_filename = if let Some(out_filename) = out_filename {
118 out_filename
119 } else {
120 format!("{}.json", manifest.lib_name()?)
121 };
122 let idl_json_path = out_dir.join(out_filename);
123 let mut idl_json_file = File::create(&idl_json_path)?;
124 info!("Writing IDL to {}", &idl_json_path.display());
125
126 idl_json_file.write_all(idl_json.as_bytes())?;
127
128 Ok(())
129}