Files
archived-interactive-clap/examples/struct_with_flatten.rs
FroVolod 6f6796c94b ci: Added clippy to examples (#31)
Resolves #30

---------

Co-authored-by: FroVolod <frol_off@meta.ua>
2025-01-07 20:32:49 +02:00

287 lines
11 KiB
Rust

// This example shows additional functionality of the "interactive-clap" macro for parsing command-line data into a structure using the macro's flatten attributes.
// 1) build an example: cargo build --example struct_with_flatten
// 2) go to the `examples` folder: cd target/debug/examples
// 3) run an example: ./struct_with_flatten (without parameters) => entered interactive mode
// ./struct_with_flatten --no-docker --no-abi --out-dir /Users/Documents/Rust --color never test.testnet offline => contract: CliContract { build_command_args: Some(CliBuildCommand { no_docker: true, no_release: false, no_abi: true, no_embed_abi: false, no_doc: false, out_dir: Some("/Users/Documents/Rust"), manifest_path: None, color: Some(Never) }), contract_account_id: Some("test.testnet"), mode: Some(Offline) }
// To learn more about the parameters, use "help" flag: ./struct_with_flatten --help
// Note: currently there is no automatic generation of "interactive clap::From Cli"
#![allow(clippy::unit_arg, dead_code, unreachable_patterns, unused_variables)]
use interactive_clap::{ResultFromCli, ToCliArgs};
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = ())]
#[interactive_clap(output_context = ContractContext)]
#[interactive_clap(skip_default_from_cli)]
pub struct Contract {
#[interactive_clap(flatten)]
/// Specify a build command args:
build_command_args: BuildCommand,
/// What is the contract account ID?
contract_account_id: String,
#[interactive_clap(subcommand)]
pub mode: Mode,
}
#[derive(Debug, Clone)]
pub struct ContractContext;
impl ContractContext {
pub fn from_previous_context(
previous_context: (),
scope: &<Contract as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
// Your commands
Ok(Self)
}
}
impl interactive_clap::FromCli for Contract {
type FromCliContext = ();
type FromCliError = color_eyre::eyre::Error;
fn from_cli(
optional_clap_variant: Option<<Self as interactive_clap::ToCli>::CliVariant>,
context: Self::FromCliContext,
) -> interactive_clap::ResultFromCli<
<Self as interactive_clap::ToCli>::CliVariant,
Self::FromCliError,
>
where
Self: Sized + interactive_clap::ToCli,
{
let mut clap_variant = optional_clap_variant.unwrap_or_default();
let build_command_args =
if let Some(cli_build_command_args) = &clap_variant.build_command_args {
BuildCommand {
no_docker: cli_build_command_args.no_docker,
no_release: cli_build_command_args.no_release,
no_abi: cli_build_command_args.no_abi,
no_embed_abi: cli_build_command_args.no_embed_abi,
no_doc: cli_build_command_args.no_doc,
out_dir: cli_build_command_args.out_dir.clone(),
manifest_path: cli_build_command_args.manifest_path.clone(),
color: cli_build_command_args.color.clone(),
env: cli_build_command_args.env.clone(),
}
} else {
BuildCommand::default()
};
if clap_variant.contract_account_id.is_none() {
clap_variant.contract_account_id = match Self::input_contract_account_id(&context) {
Ok(Some(contract_account_id)) => Some(contract_account_id),
Ok(None) => return interactive_clap::ResultFromCli::Cancel(Some(clap_variant)),
Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err),
};
}
let contract_account_id = clap_variant
.contract_account_id
.clone()
.expect("Unexpected error");
let new_context_scope = InteractiveClapContextScopeForContract {
build_command_args,
contract_account_id,
};
let _output_context =
match ContractContext::from_previous_context(context, &new_context_scope) {
Ok(new_context) => new_context,
Err(err) => return interactive_clap::ResultFromCli::Err(Some(clap_variant), err),
};
match <Mode as interactive_clap::FromCli>::from_cli(clap_variant.mode.take(), context) {
interactive_clap::ResultFromCli::Ok(cli_field) => {
clap_variant.mode = Some(cli_field);
}
interactive_clap::ResultFromCli::Cancel(option_cli_field) => {
clap_variant.mode = option_cli_field;
return interactive_clap::ResultFromCli::Cancel(Some(clap_variant));
}
interactive_clap::ResultFromCli::Cancel(option_cli_field) => {
clap_variant.mode = option_cli_field;
return interactive_clap::ResultFromCli::Cancel(Some(clap_variant));
}
interactive_clap::ResultFromCli::Back => {
return interactive_clap::ResultFromCli::Back;
}
interactive_clap::ResultFromCli::Err(option_cli_field, err) => {
clap_variant.mode = option_cli_field;
return interactive_clap::ResultFromCli::Err(Some(clap_variant), err);
}
};
interactive_clap::ResultFromCli::Ok(clap_variant)
}
}
#[derive(Debug, Default, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = ())]
#[interactive_clap(output_context = BuildCommandlContext)]
pub struct BuildCommand {
/// Build contract without SourceScan verification
#[interactive_clap(long)]
pub no_docker: bool,
/// Build contract in debug mode, without optimizations and bigger is size
#[interactive_clap(long)]
pub no_release: bool,
/// Do not generate ABI for the contract
#[interactive_clap(long)]
pub no_abi: bool,
/// Do not embed the ABI in the contract binary
#[interactive_clap(long)]
pub no_embed_abi: bool,
/// Do not include rustdocs in the embedded ABI
#[interactive_clap(long)]
pub no_doc: bool,
/// Copy final artifacts to this directory
#[interactive_clap(long)]
#[interactive_clap(skip_interactive_input)]
pub out_dir: Option<String>,
/// Path to the `Cargo.toml` of the contract to build
#[interactive_clap(long)]
#[interactive_clap(skip_interactive_input)]
pub manifest_path: Option<String>,
/// Coloring: auto, always, never
#[interactive_clap(long)]
#[interactive_clap(value_enum)]
#[interactive_clap(skip_interactive_input)]
pub color: Option<ColorPreference>,
// `long_vec_multiple_opt` implies `skip_interactive_input`
// `long_vec_multiple_opt` implies `long`
#[interactive_clap(long_vec_multiple_opt)]
pub env: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct BuildCommandlContext {
build_command_args: BuildCommand,
}
impl BuildCommandlContext {
pub fn from_previous_context(
_previous_context: (),
scope: &<BuildCommand as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
let build_command_args = BuildCommand {
no_docker: scope.no_docker,
no_release: scope.no_release,
no_abi: scope.no_abi,
no_embed_abi: scope.no_embed_abi,
no_doc: scope.no_doc,
out_dir: scope.out_dir.clone(),
manifest_path: scope.manifest_path.clone(),
color: scope.color.clone(),
env: scope.env.clone(),
};
Ok(Self { build_command_args })
}
}
use strum::{EnumDiscriminants, EnumIter, EnumMessage};
#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)]
#[strum_discriminants(derive(EnumMessage, EnumIter))]
///To construct a transaction you will need to provide information about sender (signer) and receiver accounts, and actions that needs to be performed.
///Do you want to derive some information required for transaction construction automatically querying it online?
pub enum Mode {
/// Prepare and, optionally, submit a new transaction with online mode
#[strum_discriminants(strum(message = "Yes, I keep it simple"))]
Network,
/// Prepare and, optionally, submit a new transaction with offline mode
#[strum_discriminants(strum(
message = "No, I want to work in no-network (air-gapped) environment"
))]
Offline,
}
use std::str::FromStr;
#[derive(Debug, EnumDiscriminants, Clone, clap::ValueEnum)]
#[strum_discriminants(derive(EnumMessage, EnumIter))]
pub enum ColorPreference {
Auto,
Always,
Never,
}
impl interactive_clap::ToCli for ColorPreference {
type CliVariant = ColorPreference;
}
impl std::fmt::Display for ColorPreference {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Auto => write!(f, "auto"),
Self::Always => write!(f, "always"),
Self::Never => write!(f, "never"),
}
}
}
impl FromStr for ColorPreference {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"auto" => Ok(default_mode()),
"always" => Ok(ColorPreference::Always),
"never" => Ok(ColorPreference::Never),
_ => Err(format!("invalid color preference: {}", s)),
}
}
}
fn default_mode() -> ColorPreference {
ColorPreference::Never
}
impl ColorPreference {
pub fn as_str(&self) -> &str {
match self {
ColorPreference::Auto => "auto",
ColorPreference::Always => "always",
ColorPreference::Never => "never",
}
}
}
fn main() -> color_eyre::Result<()> {
let mut cli_contract = Contract::parse();
let context = (); // default: input_context = ()
loop {
let contract =
<Contract as interactive_clap::FromCli>::from_cli(Some(cli_contract), context);
match contract {
ResultFromCli::Ok(cli_contract) | ResultFromCli::Cancel(Some(cli_contract)) => {
println!("contract: {cli_contract:#?}");
println!(
"Your console command: {}",
shell_words::join(cli_contract.to_cli_args())
);
return Ok(());
}
ResultFromCli::Cancel(None) => {
println!("Goodbye!");
return Ok(());
}
ResultFromCli::Back => {
cli_contract = Default::default();
}
ResultFromCli::Err(cli_contract, err) => {
if let Some(cli_contract) = cli_contract {
println!(
"Your console command: {}",
shell_words::join(cli_contract.to_cli_args())
);
}
return Err(err);
}
}
}
}