@@ -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
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use cuddle_actions::AddActionOptions;
|
||||
use cuddle_actions_sdk::AddActionOptions;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
|
@@ -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(¤t.to_string()) {
|
||||
Some(value) => value.get(rest),
|
||||
None => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
None => Some(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user