Open MabezDev opened 2 months ago
Currently structs are only supported as subcommands. You can find example in desktop example:
#[derive(Debug, Command)]
enum BaseCommand<'a> {
/// Control LEDs
Led {
/// LED id
#[arg(long)]
id: u8,
#[command(subcommand)]
command: LedCommand,
},
/// Control ADC
Adc {
/// ADC id
#[arg(long)]
id: u8,
#[command(subcommand)]
command: AdcCommand<'a>,
},
/// Show some status
Status,
/// Stop CLI and exit
Exit,
}
And subcommands can be used as single field of tuple variant:
#[derive(Debug, Command)]
enum BaseCommand<'a> {
/// Control ADC
Adc(
#[command(subcommand)]
AdcCommand<'a>
),
}
Flattening can be supported but it's a bit tricky. When generating parser implementation you will need to try pass current argument to parser implementation to struct and if it fails, try to parse other fields of current variant. And there might be multiple fields that should be flattened.
Contributions are welcome, but this might be a hard one. If you want to dig a little bit, try to start from analyzing actual generated code of examples (use cargo expand). And then figure out how to modify parser implementation. After that it will be easy to implement that implementation in macro.
To be more exact, how do you think generated code for autocomple, parser and help should look like for following example?
pub struct NetworkSettings {
dhcp: bool,
}
pub struct UartSettings {
baud: u32
}
pub enum Setting {
/// Global network settings
Network(
#[command(flatten)]
NetworkSettings
),
/// Settings for a given uart, the first param is the uart number
Uart{
id: usize,
#[command(flatten)]
uart: UartSettings
},
}
Hmm, this is indeed a bit tricky. I've been playing around with it locally, and made some progress (or rather gained some familiarity with how the derives actually work).
When generating parser implementation you will need to try pass current argument to parser implementation to struct and if it fails
I'm planning on avoiding this by treating flattened in order of placement in the enum variant, so I guess a bit like positional. Let me know if you think this is the wrong path.
I'm starting to think that Command
is the wrong thing to base this off. My initial attempts tried to shoe horn the scaffolding for SubCommand
into a flatten, but because it's a command, and commands only are parsed from RawCommand
it must have a name. In reality we want to treat the struct as inlined arguments.
My idea now is to introduce an Args
derive which works quite a lot like RawCommand
parsing, but drops the requirement of a name. I'll see where I get with this.
I'm planning on avoiding this by treating flattened in order of placement in the enum variant, so I guess a bit like positional.
The moment you have flattened args, someone will combine them with other args. And forcing specific order for user will be very misleading. Suppose you have following command:
pub struct UartSettings {
#[arg(long)]
baud: u32
}
pub enum Setting {
Uart{
#[arg(long)]
id: usize,
#[command(flatten)]
uart: UartSettings
},
}
By forcing order your cli will fail to parse this command:
uart --baud 12 --id 0
and will only work with this command:
uart --it 0 --baud 12
So having support for arbitrary order when using flattened structs is crucial.
I'm starting to think that Command is the wrong thing to base this off.
You're probably right. Initially I tried to mimic clap approach but it didn't fit well in all places. Mostly because of embbedded stuff and living without dynamic memory. If you figure out better API for command description with macros - that would be great. That would be a breaking change of course but it's worth it.
I can also highlight that another distinction between commands and args is that commands are modeled as enums and args - as structs. This seems convenient but maybe not a right choice.
The moment you have flattened args, someone will combine them with other args. And forcing specific order for user will be very misleading. Suppose you have following command:
Hmm, but are we in agreement at least that for any flattened field the arguments should be passed in any order, but grouped. In other words:
pub struct UartSettings {
#[arg(long)]
baud: u32,
#[arg(long)]
other: u32
}
pub struct UartSettings2 {
#[arg(long)]
something: u32
#[arg(long)]
something_else: u32
}
pub enum Setting {
Uart{
#[arg(long)]
id: usize,
#[command(flatten)]
uart: UartSettings,
#[command(flatten)]
uart: UartSettings2
},
}
A user should enter --baud
and --other
together (but the order doesn't matter) and should not interleave args for UartSettings2
.
I think without this restriction it becomes very hard to implement, at least with the way I had in mind.
I have some settings I wish to be able to configure over a CLI, however, the setting
enum
often has structs which have most of the configuration information.As things currently stand, the
derive
can't handle this, and would require me to duplicate the fields in the enum.I'm wondering:
#[flatten]
attribute to handle the above conversion automatically make sense?I'm happy to contribute, I just need a bit of direction as proc_macros are my weakest area of Rust.