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