Skip to main content

ember_plus/glow/
command.rs

1//! Glow command handling.
2
3use crate::error::Result;
4use super::root::{GlowCommand, GlowElement, EmberPath};
5use super::element::EmberValue;
6
7/// Direction field mask for GetDirectory command.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub struct DirFieldMask {
10    /// Include identifier
11    pub identifier: bool,
12    /// Include description
13    pub description: bool,
14    /// Include tree (children)
15    pub tree: bool,
16    /// Include value
17    pub value: bool,
18    /// Include connections (for matrices)
19    pub connections: bool,
20}
21
22impl DirFieldMask {
23    /// Create a mask requesting all fields.
24    pub fn all() -> Self {
25        DirFieldMask {
26            identifier: true,
27            description: true,
28            tree: true,
29            value: true,
30            connections: true,
31        }
32    }
33
34    /// Create a mask requesting default fields.
35    pub fn default_fields() -> Self {
36        DirFieldMask {
37            identifier: true,
38            description: true,
39            tree: false,
40            value: true,
41            connections: false,
42        }
43    }
44
45    /// Create from a bitmask integer.
46    pub fn from_bits(bits: i32) -> Self {
47        DirFieldMask {
48            identifier: (bits & 0x01) != 0,
49            description: (bits & 0x02) != 0,
50            tree: (bits & 0x04) != 0,
51            value: (bits & 0x08) != 0,
52            connections: (bits & 0x10) != 0,
53        }
54    }
55
56    /// Convert to a bitmask integer.
57    pub fn to_bits(&self) -> i32 {
58        let mut bits = 0;
59        if self.identifier { bits |= 0x01; }
60        if self.description { bits |= 0x02; }
61        if self.tree { bits |= 0x04; }
62        if self.value { bits |= 0x08; }
63        if self.connections { bits |= 0x10; }
64        bits
65    }
66}
67
68/// Command builder for creating Glow commands.
69pub struct CommandBuilder;
70
71impl CommandBuilder {
72    /// Create a GetDirectory command for the root.
73    pub fn get_root_directory() -> GlowElement {
74        GlowElement::Command(GlowCommand::GetDirectory)
75    }
76
77    /// Create a Subscribe command.
78    pub fn subscribe() -> GlowElement {
79        GlowElement::Command(GlowCommand::Subscribe)
80    }
81
82    /// Create an Unsubscribe command.
83    pub fn unsubscribe() -> GlowElement {
84        GlowElement::Command(GlowCommand::Unsubscribe)
85    }
86
87    /// Create an Invoke command.
88    pub fn invoke(invocation_id: i32, arguments: Vec<EmberValue>) -> GlowElement {
89        GlowElement::Command(GlowCommand::Invoke {
90            invocation_id,
91            arguments,
92        })
93    }
94
95    /// Create a command to get directory at a specific path.
96    pub fn get_directory_at_path(path: &EmberPath) -> Vec<GlowElement> {
97        Self::wrap_command_at_path(path, GlowCommand::GetDirectory, None)
98    }
99
100    /// Create a command to get directory at a specific path with node info.
101    pub fn get_directory_at_path_with_info(path: &EmberPath, identifier: Option<&str>) -> Vec<GlowElement> {
102        Self::wrap_command_at_path(path, GlowCommand::GetDirectory, identifier)
103    }
104
105    /// Create a command to subscribe at a specific path.
106    pub fn subscribe_at_path(path: &EmberPath) -> Vec<GlowElement> {
107        Self::wrap_command_at_path(path, GlowCommand::Subscribe, None)
108    }
109
110    /// Create a command to unsubscribe at a specific path.
111    pub fn unsubscribe_at_path(path: &EmberPath) -> Vec<GlowElement> {
112        Self::wrap_command_at_path(path, GlowCommand::Unsubscribe, None)
113    }
114
115    /// Wrap a command in a QualifiedNode to reach the given path.
116    fn wrap_command_at_path(path: &EmberPath, command: GlowCommand, identifier: Option<&str>) -> Vec<GlowElement> {
117        if path.is_empty() {
118            return vec![GlowElement::Command(command)];
119        }
120
121        // Use QualifiedNode with the path and command as child
122        let mut node = super::root::GlowNode::new(0);
123        node.identifier = identifier.map(|s| s.to_string());
124        node.children.push(GlowElement::Command(command));
125        vec![GlowElement::QualifiedNode(path.clone(), node)]
126    }
127
128    /// Create a setValue command for a parameter at a path.
129    pub fn set_value_at_path(path: &EmberPath, value: EmberValue) -> Vec<GlowElement> {
130        if path.is_empty() {
131            return vec![];
132        }
133
134        let param_number = *path.last().unwrap();
135        let parent_path = &path[..path.len() - 1];
136
137        let mut param = super::root::GlowParameter::new(param_number);
138        param.value = Some(value);
139        let param_element = GlowElement::Parameter(param);
140
141        if parent_path.is_empty() {
142            return vec![param_element];
143        }
144
145        // Build from innermost to outermost
146        let mut current = param_element;
147
148        for &number in parent_path.iter().rev() {
149            let mut node = super::root::GlowNode::new(number);
150            node.children.push(current);
151            current = GlowElement::Node(node);
152        }
153
154        vec![current]
155    }
156
157    /// Create an invoke command for a function at a path.
158    pub fn invoke_at_path(
159        path: &EmberPath,
160        invocation_id: i32,
161        arguments: Vec<EmberValue>,
162    ) -> Vec<GlowElement> {
163        if path.is_empty() {
164            return vec![];
165        }
166
167        // Use QualifiedFunction with the full path
168        let mut func = super::root::GlowFunction::new(0);
169        func.children.push(GlowElement::Command(GlowCommand::Invoke {
170            invocation_id,
171            arguments,
172        }));
173        vec![GlowElement::QualifiedFunction(path.clone(), func)]
174    }
175
176    /// Create a matrix connection operation.
177    pub fn matrix_connect(
178        path: &EmberPath,
179        target: i32,
180        sources: Vec<i32>,
181        operation: super::element::ConnectionOperation,
182    ) -> Vec<GlowElement> {
183        if path.is_empty() {
184            return vec![];
185        }
186
187        let matrix_number = *path.last().unwrap();
188        let parent_path = &path[..path.len() - 1];
189
190        let connection = super::root::GlowConnection {
191            target,
192            sources,
193            operation: Some(operation),
194            disposition: None,
195        };
196
197        let mut matrix = super::root::GlowMatrix::new(matrix_number);
198        matrix.connections.push(connection);
199        let matrix_element = GlowElement::Matrix(matrix);
200
201        if parent_path.is_empty() {
202            return vec![matrix_element];
203        }
204
205        // Build from innermost to outermost
206        let mut current = matrix_element;
207
208        for &number in parent_path.iter().rev() {
209            let mut node = super::root::GlowNode::new(number);
210            node.children.push(current);
211            current = GlowElement::Node(node);
212        }
213
214        vec![current]
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_dirfield_mask() {
224        let mask = DirFieldMask::all();
225        let bits = mask.to_bits();
226        let decoded = DirFieldMask::from_bits(bits);
227        
228        assert_eq!(mask.identifier, decoded.identifier);
229        assert_eq!(mask.description, decoded.description);
230        assert_eq!(mask.tree, decoded.tree);
231        assert_eq!(mask.value, decoded.value);
232        assert_eq!(mask.connections, decoded.connections);
233    }
234
235    #[test]
236    fn test_wrap_command_at_path() {
237        let path = vec![0, 1, 2];
238        let elements = CommandBuilder::get_directory_at_path(&path);
239        
240        assert_eq!(elements.len(), 1);
241        
242        // Should be a nested structure: Node(0) -> Node(1) -> Node(2) -> Command
243        if let GlowElement::Node(n0) = &elements[0] {
244            assert_eq!(n0.number, 0);
245            if let GlowElement::Node(n1) = &n0.children[0] {
246                assert_eq!(n1.number, 1);
247                if let GlowElement::Node(n2) = &n1.children[0] {
248                    assert_eq!(n2.number, 2);
249                    assert!(matches!(n2.children[0], GlowElement::Command(GlowCommand::GetDirectory)));
250                } else {
251                    panic!("Expected Node at level 2");
252                }
253            } else {
254                panic!("Expected Node at level 1");
255            }
256        } else {
257            panic!("Expected Node at level 0");
258        }
259    }
260}