feat: add command get for doing queries
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
124
crates/cuddle/src/cli/get_command.rs
Normal file
124
crates/cuddle/src/cli/get_command.rs
Normal file
@@ -0,0 +1,124 @@
|
||||
use crate::{
|
||||
cuddle_state::Cuddle,
|
||||
state::{
|
||||
validated_project::{Project, Value},
|
||||
ValidatedState,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct GetCommand {
|
||||
query_engine: ProjectQueryEngine,
|
||||
}
|
||||
|
||||
impl GetCommand {
|
||||
pub fn new(cuddle: Cuddle<ValidatedState>) -> 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<String> {
|
||||
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<Option<Value>> {
|
||||
let parts = query
|
||||
.split('.')
|
||||
.filter(|i| !i.is_empty())
|
||||
.collect::<Vec<&str>>();
|
||||
|
||||
Ok(self.traverse(&parts, &self.project.value))
|
||||
}
|
||||
|
||||
fn traverse(&self, query: &[&str], value: &Value) -> Option<Value> {
|
||||
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(())
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user