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";
12
13#[derive(Debug, thiserror::Error)]
14pub enum ArgsError {
15 #[error("Invalid arg value: {0}")]
16 TypesError(#[from] crate::types::Error),
17 #[error("Decoding error: {0}")]
18 DecodingError(String),
19 #[error("Arg not found: {0}")]
20 ArgNotFound(String),
21 #[error("Arg type not found: {0}")]
22 ArgTypeNotFound(String)
23}
24
25#[derive(Debug, PartialEq)]
27pub struct CommandArg {
28 pub name: String,
29 pub required: bool,
30 pub description: String,
31 pub ty: NamedCLType,
32 pub is_list_element: bool
33}
34
35impl CommandArg {
36 pub fn new(name: &str, description: &str, ty: NamedCLType) -> Self {
37 Self {
38 name: name.to_string(),
39 description: description.to_string(),
40 ty,
41 required: false,
42 is_list_element: false
43 }
44 }
45
46 pub fn required(self) -> Self {
47 Self {
48 required: true,
49 ..self
50 }
51 }
52
53 pub fn list(self) -> Self {
54 Self {
55 is_list_element: true,
56 ..self
57 }
58 }
59
60 pub(crate) fn split_name(&self) -> Vec<String> {
61 self.name
62 .split('.')
63 .map(|s| s.to_string())
64 .collect::<Vec<_>>()
65 }
66}
67
68impl From<CommandArg> for clap::Arg {
69 fn from(arg: CommandArg) -> Self {
70 let result = clap::Arg::new(&arg.name)
71 .long(arg.name)
72 .value_name(format!("{:?}", arg.ty))
73 .required(arg.required)
74 .value_parser(CLTypedParser::new(arg.ty))
75 .help(arg.description);
76
77 match arg.is_list_element {
78 true => result.action(ArgAction::Append),
79 false => result.action(ArgAction::Set)
80 }
81 }
82}
83
84pub enum Arg {
85 AttachedValue,
86 Gas,
87 Contracts,
88 EventsNumber,
89 PrintEvents
90}
91
92impl Arg {
93 pub fn name(&self) -> &str {
94 match self {
95 Arg::AttachedValue => ARG_ATTACHED_VALUE,
96 Arg::Gas => ARG_GAS,
97 Arg::Contracts => ARG_CONTRACTS,
98 Arg::EventsNumber => ARG_NUMBER,
99 Arg::PrintEvents => ARG_PRINT_EVENTS
100 }
101 }
102}
103
104impl From<Arg> for clap::Arg {
105 fn from(arg: Arg) -> Self {
106 match arg {
107 Arg::AttachedValue => arg_attached_value(),
108 Arg::Gas => arg_gas(),
109 Arg::Contracts => arg_contracts(),
110 Arg::EventsNumber => arg_number("Number of events to print"),
111 Arg::PrintEvents => arg_print_events()
112 }
113 }
114}
115
116fn arg_attached_value() -> clap::Arg {
117 clap::Arg::new(ARG_ATTACHED_VALUE)
118 .help("The amount of CSPRs attached to the call")
119 .long(ARG_ATTACHED_VALUE)
120 .required(false)
121 .value_name("CSPR")
122 .value_parser(CsprTokenAmountParser)
123 .action(ArgAction::Set)
124}
125
126fn arg_gas() -> clap::Arg {
127 clap::Arg::new(ARG_GAS)
128 .help("The amount of gas to attach to the call")
129 .long(ARG_GAS)
130 .required(true)
131 .value_name("CSPR")
132 .value_parser(GasParser)
133 .action(ArgAction::Set)
134}
135
136fn arg_contracts() -> clap::Arg {
137 clap::Arg::new(ARG_CONTRACTS)
138 .help("The path to the file with the deployed contracts. Relative to the project root.")
139 .long(ARG_CONTRACTS)
140 .short('c')
141 .required(false)
142 .value_name("PathBuf")
143 .value_parser(PathBufValueParser::new())
144 .action(ArgAction::Set)
145}
146
147fn arg_number(description: &'static str) -> clap::Arg {
148 clap::Arg::new(ARG_NUMBER)
149 .short('n')
150 .long(ARG_NUMBER)
151 .value_name("N")
152 .default_value("10")
153 .value_parser(clap::value_parser!(u32).range(1..50))
154 .help(description)
155}
156
157fn arg_print_events() -> clap::Arg {
158 clap::Arg::new(ARG_PRINT_EVENTS)
159 .long(ARG_PRINT_EVENTS)
160 .short('p')
161 .help("Print events emitted by the contract")
162 .action(ArgAction::SetTrue)
163}
164
165pub fn read_arg<T: ToOwned<Owned = T> + Any + Clone + Send + Sync + 'static>(
166 matches: &ArgMatches,
167 arg: Arg
168) -> Option<T> {
169 matches.get_one::<T>(arg.name()).map(ToOwned::to_owned)
170}