1use crate::realm::{get_all_instances, Instance};
6use ansi_term::Colour;
7use anyhow::Result;
8use prettytable::format::consts::FORMAT_CLEAN;
9use prettytable::{cell, row, Table};
10use std::collections::HashSet;
11use std::str::FromStr;
12
13use flex_fuchsia_sys2 as fsys;
14
15#[derive(Debug, PartialEq)]
17pub enum ListFilter {
18 Running,
19 Stopped,
20 Ancestor(String),
23 Descendant(String),
26 Relative(String),
29}
30
31impl FromStr for ListFilter {
32 type Err = &'static str;
33
34 fn from_str(s: &str) -> Result<Self, Self::Err> {
35 match s {
36 "running" => Ok(ListFilter::Running),
37 "stopped" => Ok(ListFilter::Stopped),
38 filter => match filter.split_once(":") {
39 Some((function, arg)) => match function {
40 "ancestor" | "ancestors" => Ok(ListFilter::Ancestor(arg.to_string())),
41 "descendant" | "descendants" => Ok(ListFilter::Descendant(arg.to_string())),
42 "relative" | "relatives" => Ok(ListFilter::Relative(arg.to_string())),
43 _ => Err("unknown function for list filter."),
44 },
45 None => Err("list filter should be 'running', 'stopped', 'ancestors:<component_name>', 'descendants:<component_name>', or 'relatives:<component_name>'."),
46 },
47 }
48 }
49}
50
51pub async fn list_cmd_print<W: std::io::Write>(
52 filter: Option<ListFilter>,
53 verbose: bool,
54 realm_query: fsys::RealmQueryProxy,
55 mut writer: W,
56) -> Result<()> {
57 let instances = get_instances_matching_filter(filter, &realm_query).await?;
58
59 if verbose {
60 let table = create_table(instances);
61 table.print(&mut writer)?;
62 } else {
63 for instance in instances {
64 writeln!(writer, "{}", instance.moniker)?;
65 }
66 }
67
68 Ok(())
69}
70
71pub async fn list_cmd_serialized(
72 filter: Option<ListFilter>,
73 realm_query: fsys::RealmQueryProxy,
74) -> Result<Vec<Instance>> {
75 let basic_infos = get_instances_matching_filter(filter, &realm_query).await?;
76 Ok(basic_infos)
77}
78
79fn create_table(instances: Vec<Instance>) -> Table {
81 let mut table = Table::new();
82 table.set_format(*FORMAT_CLEAN);
83 table.set_titles(row!("State", "Moniker", "URL"));
84
85 for instance in instances {
86 let state = instance.resolved_info.map_or(Colour::Red.paint("Stopped"), |r| {
87 r.execution_info
88 .map_or(Colour::Yellow.paint("Resolved"), |_| Colour::Green.paint("Running"))
89 });
90
91 table.add_row(row!(state, instance.moniker.to_string(), instance.url));
92 }
93 table
94}
95
96pub async fn get_instances_matching_filter(
97 filter: Option<ListFilter>,
98 realm_query: &fsys::RealmQueryProxy,
99) -> Result<Vec<Instance>> {
100 let instances = get_all_instances(realm_query).await?;
101
102 let mut instances = match filter {
103 Some(ListFilter::Running) => instances
104 .into_iter()
105 .filter(|i| i.resolved_info.as_ref().map_or(false, |r| r.execution_info.is_some()))
106 .collect(),
107 Some(ListFilter::Stopped) => instances
108 .into_iter()
109 .filter(|i| i.resolved_info.as_ref().map_or(true, |r| r.execution_info.is_none()))
110 .collect(),
111 Some(ListFilter::Ancestor(m)) => filter_ancestors(instances, m),
112 Some(ListFilter::Descendant(m)) => filter_descendants(instances, m),
113 Some(ListFilter::Relative(m)) => filter_relatives(instances, m),
114 _ => instances,
115 };
116
117 instances.sort_by_key(|c| c.moniker.to_string());
118
119 Ok(instances)
120}
121
122fn filter_ancestors(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
123 let mut ancestors = HashSet::new();
124
125 for instance in &instances {
127 if let Some(child) = instance.moniker.leaf() {
128 if child.to_string() == child_str {
129 let mut cur_moniker = instance.moniker.clone();
131 ancestors.insert(cur_moniker.clone());
132
133 while let Some(parent) = cur_moniker.parent() {
135 ancestors.insert(parent.clone());
136 cur_moniker = parent;
137 }
138 }
139 }
140 }
141
142 instances.into_iter().filter(|i| ancestors.contains(&i.moniker)).collect()
143}
144
145fn filter_descendants(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
146 let mut descendants = HashSet::new();
147
148 for instance in &instances {
150 if let Some(child) = instance.moniker.leaf() {
151 if child.to_string() == child_str {
152 for possible_child_instance in &instances {
154 if possible_child_instance.moniker.has_prefix(&instance.moniker) {
155 descendants.insert(possible_child_instance.moniker.clone());
156 }
157 }
158 }
159 }
160 }
161
162 instances.into_iter().filter(|i| descendants.contains(&i.moniker)).collect()
163}
164
165fn filter_relatives(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
166 let mut relatives = HashSet::new();
167
168 for instance in &instances {
170 if let Some(child) = instance.moniker.leaf() {
171 if child.to_string() == child_str {
172 let mut cur_moniker = instance.moniker.clone();
174 while let Some(parent) = cur_moniker.parent() {
175 relatives.insert(parent.clone());
176 cur_moniker = parent;
177 }
178
179 for possible_child_instance in &instances {
181 if possible_child_instance.moniker.has_prefix(&instance.moniker) {
182 relatives.insert(possible_child_instance.moniker.clone());
183 }
184 }
185 }
186 }
187 }
188
189 instances.into_iter().filter(|i| relatives.contains(&i.moniker)).collect()
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::test_utils::*;
196 use moniker::Moniker;
197 use std::collections::HashMap;
198
199 fn create_query() -> fsys::RealmQueryProxy {
200 let query = serve_realm_query(
202 vec![
203 fsys::Instance {
204 moniker: Some("./".to_string()),
205 url: Some("fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string()),
206 instance_id: None,
207 resolved_info: Some(fsys::ResolvedInfo {
208 resolved_url: Some(
209 "fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string(),
210 ),
211 execution_info: None,
212 ..Default::default()
213 }),
214 ..Default::default()
215 },
216 fsys::Instance {
217 moniker: Some("./core".to_string()),
218 url: Some("fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string()),
219 instance_id: None,
220 resolved_info: Some(fsys::ResolvedInfo {
221 resolved_url: Some(
222 "fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string(),
223 ),
224 execution_info: Some(fsys::ExecutionInfo {
225 start_reason: Some("Debugging Workflow".to_string()),
226 ..Default::default()
227 }),
228 ..Default::default()
229 }),
230 ..Default::default()
231 },
232 fsys::Instance {
233 moniker: Some("./core/appmgr".to_string()),
234 url: Some("fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string()),
235 instance_id: None,
236 resolved_info: Some(fsys::ResolvedInfo {
237 resolved_url: Some(
238 "fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string(),
239 ),
240 execution_info: Some(fsys::ExecutionInfo {
241 start_reason: Some("Debugging Workflow".to_string()),
242 ..Default::default()
243 }),
244 ..Default::default()
245 }),
246 ..Default::default()
247 },
248 ],
249 HashMap::new(),
250 HashMap::new(),
251 HashMap::new(),
252 );
253 query
254 }
255
256 #[fuchsia::test]
257 async fn no_filter() {
258 let query = create_query();
259
260 let instances = get_instances_matching_filter(None, &query).await.unwrap();
261 assert_eq!(
262 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
263 vec![
264 Moniker::root(),
265 Moniker::parse_str("/core").unwrap(),
266 Moniker::parse_str("/core/appmgr").unwrap(),
267 ]
268 );
269 }
270
271 #[fuchsia::test]
272 async fn running_only() {
273 let query = create_query();
274
275 let instances =
276 get_instances_matching_filter(Some(ListFilter::Running), &query).await.unwrap();
277 assert_eq!(
278 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
279 vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
280 );
281 }
282
283 #[fuchsia::test]
284 async fn stopped_only() {
285 let query = create_query();
286
287 let instances =
288 get_instances_matching_filter(Some(ListFilter::Stopped), &query).await.unwrap();
289 assert_eq!(
290 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
291 [Moniker::root()]
292 );
293 }
294
295 #[fuchsia::test]
296 async fn descendants_only() {
297 let query = create_query();
298
299 let instances =
300 get_instances_matching_filter(Some(ListFilter::Descendant("core".to_string())), &query)
301 .await
302 .unwrap();
303 assert_eq!(
304 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
305 vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
306 );
307 }
308
309 #[fuchsia::test]
310 async fn ancestors_only() {
311 let query = create_query();
312
313 let instances =
314 get_instances_matching_filter(Some(ListFilter::Ancestor("core".to_string())), &query)
315 .await
316 .unwrap();
317 assert_eq!(
318 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
319 vec![Moniker::root(), Moniker::parse_str("/core").unwrap()]
320 );
321 }
322
323 #[fuchsia::test]
324 async fn relative_only() {
325 let query = create_query();
326
327 let instances =
328 get_instances_matching_filter(Some(ListFilter::Relative("core".to_string())), &query)
329 .await
330 .unwrap();
331 assert_eq!(
332 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
333 vec![
334 Moniker::root(),
335 Moniker::parse_str("/core").unwrap(),
336 Moniker::parse_str("/core/appmgr").unwrap(),
337 ]
338 );
339 }
340}