use crate::models::prelude::{ModelMetadata, V1AlphaDoctorGroup};
use crate::models::HelpMetadata;
use crate::shared::models::internal::extract_command_path;
use derive_builder::Builder;
use std::path::{Path, PathBuf};
#[derive(Debug, PartialEq, Clone, Builder)]
#[builder(setter(into))]
pub struct DoctorGroupAction {
pub name: String,
pub description: String,
pub fix: DoctorGroupActionFix,
pub check: DoctorGroupActionCheck,
pub required: bool,
}
#[derive(Debug, PartialEq, Clone, Builder)]
#[builder(setter(into))]
pub struct DoctorGroupActionFix {
#[builder(default)]
pub command: Option<DoctorGroupActionCommand>,
#[builder(default)]
pub help_text: Option<String>,
#[builder(default)]
pub help_url: Option<String>,
}
impl DoctorGroupAction {
pub fn make_from(
name: &str,
description: &str,
fix_command: Option<Vec<&str>>,
check_path: Option<(&str, Vec<&str>)>,
check_command: Option<Vec<&str>>,
) -> Self {
Self {
required: true,
name: name.to_string(),
description: description.to_string(),
fix: DoctorGroupActionFix {
command: fix_command.map(DoctorGroupActionCommand::from),
help_text: None,
help_url: None,
},
check: DoctorGroupActionCheck {
command: check_command.map(DoctorGroupActionCommand::from),
files: check_path.map(|(base, paths)| DoctorGroupCachePath {
base_path: PathBuf::from(base),
paths: crate::shared::convert_to_string(paths),
}),
},
}
}
}
#[derive(Debug, PartialEq, Clone, Builder)]
#[builder(setter(into))]
pub struct DoctorGroupCachePath {
pub paths: Vec<String>,
pub base_path: PathBuf,
}
impl From<(&str, Vec<&str>)> for DoctorGroupCachePath {
fn from(value: (&str, Vec<&str>)) -> Self {
let pb = PathBuf::from(value.0);
let paths = crate::shared::convert_to_string(value.1);
Self {
paths,
base_path: pb,
}
}
}
#[derive(Debug, PartialEq, Clone, Builder)]
#[builder(setter(into))]
pub struct DoctorGroupActionCheck {
pub command: Option<DoctorGroupActionCommand>,
pub files: Option<DoctorGroupCachePath>,
}
#[derive(Debug, PartialEq, Clone, Builder)]
#[builder(setter(into))]
pub struct DoctorGroupActionCommand {
pub commands: Vec<String>,
}
impl From<Vec<&str>> for DoctorGroupActionCommand {
fn from(value: Vec<&str>) -> Self {
let commands = value.iter().map(|x| x.to_string()).collect();
Self { commands }
}
}
impl<T> From<(&Path, Vec<T>)> for DoctorGroupActionCommand
where
String: for<'a> From<&'a T>,
{
fn from((base_path, command_strings): (&Path, Vec<T>)) -> Self {
let commands = command_strings
.iter()
.map(|s| {
let exec: String = s.into();
extract_command_path(base_path, &exec)
})
.collect();
DoctorGroupActionCommand { commands }
}
}
#[derive(Debug, PartialEq, Clone, Builder)]
#[builder(setter(into))]
pub struct DoctorGroup {
pub full_name: String,
pub metadata: ModelMetadata,
pub requires: Vec<String>,
pub actions: Vec<DoctorGroupAction>,
}
impl HelpMetadata for DoctorGroup {
fn metadata(&self) -> &ModelMetadata {
&self.metadata
}
fn full_name(&self) -> String {
self.full_name.to_string()
}
}
impl TryFrom<V1AlphaDoctorGroup> for DoctorGroup {
type Error = anyhow::Error;
fn try_from(model: V1AlphaDoctorGroup) -> Result<Self, Self::Error> {
let binding = model.containing_dir();
let containing_dir = Path::new(&binding);
let mut actions: Vec<_> = Default::default();
for (count, spec_action) in model.spec.actions.iter().enumerate() {
let spec_action = spec_action.clone();
let help_text = spec_action
.fix
.as_ref()
.and_then(|x| x.help_text.as_ref().map(|st| st.trim().to_string()).clone());
let help_url = spec_action.fix.as_ref().and_then(|x| x.help_url.clone());
let fix_command = spec_action.fix.as_ref().map(|commands| {
DoctorGroupActionCommand::from((containing_dir, commands.commands.clone()))
});
actions.push(DoctorGroupAction {
name: spec_action.name.unwrap_or_else(|| format!("{}", count + 1)),
required: spec_action.required,
description: spec_action
.description
.unwrap_or_else(|| "default".to_string()),
fix: DoctorGroupActionFix {
command: fix_command,
help_text,
help_url,
},
check: DoctorGroupActionCheck {
command: spec_action
.check
.commands
.map(|commands| DoctorGroupActionCommand::from((containing_dir, commands))),
files: spec_action.check.paths.map(|paths| DoctorGroupCachePath {
paths,
base_path: containing_dir.parent().unwrap().to_path_buf(),
}),
},
})
}
Ok(DoctorGroup {
full_name: model.full_name(),
metadata: model.metadata,
actions,
requires: model.spec.needs,
})
}
}
#[cfg(test)]
mod tests {
use crate::shared::models::parse_models_from_string;
use crate::shared::models::prelude::{
DoctorGroupAction, DoctorGroupActionCheck, DoctorGroupActionCommand, DoctorGroupActionFix,
};
use crate::shared::prelude::DoctorGroupCachePath;
use std::path::Path;
#[test]
fn parse_group_1() {
let test_file = format!("{}/examples/group-1.yaml", env!("CARGO_MANIFEST_DIR"));
let text = std::fs::read_to_string(test_file).unwrap();
let path = Path::new("/foo/bar/.scope/file.yaml");
let configs = parse_models_from_string(path, &text).unwrap();
assert_eq!(1, configs.len());
let dg = configs[0].get_doctor_group().unwrap();
assert_eq!("foo", dg.metadata.name);
assert_eq!(
"/foo/bar/.scope/file.yaml",
dg.metadata.annotations.file_path.unwrap()
);
assert_eq!("/foo/bar/.scope", dg.metadata.annotations.file_dir.unwrap());
assert_eq!("ScopeDoctorGroup/foo", dg.full_name);
assert_eq!(vec!["bar"], dg.requires);
assert_eq!(
dg.actions[0],
DoctorGroupAction {
name: "1".to_string(),
required: false,
description: "foo1".to_string(),
fix: DoctorGroupActionFix {
command: Some(DoctorGroupActionCommand::from(vec![
"/foo/bar/.scope/fix1.sh"
])),
help_text: Some("There is a good way to fix this, maybe...".to_string()),
help_url: Some("https://go.example.com/fixit".to_string()),
},
check: DoctorGroupActionCheck {
command: Some(DoctorGroupActionCommand::from(vec![
"/foo/bar/.scope/foo1.sh"
])),
files: Some(DoctorGroupCachePath::from((
"/foo/bar",
vec!["flig/bar/**/*"]
)))
}
}
);
assert_eq!(
dg.actions[1],
DoctorGroupAction {
name: "2".to_string(),
required: true,
description: "foo2".to_string(),
fix: DoctorGroupActionFix {
command: None,
help_text: None,
help_url: None,
},
check: DoctorGroupActionCheck {
command: Some(DoctorGroupActionCommand::from(vec!["sleep infinity"])),
files: Some(DoctorGroupCachePath::from(("/foo/bar", vec!["*/*.txt"])))
}
}
);
}
}