feat: add command get for doing queries
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
parent
23d68caf71
commit
02dd805db4
25
Cargo.lock
generated
25
Cargo.lock
generated
@ -175,6 +175,7 @@ dependencies = [
|
||||
"dotenv",
|
||||
"fs_extra",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tracing",
|
||||
@ -251,6 +252,12 @@ version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@ -399,6 +406,12 @@ version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@ -425,6 +438,18 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.7"
|
||||
|
@ -16,3 +16,4 @@ serde = { version = "1.0.197", features = ["derive"] }
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
toml = "0.8.19"
|
||||
fs_extra = "1.3.0"
|
||||
serde_json = "1.0.127"
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
ProjectSchema = {
|
||||
name | String
|
||||
name | String,..
|
||||
}
|
||||
}
|
||||
|
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),
|
||||
|
Loading…
Reference in New Issue
Block a user