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