1use crate::walk::Context;
2
3#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
4pub enum Change {
5 #[default]
6 None,
7 Add {
8 id: Option<String>,
9 uri: Option<String>,
10 flake: bool,
12 },
13 Remove {
14 ids: Vec<ChangeId>,
15 },
16 Change {
17 id: Option<String>,
18 uri: Option<String>,
19 ref_or_rev: Option<String>,
20 },
21 Follows {
25 input: ChangeId,
27 target: String,
29 },
30}
31
32#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
33pub struct ChangeId(String);
34
35impl ChangeId {
36 pub fn follows(&self) -> Option<&str> {
38 self.0.split_once('.').map(|(_, post)| post)
39 }
40
41 pub fn input(&self) -> &str {
43 self.0.split_once('.').map_or(&self.0, |(pre, _)| pre)
44 }
45
46 fn matches(&self, input: &str, follows: Option<&str>) -> bool {
48 if self.input() != input {
49 return false;
50 }
51 match (self.follows(), follows) {
52 (Some(self_follows), Some(f)) => self_follows == f,
53 (Some(_), None) => false,
54 (None, _) => true,
55 }
56 }
57
58 pub fn matches_with_follows(&self, input: &str, follows: Option<String>) -> bool {
59 self.matches(input, follows.as_deref())
60 }
61
62 pub fn matches_with_ctx(&self, follows: &str, ctx: Option<Context>) -> bool {
64 let ctx_input = ctx.and_then(|f| f.level().first().cloned());
65 match ctx_input {
66 Some(input) => self.matches(&input, Some(follows)),
67 None => self.input() == follows,
68 }
69 }
70}
71
72impl std::fmt::Display for ChangeId {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 write!(f, "{}", self.0)
75 }
76}
77
78impl From<String> for ChangeId {
79 fn from(value: String) -> Self {
80 ChangeId(value)
81 }
82}
83
84impl Change {
85 pub fn id(&self) -> Option<ChangeId> {
86 match self {
87 Change::None => None,
88 Change::Add { id, .. } => id.clone().map(|id| id.into()),
89 Change::Remove { ids } => ids.first().cloned(),
90 Change::Change { id, .. } => id.clone().map(|id| id.into()),
91 Change::Follows { input, .. } => Some(input.clone()),
92 }
93 }
94
95 pub fn ids(&self) -> Vec<ChangeId> {
96 match self {
97 Change::Remove { ids } => ids.clone(),
98 Change::Follows { input, .. } => vec![input.clone()],
99 _ => self.id().into_iter().collect(),
100 }
101 }
102 pub fn is_remove(&self) -> bool {
103 matches!(self, Change::Remove { .. })
104 }
105 pub fn is_some(&self) -> bool {
106 !matches!(self, Change::None)
107 }
108 pub fn is_add(&self) -> bool {
109 matches!(self, Change::Add { .. })
110 }
111 pub fn is_change(&self) -> bool {
112 matches!(self, Change::Change { .. })
113 }
114 pub fn is_follows(&self) -> bool {
115 matches!(self, Change::Follows { .. })
116 }
117 pub fn uri(&self) -> Option<&String> {
118 match self {
119 Change::Change { uri, .. } | Change::Add { uri, .. } => uri.as_ref(),
120 _ => None,
121 }
122 }
123 pub fn follows_target(&self) -> Option<&String> {
124 match self {
125 Change::Follows { target, .. } => Some(target),
126 _ => None,
127 }
128 }
129
130 pub fn success_messages(&self) -> Vec<String> {
131 match self {
132 Change::Add { id, uri, .. } => {
133 vec![format!(
134 "Added input: {} = {}",
135 id.as_deref().unwrap_or("?"),
136 uri.as_deref().unwrap_or("?")
137 )]
138 }
139 Change::Remove { ids } => ids
140 .iter()
141 .map(|id| format!("Removed input: {}", id))
142 .collect(),
143 Change::Change { id, uri, .. } => {
144 vec![format!(
145 "Changed input: {} -> {}",
146 id.as_deref().unwrap_or("?"),
147 uri.as_deref().unwrap_or("?")
148 )]
149 }
150 Change::Follows { input, target } => {
151 vec![format!(
152 "Added follows: {}.inputs.{}.follows = \"{}\"",
153 input.input(),
154 input.follows().unwrap_or("?"),
155 target
156 )]
157 }
158 Change::None => vec![],
159 }
160 }
161}