1use std::str::FromStr;
26use strum::{EnumCount, IntoEnumIterator};
27
28#[derive(
34 Clone,
35 Copy,
36 Debug,
37 Hash,
38 PartialEq,
39 Eq,
40 strum::EnumCount,
41 strum::EnumIter,
42 strum::EnumProperty,
43 strum::EnumString,
44 strum::FromRepr,
45 strum::AsRefStr,
46 strum::IntoStaticStr,
47)]
48#[strum(serialize_all = "kebab-case")]
49#[repr(i32)]
50pub enum PackageStateKind {
51 #[strum(props(pbulk = "prefailed", desc = "PKG_SKIP_REASON set"))]
52 PreSkipped = 0,
53 #[strum(props(pbulk = "prefailed", desc = "PKG_FAIL_REASON set"))]
54 PreFailed = 1,
55 #[strum(props(pbulk = "prefailed", desc = "Has unresolved dependencies"))]
56 Unresolved = 2,
57 #[strum(props(pbulk = "indirect-prefailed", desc = "Blocked by pre-skipped package"))]
58 IndirectPreSkipped = 3,
59 #[strum(props(pbulk = "indirect-prefailed", desc = "Blocked by pre-failed package"))]
60 IndirectPreFailed = 4,
61 #[strum(props(
62 pbulk = "indirect-prefailed",
63 desc = "Blocked by package with unresolved dependencies"
64 ))]
65 IndirectUnresolved = 5,
66 #[strum(props(pbulk = "open", desc = "Ready to build"))]
67 Pending = 6,
68 #[strum(props(pbulk = "done", desc = "Binary already exists"))]
69 UpToDate = 7,
70 #[strum(props(pbulk = "done", desc = "Built successfully"))]
71 Success = 8,
72 #[strum(props(pbulk = "failed", desc = "Build attempted and failed"))]
73 Failed = 9,
74 #[strum(props(
75 pbulk = "indirect-failed",
76 desc = "Blocked by package that failed to build"
77 ))]
78 IndirectFailed = 10,
79}
80
81impl PackageStateKind {
82 pub fn desc(self) -> &'static str {
84 use strum::EnumProperty;
85 self.get_str("desc").expect("desc prop")
86 }
87}
88
89#[derive(
93 Clone,
94 Copy,
95 Debug,
96 PartialEq,
97 Eq,
98 strum::EnumIter,
99 strum::EnumProperty,
100 strum::EnumString,
101 strum::AsRefStr,
102 strum::IntoStaticStr,
103)]
104#[strum(serialize_all = "kebab-case")]
105pub enum PackageStateAlias {
106 #[strum(props(desc = "Any pre-skipped or pre-failed package"))]
107 Skipped,
108 #[strum(props(desc = "Any package blocked by another"))]
109 Blocked,
110 #[strum(props(desc = "Any successful outcome (freshly built or up-to-date)"))]
111 Ok,
112}
113
114impl PackageStateAlias {
115 pub fn expands_to(self) -> &'static [PackageStateKind] {
117 use PackageStateKind::*;
118 match self {
119 Self::Skipped => &[PreSkipped, PreFailed],
120 Self::Blocked => &[
121 IndirectPreSkipped,
122 IndirectPreFailed,
123 IndirectUnresolved,
124 IndirectFailed,
125 ],
126 Self::Ok => &[Success, UpToDate],
127 }
128 }
129
130 pub fn desc(self) -> &'static str {
132 use strum::EnumProperty;
133 self.get_str("desc").expect("desc prop")
134 }
135}
136
137pub fn parse_status_filter(s: &str) -> Result<Vec<PackageStateKind>, String> {
144 if let Ok(k) = s.parse::<PackageStateKind>() {
145 return Ok(vec![k]);
146 }
147 if let Ok(a) = s.parse::<PackageStateAlias>() {
148 return Ok(a.expands_to().to_vec());
149 }
150 Err(format!("unknown status '{s}'"))
151}
152
153#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
165pub enum PackageState {
166 PreSkipped(String),
168 PreFailed(String),
170 Unresolved(String),
172 IndirectPreSkipped(String),
174 IndirectPreFailed(String),
176 IndirectUnresolved(String),
178 Pending,
180 UpToDate,
182 Success,
184 Failed(String),
186 IndirectFailed(String),
188}
189
190impl PackageState {
191 pub fn kind(&self) -> PackageStateKind {
193 match self {
194 Self::PreSkipped(_) => PackageStateKind::PreSkipped,
195 Self::PreFailed(_) => PackageStateKind::PreFailed,
196 Self::Unresolved(_) => PackageStateKind::Unresolved,
197 Self::IndirectPreSkipped(_) => PackageStateKind::IndirectPreSkipped,
198 Self::IndirectPreFailed(_) => PackageStateKind::IndirectPreFailed,
199 Self::IndirectUnresolved(_) => PackageStateKind::IndirectUnresolved,
200 Self::Pending => PackageStateKind::Pending,
201 Self::UpToDate => PackageStateKind::UpToDate,
202 Self::Success => PackageStateKind::Success,
203 Self::Failed(_) => PackageStateKind::Failed,
204 Self::IndirectFailed(_) => PackageStateKind::IndirectFailed,
205 }
206 }
207
208 fn from_kind(kind: PackageStateKind, detail: String) -> Self {
210 match kind {
211 PackageStateKind::PreSkipped => Self::PreSkipped(detail),
212 PackageStateKind::PreFailed => Self::PreFailed(detail),
213 PackageStateKind::Unresolved => Self::Unresolved(detail),
214 PackageStateKind::IndirectPreSkipped => Self::IndirectPreSkipped(detail),
215 PackageStateKind::IndirectPreFailed => Self::IndirectPreFailed(detail),
216 PackageStateKind::IndirectUnresolved => Self::IndirectUnresolved(detail),
217 PackageStateKind::Pending => Self::Pending,
218 PackageStateKind::UpToDate => Self::UpToDate,
219 PackageStateKind::Success => Self::Success,
220 PackageStateKind::Failed => Self::Failed(detail),
221 PackageStateKind::IndirectFailed => Self::IndirectFailed(detail),
222 }
223 }
224
225 pub fn status(&self) -> &'static str {
227 self.kind().into()
228 }
229
230 pub fn pbulk_status(&self) -> &'static str {
234 use strum::EnumProperty;
235 self.kind().get_str("pbulk").expect("pbulk prop")
236 }
237
238 pub fn db_id(&self) -> i32 {
240 self.kind() as i32
241 }
242
243 pub fn from_db(id: i32, detail: Option<String>) -> Option<Self> {
245 PackageStateKind::from_repr(id).map(|k| Self::from_kind(k, detail.unwrap_or_default()))
246 }
247
248 pub fn from_status(s: &str) -> Option<Self> {
250 PackageStateKind::from_str(s)
251 .ok()
252 .map(|k| Self::from_kind(k, String::new()))
253 }
254
255 pub fn detail(&self) -> Option<&str> {
257 match self {
258 Self::Pending | Self::UpToDate | Self::Success => None,
259 Self::PreSkipped(s)
260 | Self::PreFailed(s)
261 | Self::Unresolved(s)
262 | Self::IndirectPreSkipped(s)
263 | Self::IndirectPreFailed(s)
264 | Self::IndirectUnresolved(s)
265 | Self::Failed(s)
266 | Self::IndirectFailed(s) => Some(s),
267 }
268 }
269
270 pub fn is_skip(&self) -> bool {
272 !matches!(self, Self::Success | Self::Failed(_) | Self::UpToDate)
273 }
274
275 pub fn is_direct_skip(&self) -> bool {
277 matches!(
278 self,
279 Self::PreSkipped(_) | Self::PreFailed(_) | Self::Unresolved(_)
280 )
281 }
282
283 pub fn indirect(&self, detail: String) -> Self {
285 match self {
286 Self::PreSkipped(_) | Self::IndirectPreSkipped(_) => Self::IndirectPreSkipped(detail),
287 Self::PreFailed(_) | Self::IndirectPreFailed(_) => Self::IndirectPreFailed(detail),
288 Self::Unresolved(_) | Self::IndirectUnresolved(_) => Self::IndirectUnresolved(detail),
289 Self::IndirectFailed(_) => Self::IndirectFailed(detail),
290 other => other.clone(),
291 }
292 }
293
294 pub fn db_values() -> String {
298 PackageStateKind::iter()
299 .filter(|k| *k != PackageStateKind::Pending)
300 .map(|k| {
301 let s: &'static str = k.into();
302 format!("({}, '{}')", k as i32, s)
303 })
304 .collect::<Vec<_>>()
305 .join(", ")
306 }
307}
308
309#[derive(Clone, Debug)]
315pub struct PackageCounts([usize; PackageStateKind::COUNT]);
316
317impl Default for PackageCounts {
318 fn default() -> Self {
319 Self([0; PackageStateKind::COUNT])
320 }
321}
322
323impl PackageCounts {
324 pub fn add(&mut self, state: &PackageState) {
326 self.0[state.kind() as usize] += 1;
327 }
328
329 pub fn successful(&self) -> usize {
332 self[PackageStateKind::Success] + self[PackageStateKind::UpToDate]
333 }
334
335 pub fn failed(&self) -> usize {
337 self[PackageStateKind::Failed]
338 }
339
340 pub fn up_to_date(&self) -> usize {
342 self[PackageStateKind::UpToDate]
343 }
344
345 pub fn count_alias(&self, alias: PackageStateAlias) -> usize {
347 alias.expands_to().iter().map(|k| self[*k]).sum()
348 }
349
350 pub fn masked(&self) -> usize {
354 use PackageStateKind::*;
355 self[PreSkipped]
356 + self[PreFailed]
357 + self[IndirectPreSkipped]
358 + self[IndirectPreFailed]
359 + self[IndirectUnresolved]
360 + self[IndirectFailed]
361 }
362
363 pub fn total(&self) -> usize {
366 self.successful() + self.failed() + self.masked()
367 }
368}
369
370impl std::ops::Index<PackageStateKind> for PackageCounts {
371 type Output = usize;
372 fn index(&self, kind: PackageStateKind) -> &usize {
373 &self.0[kind as usize]
374 }
375}
376
377impl std::fmt::Display for PackageState {
378 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
379 match self.detail() {
380 Some(d) if !d.is_empty() => write!(f, "{}", d),
381 _ => write!(f, "{}", self.status()),
382 }
383 }
384}