feat: refactor projects

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2024-10-26 22:27:16 +02:00
parent 350a3669b0
commit 6729f6e794
20 changed files with 614 additions and 220 deletions

View File

@@ -5,6 +5,10 @@ edition = "2021"
version.workspace = true
[dependencies]
cuddle-actions.workspace = true
cuddle-actions-api.workspace = true
cuddle-value.workspace = true
anyhow.workspace = true
tokio.workspace = true
tracing.workspace = true
@@ -15,9 +19,9 @@ serde.workspace = true
serde_json.workspace = true
uuid.workspace = true
toml = "0.8.19"
fs_extra = "1.3.0"
dirs = "5.0.1"
walkdir = "2.5.0"
sha256 = "1.5.0"
blake3 = "1.5.4"
toml.workspace = true
dirs.workspace = true
fs_extra.workspace = true
walkdir.workspace = true
sha256.workspace = true
blake3.workspace = true

View File

@@ -1,4 +1,4 @@
use cuddle_actions::AddActionOptions;
use cuddle_actions_sdk::AddActionOptions;
#[tokio::main]
async fn main() -> anyhow::Result<()> {

View File

@@ -1,163 +1 @@
use std::{
collections::BTreeMap,
future::Future,
ops::Deref,
path::{Path, PathBuf},
pin::Pin,
sync::Arc,
};
use rust_builder::RustActionsBuilder;
use serde::{Deserialize, Serialize};
use crate::state::validated_project::Value;
pub mod builder;
pub mod rust_builder;
pub struct Actions {
variants: Vec<ActionVariant>,
}
impl Actions {
pub async fn new(path: &Path, value: &Value) -> anyhow::Result<Option<Self>> {
let Some(project) = value.get(&["project", "actions"]) else {
tracing::debug!("found no actions folder");
return Ok(None);
};
let mut variants = Vec::default();
if let Some(Value::Bool(true)) = project.get(&["rust"]) {
tracing::debug!("rust actions enabled");
variants.push(ActionVariant::Rust {
root_path: path.to_path_buf(),
});
} else {
tracing::trace!("rust actions not enabled");
}
Ok(Some(Self { variants }))
}
pub async fn build(&mut self) -> anyhow::Result<ExecutableActions> {
let mut executable_actions = Vec::default();
self.clean_cache().await?;
for variant in &mut self.variants {
match variant {
ActionVariant::Rust { root_path } => {
let actions = RustActionsBuilder::new(root_path.clone()).build().await?;
executable_actions.push(actions);
}
ActionVariant::Docker => todo!(),
}
}
let mut exec_actions = ExecutableActions::default();
for mut actions in executable_actions {
exec_actions.actions.append(&mut actions.actions);
}
Ok(exec_actions)
}
fn global_registry(&self) -> anyhow::Result<Option<PathBuf>> {
if let Some(dir) = dirs::cache_dir().map(|c| c.join("sh.cuddle/registry/actions/rust")) {
if !dir.exists() {
std::fs::create_dir_all(&dir)?;
}
Ok(Some(dir))
} else {
Ok(None)
}
}
/// clean_cache checks whether a given function has been used in the last month, if it hasn't it is automatically removed, and potentially reconstructed again on demand
pub async fn clean_cache(&mut self) -> anyhow::Result<()> {
let now = std::time::SystemTime::now();
let mut file_stream = tokio::fs::read_dir(
self.global_registry()?
.ok_or(anyhow::anyhow!("failed to get global registry"))?,
)
.await?;
while let Ok(Some(entry)) = file_stream.next_entry().await {
tracing::trace!("checking file: {}", entry.path().display());
let metadata = entry.metadata().await?;
if metadata.is_dir() {
let modified = metadata.modified()?;
let cache_threshold = now
.checked_sub(std::time::Duration::from_secs(60 * 24 * 30)) // Cache duration is a month
.expect("to be able to have a systemtime above a week");
if modified < cache_threshold {
tracing::debug!("cleaning up entry: {}", entry.path().display());
tokio::fs::remove_dir_all(entry.path()).await?;
}
}
}
Ok(())
}
}
pub enum ActionVariant {
Rust { root_path: PathBuf },
Docker,
}
#[derive(Default)]
pub struct ExecutableActions {
pub actions: Vec<ExecutableAction>,
}
pub struct ExecutableAction {
pub name: String,
pub description: String,
pub flags: BTreeMap<String, ExecutableActionFlag>,
call_fn: LazyResolve,
}
impl ExecutableAction {
pub async fn call(&self) -> anyhow::Result<()> {
// Bad hack until .call becomes stable
(self.call_fn.0)().await?;
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ExecutableActionFlag {
String,
Bool,
}
struct LazyResolve(
Arc<dyn Fn() -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> + Send + Sync>,
);
impl LazyResolve {
pub fn new(
func: Box<
dyn Fn() -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> + Send + Sync,
>,
) -> Self {
Self(Arc::new(func))
}
}
impl Deref for LazyResolve {
type Target =
Arc<dyn Fn() -> Pin<Box<dyn Future<Output = anyhow::Result<()>> + Send>> + Send + Sync>;
fn deref(&self) -> &Self::Target {
&self.0
}
}

View File

@@ -1,9 +1,8 @@
use cuddle_value::Value;
use crate::{
cuddle_state::Cuddle,
state::{
validated_project::{Project, Value},
ValidatedState,
},
state::{validated_project::Project, ValidatedState},
};
pub struct GetCommand {

View File

@@ -1,7 +1,8 @@
use cuddle_actions::Actions;
use cuddle_actions_api::ExecutableActions;
use validated_project::Project;
use crate::{
actions::{Actions, ExecutableActions},
plan::{self, ClonedPlan, PlanPathExt},
project::{self, ProjectPlan},
schema_validator::SchemaValidator,

View File

@@ -1,10 +1,7 @@
use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};
use std::path::{Path, PathBuf};
use anyhow::anyhow;
use serde::Serialize;
use cuddle_value::Value;
use toml::Table;
use crate::project::CUDDLE_PROJECT_FILE;
@@ -48,43 +45,3 @@ impl Project {
Self::from_file(&cuddle_project_file, path)
}
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize)]
#[serde(untagged)]
pub enum Value {
String(String),
Bool(bool),
Array(Vec<Value>),
Map(BTreeMap<String, Value>),
}
impl From<&toml::Value> for Value {
fn from(value: &toml::Value) -> Self {
match value {
toml::Value::String(s) => Self::String(s.clone()),
toml::Value::Integer(i) => Self::String(i.to_string()),
toml::Value::Float(f) => Self::String(f.to_string()),
toml::Value::Boolean(b) => Self::Bool(*b),
toml::Value::Datetime(dt) => Self::String(dt.to_string()),
toml::Value::Array(array) => Self::Array(array.iter().map(|i| i.into()).collect()),
toml::Value::Table(tbl) => {
Self::Map(tbl.iter().map(|(k, v)| (k.clone(), v.into())).collect())
}
}
}
}
impl Value {
pub fn get(&self, path: &[&str]) -> Option<&Value> {
match path.split_first() {
Some((current, rest)) => match self {
Value::Map(map) => match map.get(&current.to_string()) {
Some(value) => value.get(rest),
None => None,
},
_ => None,
},
None => Some(self),
}
}
}