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