dev_scope/shared/models/internal/
doctor_group.rs1use crate::models::prelude::{ModelMetadata, V1AlphaDoctorGroup};
2use crate::models::HelpMetadata;
3use crate::shared::models::internal::extract_command_path;
4use derive_builder::Builder;
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, PartialEq, Clone, Builder)]
8#[builder(setter(into))]
9pub struct DoctorGroupAction {
10 pub name: String,
11 pub description: String,
12 pub fix: DoctorGroupActionFix,
13 pub check: DoctorGroupActionCheck,
14 pub required: bool,
15}
16
17#[derive(Debug, PartialEq, Clone, Builder)]
18#[builder(setter(into))]
19pub struct DoctorGroupActionFix {
20 #[builder(default)]
21 pub command: Option<DoctorGroupActionCommand>,
22 #[builder(default)]
23 pub help_text: Option<String>,
24 #[builder(default)]
25 pub help_url: Option<String>,
26}
27
28impl DoctorGroupAction {
29 pub fn make_from(
30 name: &str,
31 description: &str,
32 fix_command: Option<Vec<&str>>,
33 check_path: Option<(&str, Vec<&str>)>,
34 check_command: Option<Vec<&str>>,
35 ) -> Self {
36 Self {
37 required: true,
38 name: name.to_string(),
39 description: description.to_string(),
40 fix: DoctorGroupActionFix {
41 command: fix_command.map(DoctorGroupActionCommand::from),
42 help_text: None,
43 help_url: None,
44 },
45 check: DoctorGroupActionCheck {
46 command: check_command.map(DoctorGroupActionCommand::from),
47 files: check_path.map(|(base, paths)| DoctorGroupCachePath {
48 base_path: PathBuf::from(base),
49 paths: crate::shared::convert_to_string(paths),
50 }),
51 },
52 }
53 }
54}
55
56#[derive(Debug, PartialEq, Clone, Builder)]
57#[builder(setter(into))]
58pub struct DoctorGroupCachePath {
59 pub paths: Vec<String>,
60 pub base_path: PathBuf,
61}
62
63impl From<(&str, Vec<&str>)> for DoctorGroupCachePath {
64 fn from(value: (&str, Vec<&str>)) -> Self {
65 let pb = PathBuf::from(value.0);
66 let paths = crate::shared::convert_to_string(value.1);
67
68 Self {
69 paths,
70 base_path: pb,
71 }
72 }
73}
74
75#[derive(Debug, PartialEq, Clone, Builder)]
76#[builder(setter(into))]
77pub struct DoctorGroupActionCheck {
78 pub command: Option<DoctorGroupActionCommand>,
79 pub files: Option<DoctorGroupCachePath>,
80}
81
82#[derive(Debug, PartialEq, Clone, Builder)]
83#[builder(setter(into))]
84pub struct DoctorGroupActionCommand {
85 pub commands: Vec<String>,
86}
87
88impl From<Vec<&str>> for DoctorGroupActionCommand {
89 fn from(value: Vec<&str>) -> Self {
90 let commands = value.iter().map(|x| x.to_string()).collect();
91 Self { commands }
92 }
93}
94
95impl<T> From<(&Path, Vec<T>)> for DoctorGroupActionCommand
96where
97 String: for<'a> From<&'a T>,
98{
99 fn from((base_path, command_strings): (&Path, Vec<T>)) -> Self {
100 let commands = command_strings
101 .iter()
102 .map(|s| {
103 let exec: String = s.into();
104 extract_command_path(base_path, &exec)
105 })
106 .collect();
107
108 DoctorGroupActionCommand { commands }
109 }
110}
111
112#[derive(Debug, PartialEq, Clone, Builder)]
113#[builder(setter(into))]
114pub struct DoctorGroup {
115 pub full_name: String,
116 pub metadata: ModelMetadata,
117 pub requires: Vec<String>,
118 pub actions: Vec<DoctorGroupAction>,
119}
120
121impl HelpMetadata for DoctorGroup {
122 fn metadata(&self) -> &ModelMetadata {
123 &self.metadata
124 }
125
126 fn full_name(&self) -> String {
127 self.full_name.to_string()
128 }
129}
130
131impl TryFrom<V1AlphaDoctorGroup> for DoctorGroup {
132 type Error = anyhow::Error;
133
134 fn try_from(model: V1AlphaDoctorGroup) -> Result<Self, Self::Error> {
135 let binding = model.containing_dir();
136 let containing_dir = Path::new(&binding);
137 let mut actions: Vec<_> = Default::default();
138 for (count, spec_action) in model.spec.actions.iter().enumerate() {
139 let spec_action = spec_action.clone();
140 let help_text = spec_action
141 .fix
142 .as_ref()
143 .and_then(|x| x.help_text.as_ref().map(|st| st.trim().to_string()).clone());
144 let help_url = spec_action.fix.as_ref().and_then(|x| x.help_url.clone());
145 let fix_command = spec_action.fix.as_ref().map(|commands| {
146 DoctorGroupActionCommand::from((containing_dir, commands.commands.clone()))
147 });
148
149 actions.push(DoctorGroupAction {
150 name: spec_action.name.unwrap_or_else(|| format!("{}", count + 1)),
151 required: spec_action.required,
152 description: spec_action
153 .description
154 .unwrap_or_else(|| "default".to_string()),
155 fix: DoctorGroupActionFix {
156 command: fix_command,
157 help_text,
158 help_url,
159 },
160 check: DoctorGroupActionCheck {
161 command: spec_action
162 .check
163 .commands
164 .map(|commands| DoctorGroupActionCommand::from((containing_dir, commands))),
165 files: spec_action.check.paths.map(|paths| DoctorGroupCachePath {
166 paths,
167 base_path: containing_dir.parent().unwrap().to_path_buf(),
168 }),
169 },
170 })
171 }
172
173 Ok(DoctorGroup {
174 full_name: model.full_name(),
175 metadata: model.metadata,
176 actions,
177 requires: model.spec.needs,
178 })
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use crate::shared::models::parse_models_from_string;
185 use crate::shared::models::prelude::{
186 DoctorGroupAction, DoctorGroupActionCheck, DoctorGroupActionCommand, DoctorGroupActionFix,
187 };
188 use crate::shared::prelude::DoctorGroupCachePath;
189
190 use std::path::Path;
191
192 #[test]
193 fn parse_group_1() {
194 let test_file = format!("{}/examples/group-1.yaml", env!("CARGO_MANIFEST_DIR"));
195 let text = std::fs::read_to_string(test_file).unwrap();
196 let path = Path::new("/foo/bar/.scope/file.yaml");
197 let configs = parse_models_from_string(path, &text).unwrap();
198 assert_eq!(1, configs.len());
199
200 let dg = configs[0].get_doctor_group().unwrap();
201 assert_eq!("foo", dg.metadata.name);
202 assert_eq!(
203 "/foo/bar/.scope/file.yaml",
204 dg.metadata.annotations.file_path.unwrap()
205 );
206 assert_eq!("/foo/bar/.scope", dg.metadata.annotations.file_dir.unwrap());
207 assert_eq!("ScopeDoctorGroup/foo", dg.full_name);
208 assert_eq!(vec!["bar"], dg.requires);
209
210 assert_eq!(
211 dg.actions[0],
212 DoctorGroupAction {
213 name: "1".to_string(),
214 required: false,
215 description: "foo1".to_string(),
216 fix: DoctorGroupActionFix {
217 command: Some(DoctorGroupActionCommand::from(vec![
218 "/foo/bar/.scope/fix1.sh"
219 ])),
220 help_text: Some("There is a good way to fix this, maybe...".to_string()),
221 help_url: Some("https://go.example.com/fixit".to_string()),
222 },
223 check: DoctorGroupActionCheck {
224 command: Some(DoctorGroupActionCommand::from(vec![
225 "/foo/bar/.scope/foo1.sh"
226 ])),
227 files: Some(DoctorGroupCachePath::from((
228 "/foo/bar",
229 vec!["flig/bar/**/*"]
230 )))
231 }
232 }
233 );
234 assert_eq!(
235 dg.actions[1],
236 DoctorGroupAction {
237 name: "2".to_string(),
238 required: true,
239 description: "foo2".to_string(),
240 fix: DoctorGroupActionFix {
241 command: None,
242 help_text: None,
243 help_url: None,
244 },
245 check: DoctorGroupActionCheck {
246 command: Some(DoctorGroupActionCommand::from(vec!["sleep infinity"])),
247 files: Some(DoctorGroupCachePath::from(("/foo/bar", vec!["*/*.txt"])))
248 }
249 }
250 );
251 }
252}