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_else(
87 || Colour::Red.paint("Stopped"),
88 |r| {
89 r.execution_info.map_or_else(
90 || Colour::Yellow.paint("Resolved"),
91 |_| Colour::Green.paint("Running"),
92 )
93 },
94 );
95
96 table.add_row(row!(state, instance.moniker.to_string(), instance.url));
97 }
98 table
99}
100
101pub async fn get_instances_matching_filter(
102 filter: Option<ListFilter>,
103 realm_query: &fsys::RealmQueryProxy,
104) -> Result<Vec<Instance>> {
105 let instances = get_all_instances(realm_query).await?;
106
107 let mut instances = match filter {
108 Some(ListFilter::Running) => instances
109 .into_iter()
110 .filter(|i| i.resolved_info.as_ref().map_or(false, |r| r.execution_info.is_some()))
111 .collect(),
112 Some(ListFilter::Stopped) => instances
113 .into_iter()
114 .filter(|i| i.resolved_info.as_ref().map_or(true, |r| r.execution_info.is_none()))
115 .collect(),
116 Some(ListFilter::Ancestor(m)) => filter_ancestors(instances, m),
117 Some(ListFilter::Descendant(m)) => filter_descendants(instances, m),
118 Some(ListFilter::Relative(m)) => filter_relatives(instances, m),
119 _ => instances,
120 };
121
122 instances.sort_by_key(|c| c.moniker.to_string());
123
124 Ok(instances)
125}
126
127fn filter_ancestors(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
128 let mut ancestors = HashSet::new();
129
130 for instance in &instances {
132 if let Some(child) = instance.moniker.leaf() {
133 if *child == child_str {
134 let mut cur_moniker = instance.moniker.clone();
136 ancestors.insert(cur_moniker.clone());
137
138 while let Some(parent) = cur_moniker.parent() {
140 ancestors.insert(parent.clone());
141 cur_moniker = parent;
142 }
143 }
144 }
145 }
146
147 instances.into_iter().filter(|i| ancestors.contains(&i.moniker)).collect()
148}
149
150fn filter_descendants(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
151 let mut descendants = HashSet::new();
152
153 for instance in &instances {
155 if let Some(child) = instance.moniker.leaf() {
156 if *child == child_str {
157 for possible_child_instance in &instances {
159 if possible_child_instance.moniker.has_prefix(&instance.moniker) {
160 descendants.insert(possible_child_instance.moniker.clone());
161 }
162 }
163 }
164 }
165 }
166
167 instances.into_iter().filter(|i| descendants.contains(&i.moniker)).collect()
168}
169
170fn filter_relatives(instances: Vec<Instance>, child_str: String) -> Vec<Instance> {
171 let mut relatives = HashSet::new();
172
173 for instance in &instances {
175 if let Some(child) = instance.moniker.leaf() {
176 if *child == child_str {
177 let mut cur_moniker = instance.moniker.clone();
179 while let Some(parent) = cur_moniker.parent() {
180 relatives.insert(parent.clone());
181 cur_moniker = parent;
182 }
183
184 for possible_child_instance in &instances {
186 if possible_child_instance.moniker.has_prefix(&instance.moniker) {
187 relatives.insert(possible_child_instance.moniker.clone());
188 }
189 }
190 }
191 }
192 }
193
194 instances.into_iter().filter(|i| relatives.contains(&i.moniker)).collect()
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::test_utils::*;
201 use moniker::Moniker;
202 use std::collections::HashMap;
203
204 fn create_query() -> fsys::RealmQueryProxy {
205 let query = serve_realm_query(
207 vec![
208 fsys::Instance {
209 moniker: Some("./".to_string()),
210 url: Some("fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string()),
211 instance_id: None,
212 resolved_info: Some(fsys::ResolvedInfo {
213 resolved_url: Some(
214 "fuchsia-pkg://fuchsia.com/root#meta/root.cm".to_string(),
215 ),
216 execution_info: None,
217 ..Default::default()
218 }),
219 ..Default::default()
220 },
221 fsys::Instance {
222 moniker: Some("./core".to_string()),
223 url: Some("fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string()),
224 instance_id: None,
225 resolved_info: Some(fsys::ResolvedInfo {
226 resolved_url: Some(
227 "fuchsia-pkg://fuchsia.com/core#meta/core.cm".to_string(),
228 ),
229 execution_info: Some(fsys::ExecutionInfo {
230 start_reason: Some("Debugging Workflow".to_string()),
231 ..Default::default()
232 }),
233 ..Default::default()
234 }),
235 ..Default::default()
236 },
237 fsys::Instance {
238 moniker: Some("./core/appmgr".to_string()),
239 url: Some("fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string()),
240 instance_id: None,
241 resolved_info: Some(fsys::ResolvedInfo {
242 resolved_url: Some(
243 "fuchsia-pkg://fuchsia.com/appmgr#meta/appmgr.cm".to_string(),
244 ),
245 execution_info: Some(fsys::ExecutionInfo {
246 start_reason: Some("Debugging Workflow".to_string()),
247 ..Default::default()
248 }),
249 ..Default::default()
250 }),
251 ..Default::default()
252 },
253 ],
254 HashMap::new(),
255 HashMap::new(),
256 HashMap::new(),
257 );
258 query
259 }
260
261 #[fuchsia::test]
262 async fn no_filter() {
263 let query = create_query();
264
265 let instances = get_instances_matching_filter(None, &query).await.unwrap();
266 assert_eq!(
267 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
268 vec![
269 Moniker::root(),
270 Moniker::parse_str("/core").unwrap(),
271 Moniker::parse_str("/core/appmgr").unwrap(),
272 ]
273 );
274 }
275
276 #[fuchsia::test]
277 async fn running_only() {
278 let query = create_query();
279
280 let instances =
281 get_instances_matching_filter(Some(ListFilter::Running), &query).await.unwrap();
282 assert_eq!(
283 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
284 vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
285 );
286 }
287
288 #[fuchsia::test]
289 async fn stopped_only() {
290 let query = create_query();
291
292 let instances =
293 get_instances_matching_filter(Some(ListFilter::Stopped), &query).await.unwrap();
294 assert_eq!(
295 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
296 [Moniker::root()]
297 );
298 }
299
300 #[fuchsia::test]
301 async fn descendants_only() {
302 let query = create_query();
303
304 let instances =
305 get_instances_matching_filter(Some(ListFilter::Descendant("core".to_string())), &query)
306 .await
307 .unwrap();
308 assert_eq!(
309 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
310 vec![Moniker::parse_str("/core").unwrap(), Moniker::parse_str("/core/appmgr").unwrap(),]
311 );
312 }
313
314 #[fuchsia::test]
315 async fn ancestors_only() {
316 let query = create_query();
317
318 let instances =
319 get_instances_matching_filter(Some(ListFilter::Ancestor("core".to_string())), &query)
320 .await
321 .unwrap();
322 assert_eq!(
323 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
324 vec![Moniker::root(), Moniker::parse_str("/core").unwrap()]
325 );
326 }
327
328 #[fuchsia::test]
329 async fn relative_only() {
330 let query = create_query();
331
332 let instances =
333 get_instances_matching_filter(Some(ListFilter::Relative("core".to_string())), &query)
334 .await
335 .unwrap();
336 assert_eq!(
337 instances.iter().map(|i| i.moniker.clone()).collect::<Vec<_>>(),
338 vec![
339 Moniker::root(),
340 Moniker::parse_str("/core").unwrap(),
341 Moniker::parse_str("/core/appmgr").unwrap(),
342 ]
343 );
344 }
345}