This commit is contained in:
vova
2022-12-06 21:08:49 +02:00
parent f7ce792eb4
commit 6bd8d91f4a
19 changed files with 125 additions and 191 deletions
+2 -1
View File
@@ -64,6 +64,7 @@ impl Sender {
fn main() {
let cli_offline_args = OfflineArgs::parse();
let context = (); // #[interactive_clap(input_context = ())]
let offline_args = <OfflineArgs as interactive_clap::FromCli>::from_cli(Some(cli_offline_args), context);
let offline_args =
<OfflineArgs as interactive_clap::FromCli>::from_cli(Some(cli_offline_args), context);
println!("offline_args: {:?}", offline_args)
}
+2 -1
View File
@@ -18,6 +18,7 @@ pub struct OperationMode {
fn main() {
let cli_operation_mode = OperationMode::parse();
let context = (); // default: input_context = ()
let operation_mode = <OperationMode as interactive_clap::FromCli>::from_cli(Some(cli_operation_mode), context);
let operation_mode =
<OperationMode as interactive_clap::FromCli>::from_cli(Some(cli_operation_mode), context);
println!("operation_mode: {:?}", &operation_mode);
}
+9 -3
View File
@@ -39,7 +39,10 @@ impl interactive_clap::FromCli for Submit {
fn from_cli(
optional_clap_variant: Option<<Self as interactive_clap::ToCli>::CliVariant>,
_context: Self::FromCliContext,
) -> Result<Option<Self>, Self::FromCliError> where Self: Sized + interactive_clap::ToCli {
) -> Result<Option<Self>, Self::FromCliError>
where
Self: Sized + interactive_clap::ToCli,
{
let submit: Option<Submit> = optional_clap_variant.clone();
match submit {
Some(submit) => Ok(Some(submit)),
@@ -80,8 +83,11 @@ fn main() {
let mut cli_online_args = OnlineArgs::parse();
let context = common::ConnectionConfig::Testnet; //#[interactive_clap(context = common::ConnectionConfig)]
let online_args = loop {
if let Some(args) =
<OnlineArgs as interactive_clap::FromCli>::from_cli(Some(cli_online_args.clone()), context.clone()).unwrap()
if let Some(args) = <OnlineArgs as interactive_clap::FromCli>::from_cli(
Some(cli_online_args.clone()),
context.clone(),
)
.unwrap()
{
break args;
}
@@ -11,7 +11,7 @@ pub fn fn_choose_variant(
) -> proc_macro2::TokenStream {
let name = &ast.ident;
let interactive_clap_attrs_context =
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(&ast);
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast);
let command_discriminants =
syn::Ident::new(&format!("{}Discriminants", name), Span::call_site());
let cli_command = syn::Ident::new(&format!("Cli{}", name), Span::call_site());
@@ -23,34 +23,27 @@ pub fn fn_choose_variant(
if !ast.attrs.is_empty() {
for attr in ast.attrs.clone() {
if attr.path.is_ident("interactive_clap".into()) {
if attr.path.is_ident("interactive_clap") {
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group
.stream()
.to_string()
.contains("disable_strum_discriminants")
.clone()
{
ast_attrs.push("disable_strum_discriminants");
} else if group.stream().to_string().contains("disable_back").clone() {
ast_attrs.push("disable_back");
};
}
_ => (), //abort_call_site!("Only option `TokenTree::Group` is needed")
if let proc_macro2::TokenTree::Group(group) = attr_token {
if group
.stream()
.to_string()
.contains("disable_strum_discriminants")
{
ast_attrs.push("disable_strum_discriminants");
} else if group.stream().to_string().contains("disable_back") {
ast_attrs.push("disable_back");
};
}
}
};
if attr.path.is_ident("strum_discriminants".into()) {
if attr.path.is_ident("strum_discriminants") {
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if &group.stream().to_string() == "derive(EnumMessage, EnumIter)" {
ast_attrs.push("strum_discriminants");
};
}
_ => (), //abort_call_site!("Only option `TokenTree::Group` is needed")
if let proc_macro2::TokenTree::Group(group) = attr_token {
if &group.stream().to_string() == "derive(EnumMessage, EnumIter)" {
ast_attrs.push("strum_discriminants");
};
}
}
};
@@ -77,15 +70,12 @@ pub fn fn_choose_variant(
let doc_attrs = ast
.attrs
.iter()
.filter(|attr| attr.path.is_ident("doc".into()))
.filter(|attr| attr.path.is_ident("doc"))
.map(|attr| {
let mut literal_string = String::new();
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Literal(literal) => {
literal_string = literal.to_string();
}
_ => (), //abort_call_site!("Only option `TokenTree::Literal` is needed")
if let proc_macro2::TokenTree::Literal(literal) = attr_token {
literal_string = literal.to_string();
}
}
literal_string
@@ -93,7 +83,7 @@ pub fn fn_choose_variant(
.collect::<Vec<_>>();
let literal_vec = doc_attrs
.iter()
.map(|s| s.replace("\"", ""))
.map(|s| s.replace('\"', ""))
.collect::<Vec<_>>();
let literal = proc_macro2::Literal::string(literal_vec.join("\n ").as_str());
@@ -8,7 +8,7 @@ pub fn cli_field_type(ty: &syn::Type) -> proc_macro2::TokenStream {
match &ty {
syn::Type::Path(type_path) => match type_path.path.segments.first() {
Some(path_segment) => {
if path_segment.ident.eq("Option".into()) {
if path_segment.ident.eq("Option") {
match &path_segment.arguments {
syn::PathArguments::AngleBracketed(gen_args) => {
let ty_option = &gen_args.args;
@@ -6,29 +6,16 @@ pub fn is_field_without_skip_default_from_cli_arg(field: &syn::Field) -> bool {
if field.attrs.is_empty() {
return true;
}
match field
!field
.attrs
.iter()
.filter(|attr| attr.path.is_ident("interactive_clap".into()))
.map(|attr| attr.tokens.clone())
.flatten()
.filter(|attr_token| match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group
.stream()
.to_string()
.contains("skip_default_from_cli_arg")
{
true
} else {
false
}
}
.filter(|attr| attr.path.is_ident("interactive_clap"))
.flat_map(|attr| attr.tokens.clone())
.any(|attr_token| match attr_token {
proc_macro2::TokenTree::Group(group) => group
.stream()
.to_string()
.contains("skip_default_from_cli_arg"),
_ => false, // abort_call_site!("Only option `TokenTree::Group` is needed")
})
.next()
{
Some(_) => false,
None => true,
}
}
@@ -1,6 +1,5 @@
extern crate proc_macro;
use proc_macro_error::abort_call_site;
use syn;
pub fn is_field_without_skip_default_input_arg(field: &syn::Field) -> bool {
@@ -10,25 +9,15 @@ pub fn is_field_without_skip_default_input_arg(field: &syn::Field) -> bool {
match field
.attrs
.iter()
.filter(|attr| attr.path.is_ident("interactive_clap".into()))
.map(|attr| attr.tokens.clone())
.flatten()
.filter(|attr_token| match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group
.stream()
.to_string()
.contains("skip_default_input_arg")
{
true
} else {
false
}
}
.filter(|attr| attr.path.is_ident("interactive_clap"))
.flat_map(|attr| attr.tokens.clone())
.find(|attr_token| match attr_token {
proc_macro2::TokenTree::Group(group) => group
.stream()
.to_string()
.contains("skip_default_input_arg"),
_ => false, // abort_call_site!("Only option `TokenTree::Group` is needed")
})
.next()
{
}) {
Some(_token_stream) => false,
None => true,
}
@@ -1,6 +1,5 @@
extern crate proc_macro;
use proc_macro_error::abort_call_site;
use syn;
pub fn is_field_without_subcommand(field: &syn::Field) -> bool {
@@ -10,24 +9,16 @@ pub fn is_field_without_subcommand(field: &syn::Field) -> bool {
match field
.attrs
.iter()
.map(|attr| attr.tokens.clone())
.flatten()
.filter(|attr_token| {
.flat_map(|attr| attr.tokens.clone())
.find(|attr_token| {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("named_arg")
group.stream().to_string().contains("named_arg")
|| group.stream().to_string().contains("subcommand")
{
true
} else {
false
}
}
_ => false, // abort_call_site!("Only option `TokenTree::Group` is needed")
}
})
.next()
{
}) {
Some(_token_stream) => false,
None => true,
}
@@ -13,7 +13,7 @@ pub fn from_cli_for_enum(
let cli_name = syn::Ident::new(&format!("Cli{}", name), Span::call_site());
let interactive_clap_attrs_context =
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(&ast);
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast);
if interactive_clap_attrs_context.is_skip_default_from_cli {
return quote!();
};
@@ -12,7 +12,7 @@ pub fn from_cli_for_struct(
let name = &ast.ident;
let interactive_clap_attrs_context =
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(&ast);
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast);
if interactive_clap_attrs_context.is_skip_default_from_cli {
return quote!();
};
@@ -28,7 +28,7 @@ pub fn from_cli_for_struct(
let fields_value = fields
.iter()
.map(|field| fields_value(field))
.map(fields_value)
.filter(|token_stream| !token_stream.is_empty());
let field_value_named_arg = if let Some(token_stream) = fields
@@ -40,8 +40,7 @@ pub fn from_cli_for_struct(
&interactive_clap_attrs_context.output_context_dir,
)
})
.filter(|token_stream| !token_stream.is_empty())
.next()
.find(|token_stream| !token_stream.is_empty())
{
token_stream
} else {
@@ -57,8 +56,7 @@ pub fn from_cli_for_struct(
&interactive_clap_attrs_context.output_context_dir,
)
})
.filter(|token_stream| !token_stream.is_empty())
.next()
.find(|token_stream| !token_stream.is_empty())
{
token_stream
} else {
@@ -125,18 +123,11 @@ fn field_value_named_arg(
quote!()
} else {
match field.attrs.iter()
.filter(|attr| attr.path.is_ident("interactive_clap".into()))
.map(|attr| attr.tokens.clone())
.flatten()
.filter(|attr| attr.path.is_ident("interactive_clap"))
.flat_map(|attr| attr.tokens.clone())
.filter(|attr_token| {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("named_arg") {
true
} else {
false
}
},
proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("named_arg"),
_ => abort_call_site!("Only option `TokenTree::Group` is needed")
}
})
@@ -206,18 +197,11 @@ fn field_value_subcommand(
quote!()
} else {
match field.attrs.iter()
.filter(|attr| attr.path.is_ident("interactive_clap".into()))
.map(|attr| attr.tokens.clone())
.flatten()
.filter(|attr| attr.path.is_ident("interactive_clap"))
.flat_map(|attr| attr.tokens.clone())
.filter(|attr_token| {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("subcommand") {
true
} else {
false
}
},
proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("subcommand"),
_ => abort_call_site!("Only option `TokenTree::Group` is needed")
}
})
@@ -260,14 +244,14 @@ fn field_value_subcommand(
fn struct_field(
field: &syn::Field,
fields_without_subcommand: &Vec<proc_macro2::TokenStream>,
fields_without_subcommand: &[proc_macro2::TokenStream],
) -> proc_macro2::TokenStream {
let ident_field = &field.clone().ident.expect("this field does not exist");
let fields_without_subcommand_to_string = fields_without_subcommand
if fields_without_subcommand
.iter()
.map(|token_stream| token_stream.to_string())
.collect::<Vec<_>>();
if fields_without_subcommand_to_string.contains(&ident_field.to_string()) {
.any(|x| *ident_field == x)
{
quote! {
#ident_field: new_context_scope.#ident_field
}
@@ -6,7 +6,7 @@ use syn;
pub fn from_cli_arg(ast: &syn::DeriveInput, fields: &syn::Fields) -> Vec<proc_macro2::TokenStream> {
let interactive_clap_attrs_context =
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(&ast);
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast);
let fields_without_subcommand = fields
.iter()
@@ -46,7 +46,9 @@ pub fn from_cli_arg(ast: &syn::DeriveInput, fields: &syn::Fields) -> Vec<proc_ma
.collect::<Vec<_>>();
if fields_without_subcommand_to_string.contains(&ident_field.to_string())
& fields_without_skip_default_from_cli_arg_to_string
.contains(&ident_field.to_string())
.iter()
.map(|token_stream| token_stream.to_string())
.any(|x| *ident_field == x)
{
let fn_from_cli_arg =
syn::Ident::new(&format!("from_cli_{}", &ident_field), Span::call_site());
@@ -1,7 +1,6 @@
extern crate proc_macro;
use proc_macro2::Span;
use proc_macro_error::abort_call_site;
use quote::quote;
use syn;
@@ -10,7 +9,7 @@ pub fn vec_fn_input_arg(
fields: &syn::Fields,
) -> Vec<proc_macro2::TokenStream> {
let interactive_clap_attrs_context =
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(&ast);
super::interactive_clap_attrs_context::InteractiveClapAttrsContext::new(ast);
let vec_fn_input_arg = fields
.iter()
.filter(|field| super::fields_without_subcommand::is_field_without_subcommand(field))
@@ -46,15 +45,12 @@ pub fn vec_fn_input_arg(
let doc_attrs = field
.attrs
.iter()
.filter(|attr| attr.path.is_ident("doc".into()))
.filter(|attr| attr.path.is_ident("doc"))
.map(|attr| {
let mut literal_string = String::new();
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Literal(literal) => {
literal_string = literal.to_string();
}
_ => (), //abort_call_site!("Only option `TokenTree::Literal` is needed")
if let proc_macro2::TokenTree::Literal(literal) = attr_token {
literal_string = literal.to_string();
}
}
literal_string
@@ -62,7 +58,7 @@ pub fn vec_fn_input_arg(
.collect::<Vec<_>>();
let literal_vec = doc_attrs
.iter()
.map(|s| s.replace("\"", ""))
.map(|s| s.replace('\"', ""))
.collect::<Vec<_>>();
let literal = proc_macro2::Literal::string(literal_vec.join("\n ").as_str());
@@ -19,28 +19,25 @@ impl InteractiveClapAttrsContext {
let mut is_skip_default_from_cli = false;
if !ast.attrs.is_empty() {
for attr in ast.attrs.clone() {
if attr.path.is_ident("interactive_clap".into()) {
if attr.path.is_ident("interactive_clap") {
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("output_context") {
let group_stream =
&group.stream().into_iter().collect::<Vec<_>>()[2..];
output_context_dir = quote! {#(#group_stream)*};
} else if group.stream().to_string().contains("input_context") {
let group_stream =
&group.stream().into_iter().collect::<Vec<_>>()[2..];
input_context_dir = quote! {#(#group_stream)*};
} else if group.stream().to_string().contains("context") {
let group_stream =
&group.stream().into_iter().collect::<Vec<_>>()[2..];
context_dir = quote! {#(#group_stream)*};
};
if group.stream().to_string().contains("skip_default_from_cli") {
is_skip_default_from_cli = true;
};
}
_ => (), //abort_call_site!("Only option `TokenTree::Group` is needed")
if let proc_macro2::TokenTree::Group(group) = attr_token {
if group.stream().to_string().contains("output_context") {
let group_stream =
&group.stream().into_iter().collect::<Vec<_>>()[2..];
output_context_dir = quote! {#(#group_stream)*};
} else if group.stream().to_string().contains("input_context") {
let group_stream =
&group.stream().into_iter().collect::<Vec<_>>()[2..];
input_context_dir = quote! {#(#group_stream)*};
} else if group.stream().to_string().contains("context") {
let group_stream =
&group.stream().into_iter().collect::<Vec<_>>()[2..];
context_dir = quote! {#(#group_stream)*};
};
if group.stream().to_string().contains("skip_default_from_cli") {
is_skip_default_from_cli = true;
};
}
}
};
@@ -79,10 +76,9 @@ impl InteractiveClapAttrsContext {
if !context_dir.is_empty() {
return context_dir;
};
let input_context_dir = match self.input_context_dir {
match self.input_context_dir {
Some(input_context_dir) => input_context_dir,
None => quote! {()},
};
input_context_dir
}
}
}
@@ -32,16 +32,14 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
let mut clap_attr_vec: Vec<proc_macro2::TokenStream> = Vec::new();
let mut cfg_attr_vec: Vec<proc_macro2::TokenStream> = Vec::new();
for attr in &field.attrs {
if attr.path.is_ident("interactive_clap".into())
| attr.path.is_ident("cfg".into())
{
if attr.path.is_ident("interactive_clap") | attr.path.is_ident("cfg") {
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("subcommand")
| group.stream().to_string().contains("value_enum")
| group.stream().to_string().contains("long")
| (group.stream().to_string() == "skip".to_string())
| (group.stream().to_string() == *"skip")
{
clap_attr_vec.push(group.stream())
} else if group.stream().to_string().contains("named_arg") {
@@ -73,7 +71,7 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
if group.stream().to_string().contains("feature") {
cfg_attr_vec.push(attr.into_token_stream())
};
if group.stream().to_string() == "skip".to_string() {
if group.stream().to_string() == *"skip" {
ident_skip_field_vec.push(ident_field.clone());
cli_field = quote!()
};
@@ -112,19 +110,18 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
.filter(|token_stream| !token_stream.is_empty());
let fn_from_cli_for_struct =
self::methods::from_cli_for_struct::from_cli_for_struct(&ast, &fields);
self::methods::from_cli_for_struct::from_cli_for_struct(ast, &fields);
let fn_get_arg =
self::methods::get_arg_from_cli_for_struct::from_cli_arg(&ast, &fields);
let fn_get_arg = self::methods::get_arg_from_cli_for_struct::from_cli_arg(ast, &fields);
let vec_fn_input_arg = self::methods::input_arg::vec_fn_input_arg(&ast, &fields);
let vec_fn_input_arg = self::methods::input_arg::vec_fn_input_arg(ast, &fields);
let context_scope_fields = fields
.iter()
.map(|field| context_scope_for_struct_field(field))
.map(context_scope_for_struct_field)
.filter(|token_stream| !token_stream.is_empty())
.collect::<Vec<_>>();
let context_scope_for_struct = context_scope_for_struct(&name, context_scope_fields);
let context_scope_for_struct = context_scope_for_struct(name, context_scope_fields);
let clap_enum_for_named_arg =
if let Some(token_stream) = fields.iter().find_map(|field| {
@@ -132,22 +129,15 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
let variant_name_string = crate::helpers::snake_case_to_camel_case::snake_case_to_camel_case(ident_field.to_string());
let variant_name = &syn::Ident::new(&variant_name_string, Span::call_site());
let attr_doc_vec: Vec<_> = field.attrs.iter()
.filter(|attr| attr.path.is_ident("doc".into()))
.filter(|attr| attr.path.is_ident("doc"))
.map(|attr| attr.into_token_stream())
.collect();
field.attrs.iter()
.filter(|attr| attr.path.is_ident("interactive_clap".into()))
.map(|attr| attr.tokens.clone())
.flatten()
.filter(|attr| attr.path.is_ident("interactive_clap"))
.flat_map(|attr| attr.tokens.clone())
.filter(|attr_token| {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
if group.stream().to_string().contains("named_arg") {
true
} else {
false
}
},
proc_macro2::TokenTree::Group(group) => group.stream().to_string().contains("named_arg"),
_ => abort_call_site!("Only option `TokenTree::Group` is needed")
}
})
@@ -230,11 +220,11 @@ pub fn impl_interactive_clap(ast: &syn::DeriveInput) -> TokenStream {
let mut attrs: Vec<proc_macro2::TokenStream> = Vec::new();
if !&variant.attrs.is_empty() {
for attr in &variant.attrs {
if attr.path.is_ident("doc".into()) {
if attr.path.is_ident("doc") {
attrs.push(attr.into_token_stream());
// break;
};
if attr.path.is_ident("cfg".into()) {
if attr.path.is_ident("cfg") {
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
@@ -400,17 +390,17 @@ fn context_scope_for_enum(name: &syn::Ident) -> proc_macro2::TokenStream {
fn for_cli_field(
field: &syn::Field,
ident_skip_field_vec: &Vec<syn::Ident>,
ident_skip_field_vec: &[syn::Ident],
) -> proc_macro2::TokenStream {
let ident_field = &field.clone().ident.expect("this field does not exist");
if ident_skip_field_vec.contains(&ident_field) {
if ident_skip_field_vec.contains(ident_field) {
quote!()
} else {
let ty = &field.ty;
match &ty {
syn::Type::Path(type_path) => match type_path.path.segments.first() {
Some(path_segment) => {
if path_segment.ident.eq("Option".into()) {
if path_segment.ident.eq("Option") {
quote! {
#ident_field: args.#ident_field.into()
}
@@ -32,14 +32,14 @@ impl InteractiveClapAttrsCliField {
};
} else {
for attr in &field.attrs {
if attr.path.is_ident("clap".into()) {
if attr.path.is_ident("clap") {
for attr_token in attr.tokens.clone() {
match attr_token {
proc_macro2::TokenTree::Group(group) => {
for item in group.stream() {
match &item {
proc_macro2::TokenTree::Ident(ident) => {
if "subcommand".to_string() == ident.to_string() {
if *ident == *"subcommand" {
subcommand_args = quote! {
let mut args = self
.#ident_field
@@ -47,13 +47,13 @@ impl InteractiveClapAttrsCliField {
.map(|subcommand| subcommand.to_cli_args())
.unwrap_or_default();
};
} else if "value_enum".to_string() == ident.to_string() {
} else if *ident == *"value_enum" {
args_without_attrs = quote! {
if let Some(arg) = &self.#ident_field {
args.push_front(arg.to_string())
}
};
} else if "long".to_string() == ident.to_string() {
} else if *ident == *"long" {
let ident_field_to_kebab_case_string =
crate::helpers::to_kebab_case::to_kebab_case(
ident_field.to_string(),
@@ -1,7 +1,7 @@
pub fn snake_case_to_camel_case(s: String) -> String {
let s_vec: Vec<String> = s
.to_lowercase()
.split("_")
.split('_')
.map(|s| s.replacen(&s[..1], &s[..1].to_ascii_uppercase(), 1))
.collect();
s_vec.join("")
@@ -6,5 +6,5 @@ pub fn to_kebab_case(s: String) -> String {
}
snake.push(ch.to_ascii_lowercase());
}
snake.as_str().replace("_", "-")
snake.as_str().replace('_', "-")
}
-1
View File
@@ -2,7 +2,6 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;
use syn;
mod derives;
mod helpers;
+3 -1
View File
@@ -36,5 +36,7 @@ pub trait FromCli {
fn from_cli(
optional_clap_variant: Option<<Self as ToCli>::CliVariant>,
context: Self::FromCliContext,
) -> Result<Option<Self>, Self::FromCliError> where Self: Sized + ToCli;
) -> Result<Option<Self>, Self::FromCliError>
where
Self: Sized + ToCli;
}