Skip to content

Commit

Permalink
feat(gcli): support customized CLI derived from gcli (#3573)
Browse files Browse the repository at this point in the history
  • Loading branch information
clearloop authored Dec 12, 2023
1 parent 6efec47 commit 20d413c
Show file tree
Hide file tree
Showing 20 changed files with 318 additions and 154 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions gcli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@ license.workspace = true
homepage.workspace = true
repository.workspace = true

[[bin]]
path = "bin/gcli.rs"
name = "gcli"

[dependencies]
gsdk.workspace = true
anyhow.workspace = true
async-trait.workspace = true
base64.workspace = true
color-eyre.workspace = true
dirs.workspace = true
Expand Down
54 changes: 54 additions & 0 deletions gcli/examples/mycli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This file is part of Gear.
//
// Copyright (C) 2021-2023 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use gcli::{async_trait, App, Command, Parser};

/// My customized sub commands.
#[derive(Debug, Parser)]
pub enum SubCommand {
/// GCli preset commands.
#[clap(flatten)]
GCliCommands(Command),
/// My customized ping command.
Ping,
}

/// My customized gcli.
#[derive(Debug, Parser)]
pub struct MyGCli {
#[clap(subcommand)]
command: SubCommand,
}

#[async_trait]
impl App for MyGCli {
async fn exec(&self) -> anyhow::Result<()> {
match &self.command {
SubCommand::GCliCommands(command) => command.exec(self).await,
SubCommand::Ping => {
println!("pong");
Ok(())
}
}
}
}

#[tokio::main]
async fn main() -> color_eyre::Result<()> {
MyGCli::parse().run().await
}
135 changes: 135 additions & 0 deletions gcli/src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// This file is part of Gear.
//
// Copyright (C) 2021-2023 Gear Technologies Inc.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
//! Command line application abstraction
use crate::keystore;
use clap::Parser;
use color_eyre::{eyre::eyre, Result};
use env_logger::{Builder, Env};
use gsdk::{signer::Signer, Api};

/// Command line gear program application abstraction.
///
/// ```ignore
/// use gcli::{async_trait, App, Command, Parser};
///
/// /// My customized sub commands.
/// #[derive(Debug, Parser)]
/// pub enum SubCommand {
/// /// GCli preset commands.
/// #[clap(flatten)]
/// GCliCommands(Command),
/// /// My customized ping command.
/// Ping,
/// }
///
/// /// My customized gcli.
/// #[derive(Debug, Parser)]
/// pub struct MyGCli {
/// #[clap(subcommand)]
/// command: SubCommand,
/// }
///
/// #[async_trait]
/// impl App for MyGCli {
/// async fn exec(&self) -> anyhow::Result<()> {
/// match &self.command {
/// SubCommand::GCliCommands(command) => command.exec(self).await,
/// SubCommand::Ping => {
/// println!("pong");
/// Ok(())
/// }
/// }
/// }
/// }
///
/// #[tokio::main]
/// async fn main() -> color_eyre::Result<()> {
/// MyGCli::parse().run().await
/// }
/// ```
#[async_trait::async_trait]
pub trait App: Parser + Sync {
/// Timeout of rpc requests.
fn timeout(&self) -> u64 {
60000
}

/// The verbosity logging level.
fn verbose(&self) -> u16 {
0
}

/// The endpoint of the gear node.
fn endpoint(&self) -> Option<String> {
None
}

/// Password of the signer account.
fn passwd(&self) -> Option<String> {
None
}

/// Exec program from the parsed arguments.
async fn exec(&self) -> anyhow::Result<()>;

/// Get signer.
async fn signer(&self) -> anyhow::Result<Signer> {
let endpoint = self.endpoint().clone();
let timeout = self.timeout();
let passwd = self.passwd();

let api = Api::new_with_timeout(endpoint.as_deref(), Some(timeout)).await?;
let pair = if let Ok(s) = keystore::cache(passwd.as_deref()) {
s
} else {
keystore::keyring(passwd.as_deref())?
};

Ok((api, pair).into())
}

/// Run application.
///
/// This is a wrapper of [`Self::exec`] with preset retry
/// and verbose level.
async fn run(&self) -> Result<()> {
color_eyre::install()?;
sp_core::crypto::set_default_ss58_version(crate::VARA_SS58_PREFIX.into());

let name = Self::command().get_name().to_string();
let filter = match self.verbose() {
0 => format!("{name}=info,gsdk=info"),
1 => format!("{name}=debug,gsdk=debug"),
2 => "debug".into(),
_ => "trace".into(),
};

let mut builder = Builder::from_env(Env::default().default_filter_or(filter));
builder
.format_target(false)
.format_module_path(false)
.format_timestamp(None);
builder.try_init()?;

self.exec()
.await
.map_err(|e| eyre!("Failed to run app, {e}"))
}
}
15 changes: 3 additions & 12 deletions gcli/bin/gcli.rs → gcli/src/bin/gcli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,9 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! gear command entry
use color_eyre::eyre::Result;
use gcli::{cmd::Opt, App, Parser};

#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;

sp_core::crypto::set_default_ss58_version(gcli::VARA_SS58_PREFIX.into());

if let Err(e) = gcli::cmd::Opt::run().await {
log::error!("{}", e);
}

Ok(())
async fn main() -> color_eyre::Result<()> {
Opt::parse().run().await
}
20 changes: 13 additions & 7 deletions gcli/src/cmd/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ pub struct Key {
/// Key actions
#[command(subcommand)]
action: Action,
/// Passphrase override
#[arg(short, long)]
passwd: Option<String>,
}

macro_rules! match_scheme {
Expand All @@ -121,15 +124,15 @@ impl Key {
/// # NOTE
///
/// Reserved the `passwd` for getting suri from cache.
pub fn exec(&self, passwd: Option<&str>) -> Result<()> {
pub fn exec(&self) -> Result<()> {
match &self.action {
Action::Generate => self.generate(passwd)?,
Action::Generate => self.generate()?,
#[cfg(feature = "node-key")]
Action::GenerateNodeKey => Self::generate_node_key(),
Action::Inspect { suri } => self.inspect(suri, passwd)?,
Action::Inspect { suri } => self.inspect(suri)?,
#[cfg(feature = "node-key")]
Action::InspectNodeKey { secret } => Self::inspect_node_key(secret)?,
Action::Sign { suri, message } => self.sign(suri, message, passwd)?,
Action::Sign { suri, message } => self.sign(suri, message)?,
Action::Verify {
signature,
message,
Expand All @@ -140,7 +143,8 @@ impl Key {
Ok(())
}

fn generate(&self, passwd: Option<&str>) -> Result<()> {
fn generate(&self) -> Result<()> {
let passwd = self.passwd.as_deref();
match_scheme!(self.scheme, generate_with_phrase(passwd), res, {
let (pair, phrase, seed) = res;
let signer = pair.signer();
Expand Down Expand Up @@ -181,9 +185,10 @@ impl Key {
println!(" SS58 Address: {}", signer.public().into_account());
}

fn inspect(&self, suri: &str, passwd: Option<&str>) -> Result<()> {
fn inspect(&self, suri: &str) -> Result<()> {
let key = KeyT::from_string(suri);
let key_ref = &key;
let passwd = self.passwd.as_deref();
match_scheme!(self.scheme, pair(key_ref, passwd), pair, {
Self::info(&format!("Secret Key URI `{suri}`"), pair.0.signer(), pair.1)
});
Expand All @@ -209,9 +214,10 @@ impl Key {
Ok(())
}

fn sign(&self, suri: &str, message: &str, passwd: Option<&str>) -> Result<()> {
fn sign(&self, suri: &str, message: &str) -> Result<()> {
let key = KeyT::from_string(suri);
let key_ref = &key;
let passwd = self.passwd.as_deref();

match_scheme!(self.scheme, pair(key_ref, passwd), pair, {
let signer = pair.0.signer();
Expand Down
Loading

0 comments on commit 20d413c

Please sign in to comment.