use crate::{ cuddle_state::Cuddle, state::{ validated_project::{Project, Value}, ValidatedState, }, }; pub struct GetCommand { query_engine: ProjectQueryEngine, } impl GetCommand { pub fn new(cuddle: Cuddle) -> Self { Self { query_engine: ProjectQueryEngine::new( &cuddle .state .project .expect("we should always have a project if get command is available"), ), } } pub async fn execute(&self, query: &str) -> anyhow::Result { let res = self .query_engine .query(query)? .ok_or(anyhow::anyhow!("query was not found in project"))?; match res { Value::String(s) => Ok(s), Value::Bool(b) => Ok(b.to_string()), Value::Array(value) => { let val = serde_json::to_string_pretty(&value)?; Ok(val) } Value::Map(value) => { let val = serde_json::to_string_pretty(&value)?; Ok(val) } } } pub fn description() -> String { "get returns a given variable from the project given a key, following a jq like schema (.project.name, etc.)" .into() } } pub struct ProjectQueryEngine { project: Project, } impl ProjectQueryEngine { pub fn new(project: &Project) -> Self { Self { project: project.clone(), } } pub fn query(&self, query: &str) -> anyhow::Result> { let parts = query .split('.') .filter(|i| !i.is_empty()) .collect::>(); Ok(self.traverse(&parts, &self.project.value)) } fn traverse(&self, query: &[&str], value: &Value) -> Option { match query.split_first() { Some((key, rest)) => match value { Value::Map(items) => { let item = items.get(*key)?; self.traverse(rest, item) } _ => { tracing::warn!( "key: {} doesn't have a corresponding value: {:?}", key, value ); None } }, None => Some(value.clone()), } } } #[cfg(test)] mod tests { use std::path::PathBuf; use super::*; #[tokio::test] async fn test_can_query_item() -> anyhow::Result<()> { let project = ProjectQueryEngine::new(&Project { value: Value::Map( [( String::from("project"), Value::Map( [( String::from("name"), Value::String(String::from("something")), )] .into(), ), )] .into(), ), root: PathBuf::new(), }); let res = project.query(".project.name")?; assert_eq!(Some(Value::String("something".into())), res); Ok(()) } }