feat: add command get for doing queries
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
88
crates/cuddle/src/cli.rs
Normal file
88
crates/cuddle/src/cli.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use std::{borrow::BorrowMut, io::Write};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use get_command::GetCommand;
|
||||
|
||||
use crate::{cuddle_state::Cuddle, state::ValidatedState};
|
||||
|
||||
mod get_command;
|
||||
|
||||
pub struct Cli {
|
||||
cli: clap::Command,
|
||||
cuddle: Cuddle<ValidatedState>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn new(cuddle: Cuddle<ValidatedState>) -> Self {
|
||||
let cli = clap::Command::new("cuddle").subcommand_required(true);
|
||||
|
||||
Self { cli, cuddle }
|
||||
}
|
||||
|
||||
pub async fn setup(mut self) -> anyhow::Result<Self> {
|
||||
let commands = self.get_commands().await?;
|
||||
|
||||
self.cli = self.cli.subcommands(commands);
|
||||
|
||||
// TODO: Add global
|
||||
// TODO: Add components
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
async fn get_commands(&self) -> anyhow::Result<Vec<clap::Command>> {
|
||||
Ok(vec![
|
||||
clap::Command::new("do").subcommand_required(true),
|
||||
clap::Command::new("get")
|
||||
.about(GetCommand::description())
|
||||
.arg(
|
||||
clap::Arg::new("query")
|
||||
.required(true)
|
||||
.help("query is how values are extracted, '.project.name' etc"),
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
pub async fn execute(self) -> anyhow::Result<()> {
|
||||
match self
|
||||
.cli
|
||||
.get_matches_from(std::env::args())
|
||||
.subcommand()
|
||||
.ok_or(anyhow::anyhow!("failed to find subcommand"))?
|
||||
{
|
||||
("do", _args) => {
|
||||
tracing::debug!("executing do");
|
||||
}
|
||||
("get", args) => {
|
||||
let query = args
|
||||
.get_one::<String>("query")
|
||||
.ok_or(anyhow!("query is required"))?;
|
||||
|
||||
let res = GetCommand::new(self.cuddle).execute(query).await?;
|
||||
|
||||
std::io::stdout().write_all(res.as_bytes())?;
|
||||
std::io::stdout().write_all("\n".as_bytes())?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_project_commands(&self) -> anyhow::Result<Vec<clap::Command>> {
|
||||
if let Some(_project) = self.cuddle.state.project.as_ref() {
|
||||
// Add project level commands
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
async fn add_plan_commands(self) -> anyhow::Result<Self> {
|
||||
if let Some(_plan) = self.cuddle.state.plan.as_ref() {
|
||||
// Add plan level commands
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
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(())
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
use cli::Cli;
|
||||
use cuddle_state::Cuddle;
|
||||
use state::ValidatedState;
|
||||
|
||||
mod cli;
|
||||
mod cuddle_state;
|
||||
mod plan;
|
||||
mod project;
|
||||
@@ -24,68 +25,3 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Cli {
|
||||
cli: clap::Command,
|
||||
cuddle: Cuddle<ValidatedState>,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn new(cuddle: Cuddle<ValidatedState>) -> Self {
|
||||
let cli = clap::Command::new("cuddle").subcommand_required(true);
|
||||
|
||||
Self { cli, cuddle }
|
||||
}
|
||||
|
||||
pub async fn setup(mut self) -> anyhow::Result<Self> {
|
||||
let commands = self.get_commands().await?;
|
||||
|
||||
self.cli = self.cli.subcommands(commands);
|
||||
|
||||
// TODO: Add global
|
||||
// TODO: Add components
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub async fn execute(self) -> anyhow::Result<()> {
|
||||
match self
|
||||
.cli
|
||||
.get_matches_from(std::env::args())
|
||||
.subcommand()
|
||||
.ok_or(anyhow::anyhow!("failed to find subcommand"))?
|
||||
{
|
||||
("do", _args) => {
|
||||
tracing::debug!("executing do");
|
||||
}
|
||||
("get", _args) => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_commands(&self) -> anyhow::Result<Vec<clap::Command>> {
|
||||
Ok(vec![
|
||||
clap::Command::new("do").subcommand_required(true),
|
||||
clap::Command::new("get"),
|
||||
])
|
||||
}
|
||||
|
||||
async fn add_project_commands(&self) -> anyhow::Result<Vec<clap::Command>> {
|
||||
if let Some(_project) = self.cuddle.state.project.as_ref() {
|
||||
// Add project level commands
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
async fn add_plan_commands(self) -> anyhow::Result<Self> {
|
||||
if let Some(_plan) = self.cuddle.state.plan.as_ref() {
|
||||
// Add plan level commands
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
@@ -4,10 +4,12 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use serde::Serialize;
|
||||
use toml::Table;
|
||||
|
||||
use crate::project::CUDDLE_PROJECT_FILE;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Project {
|
||||
pub value: Value,
|
||||
pub root: PathBuf,
|
||||
@@ -29,6 +31,7 @@ impl Project {
|
||||
.ok_or(anyhow!("cuddle.toml doesn't provide a [project] table"))?;
|
||||
|
||||
let value: Value = project.into();
|
||||
let value = Value::Map([("project".to_string(), value)].into());
|
||||
|
||||
Ok(Self::new(value, root))
|
||||
}
|
||||
@@ -46,6 +49,8 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Value {
|
||||
String(String),
|
||||
Bool(bool),
|
||||
|
Reference in New Issue
Block a user