1use std::any::Any;
2
3use crate::{
4 parser::{CLTypedParser, CsprTokenAmountParser, EnumCLParser, GasParser},
5 types
6};
7use clap::{builder::PathBufValueParser, ArgAction, ArgMatches};
8use odra::schema::casper_contract_schema::NamedCLType;
9
10pub const ARG_ATTACHED_VALUE: &str = "attached_value";
11pub const ARG_GAS: &str = "gas";
12pub const ARG_CONTRACTS: &str = "contracts-toml";
13pub const ARG_PRINT_EVENTS: &str = "print-events";
14pub const ARG_NUMBER: &str = "number";
15pub const ARG_JSON: &str = "json";
16pub const ARG_DEPLOY_MODE: &str = "deploy_mode";
17const ARG_DEPLOY_MODE_LONG: &str = "deploy-mode";
18
19pub const DEPLOY_MODE_OVERRIDE: &str = "override";
20pub const DEPLOY_MODE_ARCHIVE: &str = "archive";
21
22#[derive(Debug, thiserror::Error)]
23pub enum ArgsError {
24 #[error("Invalid arg value: {0}")]
25 TypesError(#[from] crate::types::Error),
26 #[error("Decoding error: {0}")]
27 DecodingError(String),
28 #[error("Arg not found: {0}")]
29 ArgNotFound(String),
30 #[error("Arg type not found: {0}")]
31 ArgTypeNotFound(String)
32}
33
34#[derive(Debug, PartialEq)]
36pub struct CommandArg {
37 pub name: String,
38 pub required: bool,
39 pub description: String,
40 pub ty: NamedCLType,
41 pub is_list_element: bool,
42 pub enum_variants: Option<Vec<(String, u16)>>
43}
44
45impl CommandArg {
46 pub fn new(name: &str, description: &str, ty: NamedCLType) -> Self {
47 Self {
48 name: name.to_string(),
49 description: description.to_string(),
50 ty,
51 required: false,
52 is_list_element: false,
53 enum_variants: None
54 }
55 }
56
57 pub fn required(self) -> Self {
58 Self {
59 required: true,
60 ..self
61 }
62 }
63
64 pub fn list(self) -> Self {
65 Self {
66 is_list_element: true,
67 ..self
68 }
69 }
70
71 pub fn with_enum_variants(self, variants: Vec<(String, u16)>) -> Self {
72 Self {
73 enum_variants: Some(variants),
74 ..self
75 }
76 }
77
78 pub(crate) fn split_name(&self) -> Vec<String> {
79 self.name
80 .split('.')
81 .map(|s| s.to_string())
82 .collect::<Vec<_>>()
83 }
84}
85
86impl From<CommandArg> for clap::Arg {
87 fn from(arg: CommandArg) -> Self {
88 let CommandArg {
89 name,
90 required,
91 description,
92 ty,
93 is_list_element,
94 enum_variants
95 } = arg;
96
97 let value_hint = if let Some(ref variants) = enum_variants {
98 types::format_variant_list(variants)
99 } else {
100 types::format_type_hint(&ty)
101 };
102
103 let base = clap::Arg::new(&name)
104 .long(name)
105 .value_name(value_hint)
106 .required(required)
107 .help(description);
108
109 let base = if let Some(variants) = enum_variants {
110 base.value_parser(EnumCLParser::new(variants))
111 } else {
112 base.value_parser(CLTypedParser::new(ty))
113 };
114
115 match is_list_element {
116 true => base.action(ArgAction::Append),
117 false => base.action(ArgAction::Set)
118 }
119 }
120}
121
122pub enum Arg {
123 AttachedValue,
124 Gas,
125 Contracts,
126 EventsNumber,
127 PrintEvents,
128 DeployMode,
129 Json
130}
131
132impl Arg {
133 pub fn name(&self) -> &str {
134 match self {
135 Arg::AttachedValue => ARG_ATTACHED_VALUE,
136 Arg::Gas => ARG_GAS,
137 Arg::Contracts => ARG_CONTRACTS,
138 Arg::EventsNumber => ARG_NUMBER,
139 Arg::PrintEvents => ARG_PRINT_EVENTS,
140 Arg::DeployMode => ARG_DEPLOY_MODE,
141 Arg::Json => ARG_JSON
142 }
143 }
144}
145
146impl From<Arg> for clap::Arg {
147 fn from(arg: Arg) -> Self {
148 match arg {
149 Arg::AttachedValue => arg_attached_value(),
150 Arg::Gas => arg_gas(),
151 Arg::Contracts => arg_contracts(),
152 Arg::EventsNumber => arg_number("Number of events to print"),
153 Arg::PrintEvents => arg_print_events(),
154 Arg::DeployMode => arg_deploy_mode(),
155 Arg::Json => arg_json()
156 }
157 }
158}
159
160fn arg_attached_value() -> clap::Arg {
161 clap::Arg::new(ARG_ATTACHED_VALUE)
162 .help("The amount of CSPRs attached to the call")
163 .long(ARG_ATTACHED_VALUE)
164 .required(false)
165 .value_name("AMOUNT (motes, or 'X.Y cspr')")
166 .value_parser(CsprTokenAmountParser)
167 .action(ArgAction::Set)
168}
169
170fn arg_gas() -> clap::Arg {
171 clap::Arg::new(ARG_GAS)
172 .help("The amount of gas to attach to the call")
173 .long(ARG_GAS)
174 .required(true)
175 .value_name("AMOUNT (motes, or 'X.Y cspr')")
176 .value_parser(GasParser)
177 .action(ArgAction::Set)
178}
179
180fn arg_contracts() -> clap::Arg {
181 clap::Arg::new(ARG_CONTRACTS)
182 .help("The path to the file with the deployed contracts. Relative to the project root.")
183 .long(ARG_CONTRACTS)
184 .short('c')
185 .required(false)
186 .value_name("PathBuf")
187 .value_parser(PathBufValueParser::new())
188 .action(ArgAction::Set)
189}
190
191fn arg_number(description: &'static str) -> clap::Arg {
192 clap::Arg::new(ARG_NUMBER)
193 .short('n')
194 .long(ARG_NUMBER)
195 .value_name("N")
196 .default_value("10")
197 .value_parser(clap::value_parser!(u32).range(1..50))
198 .help(description)
199}
200
201fn arg_print_events() -> clap::Arg {
202 clap::Arg::new(ARG_PRINT_EVENTS)
203 .long(ARG_PRINT_EVENTS)
204 .short('p')
205 .help("Print events emitted by the contract")
206 .action(ArgAction::SetTrue)
207}
208
209fn arg_deploy_mode() -> clap::Arg {
210 clap::Arg::new(ARG_DEPLOY_MODE)
211 .long(ARG_DEPLOY_MODE_LONG)
212 .help("Deployment mode strategy.")
213 .long_help(
214 "Deployment mode strategy:\n\
215 - default: Use existing contract if available, otherwise deploy a new one\n\
216 - override: Force redeploy, overwrite the existing contract configuration\n\
217 - archive: Redeploy contracts, archive the existing contract configuration and create a new one."
218 )
219 .value_name("MODE")
220 .required(false)
221 .default_value("default")
222 .value_parser(["default", DEPLOY_MODE_OVERRIDE, DEPLOY_MODE_ARCHIVE])
223 .action(ArgAction::Set)
224}
225
226fn arg_json() -> clap::Arg {
230 clap::Arg::new(ARG_JSON)
231 .long(ARG_JSON)
232 .help("Emit machine-readable JSON instead of human-readable text (read commands only)")
233 .global(true)
234 .action(ArgAction::SetTrue)
235}
236
237pub fn read_arg<T: ToOwned<Owned = T> + Any + Clone + Send + Sync + 'static>(
238 matches: &ArgMatches,
239 arg: Arg
240) -> Option<T> {
241 matches.get_one::<T>(arg.name()).map(ToOwned::to_owned)
242}