|
| 1 | +use clap::value_parser; |
| 2 | + |
| 3 | +use heck::ToKebabCase; |
| 4 | +use soroban_spec_tools::Spec; |
| 5 | +use std::collections::HashMap; |
| 6 | +use std::fmt::Debug; |
| 7 | +use std::path::PathBuf; |
| 8 | +use stellar_cli::{ |
| 9 | + config::{self, sc_address}, |
| 10 | + xdr::{self, ScSpecTypeDef}, |
| 11 | +}; |
| 12 | + |
| 13 | +#[derive(thiserror::Error, Debug)] |
| 14 | +pub enum Error { |
| 15 | + #[error("function {0} was not found in the contract")] |
| 16 | + FunctionNotFoundInContractSpec(String), |
| 17 | + #[error(transparent)] |
| 18 | + Xdr(#[from] xdr::Error), |
| 19 | + #[error(transparent)] |
| 20 | + StrVal(#[from] soroban_spec_tools::Error), |
| 21 | + #[error(transparent)] |
| 22 | + ScAddress(#[from] sc_address::Error), |
| 23 | + #[error(transparent)] |
| 24 | + Config(#[from] config::Error), |
| 25 | + // #[error("")] |
| 26 | + // HelpMessage(String), |
| 27 | +} |
| 28 | + |
| 29 | +pub fn build_custom_cmd(name: &str, spec: &Spec) -> Result<clap::Command, Error> { |
| 30 | + let func = spec |
| 31 | + .find_function(name) |
| 32 | + .map_err(|_| Error::FunctionNotFoundInContractSpec(name.to_string()))?; |
| 33 | + |
| 34 | + // Parse the function arguments |
| 35 | + let inputs_map = &func |
| 36 | + .inputs |
| 37 | + .iter() |
| 38 | + .map(|i| (i.name.to_utf8_string().unwrap(), i.type_.clone())) |
| 39 | + .collect::<HashMap<String, ScSpecTypeDef>>(); |
| 40 | + let name: &'static str = Box::leak(name.to_string().into_boxed_str()); |
| 41 | + let mut cmd = clap::Command::new(name) |
| 42 | + .no_binary_name(true) |
| 43 | + .term_width(300) |
| 44 | + .max_term_width(300); |
| 45 | + let kebab_name = name.to_kebab_case(); |
| 46 | + if kebab_name != name { |
| 47 | + cmd = cmd.alias(kebab_name); |
| 48 | + } |
| 49 | + let doc: &'static str = Box::leak(func.doc.to_utf8_string_lossy().into_boxed_str()); |
| 50 | + let long_doc: &'static str = Box::leak(arg_file_help(doc).into_boxed_str()); |
| 51 | + |
| 52 | + cmd = cmd.about(Some(doc)).long_about(long_doc); |
| 53 | + for (name, type_) in inputs_map { |
| 54 | + let mut arg = clap::Arg::new(name); |
| 55 | + let file_arg_name = fmt_arg_file_name(name); |
| 56 | + let mut file_arg = clap::Arg::new(&file_arg_name); |
| 57 | + arg = arg |
| 58 | + .long(name) |
| 59 | + .alias(name.to_kebab_case()) |
| 60 | + .num_args(1) |
| 61 | + .value_parser(clap::builder::NonEmptyStringValueParser::new()) |
| 62 | + .long_help(spec.doc(name, type_)?); |
| 63 | + |
| 64 | + file_arg = file_arg |
| 65 | + .long(&file_arg_name) |
| 66 | + .alias(file_arg_name.to_kebab_case()) |
| 67 | + .num_args(1) |
| 68 | + .hide(true) |
| 69 | + .value_parser(value_parser!(PathBuf)) |
| 70 | + .conflicts_with(name); |
| 71 | + |
| 72 | + if let Some(value_name) = spec.arg_value_name(type_, 0) { |
| 73 | + let value_name: &'static str = Box::leak(value_name.into_boxed_str()); |
| 74 | + arg = arg.value_name(value_name); |
| 75 | + } |
| 76 | + |
| 77 | + // Set up special-case arg rules |
| 78 | + arg = match type_ { |
| 79 | + ScSpecTypeDef::Bool => arg |
| 80 | + .num_args(0..1) |
| 81 | + .default_missing_value("true") |
| 82 | + .default_value("false") |
| 83 | + .num_args(0..=1), |
| 84 | + ScSpecTypeDef::Option(_val) => arg.required(false), |
| 85 | + ScSpecTypeDef::I256 | ScSpecTypeDef::I128 | ScSpecTypeDef::I64 | ScSpecTypeDef::I32 => { |
| 86 | + arg.allow_hyphen_values(true) |
| 87 | + } |
| 88 | + _ => arg, |
| 89 | + }; |
| 90 | + |
| 91 | + cmd = cmd.arg(arg); |
| 92 | + cmd = cmd.arg(file_arg); |
| 93 | + } |
| 94 | + Ok(cmd) |
| 95 | +} |
| 96 | + |
| 97 | +fn fmt_arg_file_name(name: &str) -> String { |
| 98 | + format!("{name}-file-path") |
| 99 | +} |
| 100 | + |
| 101 | +fn arg_file_help(docs: &str) -> String { |
| 102 | + format!( |
| 103 | + r"{docs} |
| 104 | +Usage Notes: |
| 105 | +Each arg has a corresponding --<arg_name>-file-path which is a path to a file containing the corresponding JSON argument. |
| 106 | +Note: The only types which aren't JSON are Bytes and BytesN, which are raw bytes" |
| 107 | + ) |
| 108 | +} |
0 commit comments