nut_shell/tree/
mod.rs

1//! Command tree data structures.
2//!
3//! Provides the core tree structure for organizing commands and directories.
4//! All tree structures are const-initializable and live in ROM.
5
6use crate::auth::AccessLevel;
7
8// Sub-modules
9pub mod completion;
10pub mod path;
11
12/// Command kind marker (sync or async).
13#[derive(Debug, Copy, Clone, PartialEq, Eq)]
14pub enum CommandKind {
15    /// Synchronous command
16    Sync,
17
18    /// Asynchronous command (requires `async` feature)
19    #[cfg(feature = "async")]
20    Async,
21}
22
23/// Command metadata (const-initializable, no execution logic).
24/// Execution via `CommandHandler` trait enables sync and async commands with const-initialization.
25/// Unique `id` field allows duplicate names across directories.
26#[derive(Debug, Clone)]
27pub struct CommandMeta<L: AccessLevel> {
28    /// Unique identifier for handler dispatch (must be unique across entire tree).
29    /// Convention: use path-like IDs (e.g., "system_reboot", "network_reboot").
30    pub id: &'static str,
31
32    /// Command name (display name, can duplicate across directories)
33    pub name: &'static str,
34
35    /// Command description (shown by ls command)
36    pub description: &'static str,
37
38    /// Minimum access level required
39    pub access_level: L,
40
41    /// Command kind (sync or async marker)
42    pub kind: CommandKind,
43
44    /// Minimum number of arguments
45    pub min_args: usize,
46
47    /// Maximum number of arguments
48    pub max_args: usize,
49}
50
51/// Directory node containing child nodes (const-initializable, stored in ROM).
52/// Organizes commands hierarchically.
53#[derive(Debug, Clone)]
54pub struct Directory<L: AccessLevel> {
55    /// Directory name
56    pub name: &'static str,
57
58    /// Child nodes (commands and subdirectories)
59    pub children: &'static [Node<L>],
60
61    /// Minimum access level required to access this directory
62    pub access_level: L,
63}
64
65/// Tree node (command or directory).
66///
67/// Enables zero-cost dispatch via pattern matching instead of vtables.
68#[derive(Debug, Clone)]
69pub enum Node<L: AccessLevel> {
70    /// Command node (metadata only)
71    Command(&'static CommandMeta<L>),
72
73    /// Directory node
74    Directory(&'static Directory<L>),
75}
76
77impl<L: AccessLevel> Node<L> {
78    /// Check if this node is a command.
79    pub fn is_command(&self) -> bool {
80        matches!(self, Node::Command(_))
81    }
82
83    /// Check if this node is a directory.
84    pub fn is_directory(&self) -> bool {
85        matches!(self, Node::Directory(_))
86    }
87
88    /// Get node name.
89    pub fn name(&self) -> &'static str {
90        match self {
91            Node::Command(cmd) => cmd.name,
92            Node::Directory(dir) => dir.name,
93        }
94    }
95
96    /// Get node access level.
97    pub fn access_level(&self) -> L {
98        match self {
99            Node::Command(cmd) => cmd.access_level,
100            Node::Directory(dir) => dir.access_level,
101        }
102    }
103}
104
105impl<L: AccessLevel> Directory<L> {
106    /// Find child node by name (no access control, returns `None` if not found).
107    pub fn find_child(&self, name: &str) -> Option<&Node<L>> {
108        self.children.iter().find(|child| child.name() == name)
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::auth::AccessLevel;
116
117    // Mock access level for testing
118    #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
119    enum TestAccessLevel {
120        Guest = 0,
121        User = 1,
122    }
123
124    impl AccessLevel for TestAccessLevel {
125        fn from_str(s: &str) -> Option<Self> {
126            match s {
127                "Guest" => Some(Self::Guest),
128                "User" => Some(Self::User),
129                _ => None,
130            }
131        }
132
133        fn as_str(&self) -> &'static str {
134            match self {
135                Self::Guest => "Guest",
136                Self::User => "User",
137            }
138        }
139    }
140
141    #[test]
142    fn test_command_kind() {
143        assert_eq!(CommandKind::Sync, CommandKind::Sync);
144
145        #[cfg(feature = "async")]
146        assert_ne!(CommandKind::Sync, CommandKind::Async);
147    }
148
149    #[test]
150    fn test_node_type_checking() {
151        const CMD: CommandMeta<TestAccessLevel> = CommandMeta {
152            id: "test",
153            name: "test",
154            description: "Test command",
155            access_level: TestAccessLevel::User,
156            kind: CommandKind::Sync,
157            min_args: 0,
158            max_args: 0,
159        };
160
161        let node = Node::Command(&CMD);
162        assert!(node.is_command());
163        assert!(!node.is_directory());
164        assert_eq!(node.name(), "test");
165    }
166}