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