kubectl_view_allocations/
sort.rs1use crate::qty::Qty;
2use crate::{Error, QtyByQualifier, TableNode};
3
4#[derive(Debug, Clone, PartialEq)]
5pub enum SortDirection {
6 Asc,
7 Desc,
8}
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum SortColumnName {
12 Usage,
13 Requested,
14 Limits,
15 Allocatable,
16 Free,
17 Name,
18}
19
20#[derive(Debug, Clone)]
21pub struct SortColumn {
22 pub column: SortColumnName,
23 pub direction: SortDirection,
24}
25
26#[allow(clippy::result_large_err)]
27pub fn parse_sort_spec(s: &str) -> Result<Vec<SortColumn>, Error> {
28 s.split(',')
29 .map(|token| {
30 let parts: Vec<&str> = token.split_whitespace().collect();
31 let col_name = parts.first().copied().unwrap_or("").to_lowercase();
32 let direction_str = parts.get(1).copied().unwrap_or("asc").to_lowercase();
33
34 let column = match col_name.as_str() {
35 "usage" | "utilization" => SortColumnName::Usage,
36 "requested" => SortColumnName::Requested,
37 "limits" | "limit" => SortColumnName::Limits,
38 "allocatable" => SortColumnName::Allocatable,
39 "free" => SortColumnName::Free,
40 "name" => SortColumnName::Name,
41 other => {
42 return Err(Error::InvalidSortColumn {
43 name: other.to_string(),
44 });
45 }
46 };
47
48 let direction = match direction_str.as_str() {
49 "desc" => SortDirection::Desc,
50 _ => SortDirection::Asc,
51 };
52
53 Ok(SortColumn { column, direction })
54 })
55 .collect()
56}
57
58pub(crate) fn effective_sort_spec(spec: &[SortColumn], show_utilization: bool) -> Vec<SortColumn> {
59 spec.iter()
60 .filter(|col| show_utilization || col.column != SortColumnName::Usage)
61 .cloned()
62 .collect()
63}
64
65fn compare_qty(a: Option<&Qty>, b: Option<&Qty>) -> std::cmp::Ordering {
66 match (a, b) {
67 (None, None) => std::cmp::Ordering::Equal,
68 (None, Some(_)) => std::cmp::Ordering::Greater,
69 (Some(_), None) => std::cmp::Ordering::Less,
70 (Some(a), Some(b)) => a.cmp(b),
71 }
72}
73
74fn compare_nodes_by(a: &TableNode, b: &TableNode, col: &SortColumn) -> std::cmp::Ordering {
75 let ord = match col.column {
76 SortColumnName::Name => a.key.cmp(&b.key),
77 SortColumnName::Usage => compare_qty(
78 a.quantities.as_ref().and_then(|q| q.utilization.as_ref()),
79 b.quantities.as_ref().and_then(|q| q.utilization.as_ref()),
80 ),
81 SortColumnName::Requested => compare_qty(
82 a.quantities.as_ref().and_then(|q| q.requested.as_ref()),
83 b.quantities.as_ref().and_then(|q| q.requested.as_ref()),
84 ),
85 SortColumnName::Limits => compare_qty(
86 a.quantities.as_ref().and_then(|q| q.limit.as_ref()),
87 b.quantities.as_ref().and_then(|q| q.limit.as_ref()),
88 ),
89 SortColumnName::Allocatable => compare_qty(
90 a.quantities.as_ref().and_then(|q| q.allocatable.as_ref()),
91 b.quantities.as_ref().and_then(|q| q.allocatable.as_ref()),
92 ),
93 SortColumnName::Free => compare_qty(a.free.as_ref(), b.free.as_ref()),
94 };
95 match col.direction {
96 SortDirection::Asc => ord,
97 SortDirection::Desc => ord.reverse(),
98 }
99}
100
101pub(crate) fn sort_children_recursive(
102 nodes: &mut Vec<TableNode>,
103 indices: &mut [usize],
104 depth: usize,
105 resource_depth: usize,
106 sort_spec: &[SortColumn],
107) {
108 let effective: &[SortColumn] = if depth == resource_depth {
113 &[]
114 } else {
115 sort_spec
116 };
117 indices.sort_by(|&a, &b| {
118 for col in effective {
119 let ord = compare_nodes_by(&nodes[a], &nodes[b], col);
120 if ord != std::cmp::Ordering::Equal {
121 return ord;
122 }
123 }
124 nodes[a].key.cmp(&nodes[b].key)
125 });
126 for &i in indices.iter() {
127 let mut ch = std::mem::take(&mut nodes[i].children);
128 sort_children_recursive(nodes, &mut ch, depth + 1, resource_depth, sort_spec);
129 nodes[i].children = ch;
130 }
131}
132
133pub(crate) fn flatten_tree(
134 nodes: &[TableNode],
135 indices: &[usize],
136) -> Vec<(Vec<String>, Option<QtyByQualifier>, Option<Qty>)> {
137 let mut out = vec![];
138 for &i in indices {
139 out.push((
140 nodes[i].path.clone(),
141 nodes[i].quantities.clone(),
142 nodes[i].free.clone(),
143 ));
144 out.extend(flatten_tree(nodes, &nodes[i].children));
145 }
146 out
147}