1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::{
    fs::{self, File},
    io::Write,
    path::{Path, PathBuf},
};

use anyhow::{anyhow, format_err, Result};
use clap::Parser;
use log::{debug, info};
use shank_idl::{extract_idl, manifest::Manifest, ParseIdlOpts};

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

#[derive(Debug, Parser)]
#[clap(version = VERSION)]
pub struct Opts {
    #[clap(subcommand)]
    pub command: Command,
}

#[derive(Debug, Parser)]
pub enum Command {
    Idl {
        /// Output directory for the IDL JSON.
        #[clap(short, long, default_value = "idl")]
        out_dir: String,

        /// Output filename for the IDL JSON [default: <program crate name>.json,
        /// e.g. "my_program.json"]
        #[clap(long)]
        out_filename: Option<String>,

        /// Directory of program crate for which to generate the IDL.
        #[clap(short = 'r', long)]
        crate_root: Option<String>,

        /// Manually specify and override the address in the IDL
        #[clap(short = 'p', long)]
        program_id: Option<String>,
    },
}

pub fn entry(opts: Opts) -> Result<()> {
    match opts.command {
        Command::Idl {
            out_dir,
            out_filename,
            crate_root,
            program_id,
        } => idl(out_dir, out_filename, crate_root, program_id),
    }
}

pub fn try_resolve_path(p: Option<String>, label: &str) -> Result<PathBuf> {
    let p = match p {
        Some(crate_root) => Ok(Path::new(&crate_root).to_path_buf()),
        None => {
            debug!("No {} provided, assuming in current dir", label);
            std::env::current_dir()
        }
    }?;

    let p = if p.is_absolute() {
        Ok(p)
    } else {
        debug!("{} is relative, resolving from current dir", label);
        std::env::current_dir().map(|x| x.join(p))
    }?;

    Ok(p)
}

pub fn idl(
    out_dir: String,
    out_filename: Option<String>,
    crate_root: Option<String>,
    program_id: Option<String>,
) -> Result<()> {
    // Resolve input and output directories
    let crate_root = try_resolve_path(crate_root, "crate_root")?;
    let out_dir = try_resolve_path(Some(out_dir), "out_dir")?;
    fs::create_dir_all(&out_dir).map_err(|err| {
        format_err!(
            "Unable to create out_dir ({}), {}",
            &out_dir.display(),
            err
        )
    })?;

    // Resolve info about lib for which we generate IDL
    let cargo_toml = crate_root.join("Cargo.toml");
    if !cargo_toml.exists() {
        return Err(anyhow!(
            "Did not find Cargo.toml at the path: {}",
            crate_root.display()
        ));
    }
    let manifest = Manifest::from_path(&cargo_toml)?;
    let lib_rel_path = manifest
        .lib_rel_path()
        .ok_or(anyhow!("Program needs to be a lib"))?;

    let lib_full_path_str = crate_root.join(lib_rel_path);
    let lib_full_path =
        lib_full_path_str.to_str().ok_or(anyhow!("Invalid Path"))?;

    // Extract IDL and convert to JSON
    let opts = ParseIdlOpts {
        program_address_override: program_id,
        ..ParseIdlOpts::default()
    };
    let idl = extract_idl(lib_full_path, opts)?
        .ok_or(anyhow!("No IDL could be extracted"))?;
    let idl_json = idl.try_into_json()?;

    // Write to JSON file
    let out_filename = if let Some(out_filename) = out_filename {
        out_filename
    } else {
        format!("{}.json", manifest.lib_name()?)
    };
    let idl_json_path = out_dir.join(out_filename);
    let mut idl_json_file = File::create(&idl_json_path)?;
    info!("Writing IDL to {}", &idl_json_path.display());

    idl_json_file.write_all(idl_json.as_bytes())?;

    Ok(())
}