如何在rust中为clap结构体创建一个自定义的派生宏?

uxhixvfz  于 2022-12-26  发布在  其他
关注(0)|答案(1)|浏览(262)

我试图在rust中创建一些简单的计费CLI应用程序,以供实践。它是一种数据库应用程序。我有许多操作,我需要用户能够通过列的值过滤哪些行将被操作。
下面的示例包含delete和show命令:

  1. #[derive(Debug, Args)]
  2. struct DeleteCommand {
  3. /// The ID of the bill
  4. #[clap(short, long, value_parser)]
  5. id: Option<String>,
  6. /// The name of the bill
  7. #[clap(short, long, value_parser)]
  8. name: Option<String>,
  9. /// The value of the bill
  10. #[clap(short, long, value_parser)]
  11. value: Option<String>,
  12. /// The amount of the billB
  13. #[clap(short, long, value_parser)]
  14. amount: Option<String>,
  15. /// Datetime
  16. #[clap(short, long, value_parser)]
  17. datetime: Option<String>,
  18. /// Currency
  19. #[clap(short, long, value_parser)]
  20. currency: Option<String>,
  21. /// Recipient
  22. #[clap(short, long, value_parser)]
  23. recipient: Option<String>,
  24. /// Situation
  25. #[clap(short, long, value_parser)]
  26. situation: Option<String>,
  27. /// Hard
  28. #[clap(short = 'H', long)]
  29. hard: bool,
  30. }
  31. #[derive(Debug, Args)]
  32. struct ShowCommand {
  33. #[clap(subcommand)]
  34. subcommand: ShowSubcommand,
  35. /// The ID of the bill
  36. #[clap(short, long, value_parser)]
  37. id: Option<String>,
  38. /// The name of the bill
  39. #[clap(short, long, value_parser)]
  40. name: Option<String>,
  41. /// The value of the bill
  42. #[clap(short, long, value_parser)]
  43. value: Option<String>,
  44. /// The amount of the bill
  45. #[clap(short, long, value_parser)]
  46. amount: Option<String>,
  47. /// Datetime
  48. #[clap(short, long, value_parser)]
  49. datetime: Option<String>,
  50. /// Currency
  51. #[clap(short, long, value_parser)]
  52. currency: Option<String>,
  53. /// Datetime
  54. #[clap(short, long, value_parser)]
  55. recipient: Option<String>,
  56. /// Datetime
  57. #[clap(short, long, value_parser)]
  58. situation: Option<String>,
  59. /// Head
  60. #[clap(long, value_parser)]
  61. head: Option<u32>,
  62. /// Tail
  63. #[clap(long, value_parser)]
  64. tail: Option<u32>,
  65. /// Order by
  66. #[clap(short, long, value_parser)]
  67. orderby: Option<String>,
  68. }

正如你所看到的,两个结构体之间有许多公共字段,因为都需要for来过滤数据库中的项。我只想创建一个派生自定义宏,它允许我在结构体中简单地重复这些行,而不需要一遍又一遍地编写它:

  1. /// The ID of the bill
  2. #[clap(short, long, value_parser)]
  3. id: Option<String>,
  4. /// The name of the bill
  5. #[clap(short, long, value_parser)]
  6. name: Option<String>,
  7. /// The value of the bill
  8. #[clap(short, long, value_parser)]
  9. value: Option<String>,
  10. /// The amount of the bill
  11. #[clap(short, long, value_parser)]
  12. amount: Option<String>,
  13. /// Datetime
  14. #[clap(short, long, value_parser)]
  15. datetime: Option<String>,
  16. /// Currency
  17. #[clap(short, long, value_parser)]
  18. currency: Option<String>,
  19. /// Recipient
  20. #[clap(short, long, value_parser)]
  21. recipient: Option<String>,
  22. /// Situation
  23. #[clap(short, long, value_parser)]
  24. situation: Option<String>,

我还需要重复///行,因为它将成为CLI中每个参数的描述。

最小重现示例:

