1use std::str::FromStr;
2
3use clap::{error::ErrorKind, CommandFactory, FromArgMatches, Parser};
4
5use crate::{config, print::Print, utils::deprecate_message};
6
7pub mod cache;
8pub mod cfg;
9pub mod completion;
10pub mod container;
11pub mod contract;
12pub mod doctor;
13pub mod env;
14pub mod events;
15pub mod fee_stats;
16pub mod fees;
17pub mod global;
18pub mod keys;
19pub mod ledger;
20pub mod message;
21pub mod network;
22pub mod plugin;
23pub mod snapshot;
24pub mod tx;
25pub mod version;
26
27pub mod txn_result;
28
29pub const HEADING_RPC: &str = "Options (RPC)";
30pub const HEADING_ARCHIVE: &str = "Options (Archive)";
31pub const HEADING_GLOBAL: &str = "Options (Global)";
32const ABOUT: &str =
33 "Work seamlessly with Stellar accounts, contracts, and assets from the command line.
34
35- Generate and manage keys and accounts
36- Build, deploy, and interact with contracts
37- Deploy asset contracts
38- Stream events
39- Start local testnets
40- Decode, encode XDR
41- More!
42
43For additional information see:
44
45- Stellar Docs: https://developers.stellar.org
46- Smart Contract Docs: https://developers.stellar.org/docs/build/smart-contracts/overview
47- CLI Docs: https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli";
48
49const LONG_ABOUT: &str = "
51
52To get started generate a new identity:
53
54 stellar keys generate alice
55
56Use keys with the `--source` flag in other commands.
57
58Commands that work with contracts are organized under the `contract` subcommand. List them:
59
60 stellar contract --help
61
62Use contracts like a CLI:
63
64 stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- --help
65
66Anything after the `--` double dash (the \"slop\") is parsed as arguments to the contract-specific CLI, generated on-the-fly from the contract schema. For the hello world example, with a function called `hello` that takes one string argument `to`, here's how you invoke it:
67
68 stellar contract invoke --id CCR6QKTWZQYW6YUJ7UP7XXZRLWQPFRV6SWBLQS4ZQOSAF4BOUD77OTE2 --source alice --network testnet -- hello --to world
69";
70
71#[derive(Parser, Debug)]
72#[command(
73 name = "stellar",
74 about = ABOUT,
75 version = version::long(),
76 long_about = ABOUT.to_string() + LONG_ABOUT,
77 disable_help_subcommand = true,
78)]
79pub struct Root {
80 #[clap(flatten)]
81 pub global_args: global::Args,
82
83 #[command(subcommand)]
84 pub cmd: Cmd,
85}
86
87impl Root {
88 pub fn new() -> Result<Self, Error> {
89 Self::try_parse().map_err(|e| {
90 if std::env::args().any(|s| s == "--list") {
91 let print = Print::new(std::env::args().any(|s| s == "--quiet" || s == "-q"));
92 deprecate_message(print, "--list", "Use `stellar plugin ls` instead.");
93 let _ = plugin::ls::Cmd.run();
94 std::process::exit(0);
95 }
96
97 match e.kind() {
98 ErrorKind::InvalidSubcommand => match plugin::default::run() {
99 Ok(()) => Error::Clap(e),
100 Err(e) => Error::PluginDefault(e),
101 },
102 _ => Error::Clap(e),
103 }
104 })
105 }
106
107 pub fn from_arg_matches<I, T>(itr: I) -> Result<Self, clap::Error>
108 where
109 I: IntoIterator<Item = T>,
110 T: Into<std::ffi::OsString> + Clone,
111 {
112 Self::from_arg_matches_mut(&mut Self::command().get_matches_from(itr))
113 }
114
115 pub async fn run(&mut self) -> Result<(), Error> {
116 let print = Print::new(self.global_args.quiet);
117
118 if self.global_args.locator.global {
119 deprecate_message(
120 print,
121 "--global",
122 "Global configuration is now the default behavior.",
123 );
124 }
125
126 match &mut self.cmd {
127 Cmd::Completion(completion) => completion.run(),
128 Cmd::Plugin(plugin) => plugin.run(&self.global_args).await?,
129 Cmd::Contract(contract) => contract.run(&self.global_args).await?,
130 Cmd::Doctor(doctor) => doctor.run(&self.global_args).await?,
131 Cmd::Config(config) => config.run()?,
132 Cmd::Events(events) => events.run().await?,
133 Cmd::Xdr(xdr) => xdr.run()?,
134 Cmd::Strkey(strkey) => strkey.run()?,
135 Cmd::Network(network) => network.run(&self.global_args).await?,
136 Cmd::Container(container) => container.run(&self.global_args).await?,
137 Cmd::Snapshot(snapshot) => snapshot.run(&self.global_args).await?,
138 Cmd::Version(version) => version.run(),
139 Cmd::Keys(id) => id.run(&self.global_args).await?,
140 Cmd::Tx(tx) => tx.run(&self.global_args).await?,
141 Cmd::Ledger(ledger) => ledger.run(&self.global_args).await?,
142 Cmd::Message(message) => message.run(&self.global_args).await?,
143 Cmd::Cache(cache) => cache.run()?,
144 Cmd::Env(env) => env.run(&self.global_args)?,
145 Cmd::Fees(env) => env.run(&self.global_args).await?,
146 Cmd::FeeStats(env) => env.run(&self.global_args).await?,
147 }
148 Ok(())
149 }
150}
151
152impl FromStr for Root {
153 type Err = clap::Error;
154
155 fn from_str(s: &str) -> Result<Self, Self::Err> {
156 Self::from_arg_matches(s.split_whitespace())
157 }
158}
159
160#[derive(Parser, Debug)]
161pub enum Cmd {
162 #[command(subcommand)]
164 Contract(contract::Cmd),
165
166 Doctor(doctor::Cmd),
168
169 Events(events::Cmd),
171
172 Env(env::Cmd),
181
182 #[command(subcommand)]
184 Keys(keys::Cmd),
185
186 #[command(subcommand)]
188 Network(network::Cmd),
189
190 #[command(subcommand)]
192 Container(container::Cmd),
193
194 #[command(subcommand)]
196 Config(cfg::Cmd),
197
198 #[command(subcommand)]
200 Snapshot(snapshot::Cmd),
201
202 #[command(subcommand)]
204 Tx(tx::Cmd),
205
206 Xdr(stellar_xdr::cli::Root),
208
209 Strkey(stellar_strkey::cli::Root),
211
212 #[command(long_about = completion::LONG_ABOUT)]
214 Completion(completion::Cmd),
215
216 #[command(subcommand)]
218 Cache(cache::Cmd),
219
220 Version(version::Cmd),
222
223 #[command(subcommand)]
225 Plugin(plugin::Cmd),
226
227 #[command(subcommand)]
229 Ledger(ledger::Cmd),
230
231 #[command(subcommand)]
233 Message(message::Cmd),
234
235 FeeStats(fee_stats::Cmd),
237
238 #[command(subcommand)]
240 Fees(fees::Cmd),
241}
242
243#[derive(thiserror::Error, Debug)]
244pub enum Error {
245 #[error(transparent)]
247 Contract(#[from] contract::Error),
248
249 #[error(transparent)]
250 Doctor(#[from] doctor::Error),
251
252 #[error(transparent)]
253 Events(#[from] events::Error),
254
255 #[error(transparent)]
256 Keys(#[from] keys::Error),
257
258 #[error(transparent)]
259 Xdr(#[from] stellar_xdr::cli::Error),
260
261 #[error(transparent)]
262 Strkey(#[from] stellar_strkey::cli::Error),
263
264 #[error(transparent)]
265 Clap(#[from] clap::error::Error),
266
267 #[error(transparent)]
268 Plugin(#[from] plugin::Error),
269
270 #[error(transparent)]
271 PluginDefault(#[from] plugin::default::Error),
272
273 #[error(transparent)]
274 Network(#[from] network::Error),
275
276 #[error(transparent)]
277 Container(#[from] container::Error),
278
279 #[error(transparent)]
280 Config(#[from] cfg::Error),
281
282 #[error(transparent)]
283 Snapshot(#[from] snapshot::Error),
284
285 #[error(transparent)]
286 Tx(#[from] tx::Error),
287
288 #[error(transparent)]
289 Cache(#[from] cache::Error),
290
291 #[error(transparent)]
292 Env(#[from] env::Error),
293
294 #[error(transparent)]
295 Ledger(#[from] ledger::Error),
296
297 #[error(transparent)]
298 Message(#[from] message::Error),
299
300 #[error(transparent)]
301 FeeStats(#[from] fee_stats::Error),
302
303 #[error(transparent)]
304 Fees(#[from] fees::Error),
305}