bt_common/
debug_command.rs

1// Copyright 2023 Google LLC
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5///! Debug command traits and helpers for defining commands for integration
6/// into a debug tool.
7use std::str::FromStr;
8
9/// A CommandSet is a set of commands (usually an enum) that each represent an
10/// action that can be performed.  i.e. 'list', 'volume' etc.  Each command can
11/// take zero or more arguments and have zero or more flags.
12/// Typically an Enum of commands would implement CommandSet trait.
13pub trait CommandSet: FromStr + ::core::fmt::Display {
14    /// Returns a vector of strings that are the commands supported by this.
15    fn variants() -> Vec<String>;
16
17    /// Returns a string listing the arguments that this command takes, in <>
18    /// brackets
19    fn arguments(&self) -> &'static str;
20
21    /// Returns a string displaying the flags that this command supports, in []
22    /// brackets
23    fn flags(&self) -> &'static str;
24
25    /// Returns a short description of this command
26    fn desc(&self) -> &'static str;
27
28    /// Help string for this variant (build from Display, arguments and flags by
29    /// default)
30    fn help_simple(&self) -> String {
31        format!("{self} {} {} -- {}", self.flags(), self.arguments(), self.desc())
32    }
33
34    /// Possibly multi-line help string for all variants of this set.
35    fn help_all() -> String {
36        Self::variants()
37            .into_iter()
38            .filter_map(|s| FromStr::from_str(&s).ok())
39            .map(|s: Self| format!("{}\n", s))
40            .collect()
41    }
42}
43
44/// Macro to help build CommandSets
45#[macro_export]
46macro_rules! gen_commandset {
47    ($name:ident {
48        $($variant:ident = ($val:expr, [$($flag:expr),*], [$($arg:expr),*], $help:expr)),*,
49    }) => {
50        /// Enum of all possible commands
51        #[derive(PartialEq, Debug)]
52        pub enum $name {
53            $($variant),*
54        }
55
56        impl CommandSet for $name {
57            fn variants() -> Vec<String> {
58                let mut variants = Vec::new();
59                $(variants.push($val.to_string());)*
60                variants
61            }
62
63            fn arguments(&self) -> &'static str {
64                match self {
65                    $(
66                        $name::$variant => concat!($("<", $arg, "> ",)*)
67                    ),*
68                }
69            }
70
71            fn flags(&self) -> &'static str {
72                match self {
73                    $(
74                        $name::$variant => concat!($("[", $flag, "] ",)*)
75                    ),*
76                }
77            }
78
79            fn desc(&self) -> &'static str {
80                match self {
81                    $(
82                        $name::$variant => $help
83                    ),*
84                }
85            }
86        }
87
88        impl ::core::fmt::Display for $name {
89            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
90                match *self {
91                    $($name::$variant => write!(f, $val)),* ,
92                }
93            }
94        }
95
96        impl ::std::str::FromStr for $name {
97            type Err = ();
98
99            fn from_str(s: &str) -> Result<$name, ()> {
100                match s {
101                    $($val => Ok($name::$variant)),* ,
102                    _ => Err(()),
103                }
104            }
105        }
106    }
107}
108
109/// CommandRunner is used to perform a specific task based on the
110pub trait CommandRunner {
111    type Set: CommandSet;
112    fn run(
113        &self,
114        cmd: Self::Set,
115        args: Vec<String>,
116    ) -> impl futures::Future<Output = Result<(), impl ::std::error::Error>>;
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn gen_commandset_simple() {
125        gen_commandset! {
126            TestCmd {
127                One = ("one", [], [], "First Command"),
128                WithFlags = ("with-flags", ["-1","-2"], [], "Command with flags"),
129                WithArgs = ("with-args", [], ["arg", "two"], "Command with args"),
130                WithBoth = ("with-both", ["-w"], ["simple"], "Command with both flags and args"),
131            }
132        }
133
134        let cmd: TestCmd = "one".parse().unwrap();
135
136        assert_eq!(cmd, TestCmd::One);
137
138        let cmd2: TestCmd = "with-flags".parse().unwrap();
139
140        assert_eq!(cmd2.arguments(), "");
141        assert_eq!(cmd2.flags(), "[-1] [-2] ");
142    }
143}