Skip to main content

odra_cli/cmd/
args.rs

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/// A typed command argument.
34#[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}