这是我的

  1. use clap::{Parser, Subcommand, Args};
  2. #[derive(Debug, Parser)]
  3. pub struct UserInput {
  4. #[clap(subcommand)]
  5. command: Command,
  6. }
  7. #[derive(Debug, Subcommand)]
  8. enum Command {
  9. /// A command description
  10. A(CommandA),
  11. /// B command description
  12. B(CommandB),
  13. }
  14. #[derive(Debug, Args)]
  15. struct CommandA {
  16. /// The value of column x to be filtered in the database
  17. #[clap(short, long, value_parser)]
  18. x_value: Option<String>,
  19. /// The value of column y to be filtered in the database
  20. #[clap(short, long, value_parser)]
  21. y_value: Option<String>,
  22. /// Some particular parameter of this command
  23. #[clap(short, long, value_parser)]
  24. particular: Option<String>,
  25. }
  26. #[derive(Debug, Args)]
  27. struct CommandB {
  28. /// The value of x to be filtered in the database
  29. #[clap(short, long, value_parser)]
  30. x_value: Option<String>,
  31. /// The value of y to be filtered in the database
  32. #[clap(short, long, value_parser)]
  33. y_value: Option<String>,
  34. /// Some particular parameter of this command
  35. #[clap(short, long, value_parser)]
  36. particular: Option<String>,
  37. }
  38. fn main() {
  39. let user_input: UserInput = UserInput::parse();
  40. print!("{:#?}", user_input)
  41. }

这就是我想要的

  1. use clap::{Parser, Subcommand, Args};
  2. #[derive(Debug, Parser)]
  3. pub struct UserInput {
  4. #[clap(subcommand)]
  5. command: Command,
  6. }
  7. #[derive(Debug, Subcommand)]
  8. enum Command {
  9. /// A command description
  10. A(CommandA),
  11. /// B command description
  12. B(CommandB),
  13. }
  14. #[derive(Debug, Args, Filter)]
  15. struct CommandA {
  16. /// Some particular parameter command A
  17. #[clap(short, long, value_parser)]
  18. particular: Option<String>,
  19. }
  20. #[derive(Debug, Args, Filter)]
  21. struct CommandB {
  22. /// Some particular parameter command B
  23. #[clap(short, long, value_parser)]
  24. particular: Option<String>,
  25. }
  26. fn main() {
  27. let user_input: UserInput = UserInput::parse();
  28. print!("{:#?}", user_input)
  29. }

等待行为:

  1. [user@host appname]$ appname a --help
  2. p1-a
  3. A command description
  4. USAGE:
  5. p1 a [OPTIONS]
  6. OPTIONS:
  7. -x, --x-value <X_VALUE> The value of column X to be filtered in the database
  8. -y, --y-value <Y_VALUE> The value of column Y to be filtered in the database
  9. -h, --help Print help information
  10. -p, --particular <PARTICULAR> Some particular parameter of command A

编辑:我使用的是clap 3.2.22,但我会尽量把代码移到最新版本。

mf98qq94

mf98qq941#

通过使用composition沿着#[clap(flatten)],您几乎可以实现您想要的效果,如下所示:

  1. use clap::{Args, Parser, Subcommand};
  2. #[derive(Debug, Args)]
  3. struct CommonArgs {
  4. /// The value of column x to be filtered in the database
  5. #[clap(short, long, value_parser)]
  6. x_value: Option<String>,
  7. /// The value of column y to be filtered in the database
  8. #[clap(short, long, value_parser)]
  9. y_value: Option<String>,
  10. }
  11. #[derive(Debug, Args)]
  12. struct ArgsA {
  13. #[clap(flatten)]
  14. common_args: CommonArgs,
  15. /// Hard
  16. #[clap(short = 'H', long)]
  17. particular_a: bool,
  18. }
  19. #[derive(Debug, Args)]
  20. struct ArgsB {
  21. // #[clap(subcommand)]
  22. // subcommand: ShowSubcommand,
  23. #[clap(flatten)]
  24. commmon_args: CommonArgs,
  25. /// Head
  26. #[clap(long)]
  27. particular_b: Option<u32>,
  28. }
  29. #[derive(Debug, Parser)]
  30. pub struct UserInput {
  31. #[clap(subcommand)]
  32. command: MyCommand,
  33. }
  34. #[derive(Debug, Subcommand)]
  35. enum MyCommand {
  36. /// A command description
  37. A(ArgsA),
  38. /// B command description
  39. B(ArgsB),
  40. }
  41. fn main() {
  42. dbg!(UserInput::parse_from("playground b --help".split(' ')));
  43. }

唯一的区别是,在访问时,您可以多输入一级间接。
这个“缺点”伴随着一个优点,那就是你可以更容易地传递部分参数,例如为ArgsAArgsB实现AsRef<CommonArgs>

展开查看全部

相关问题