graphix-stdlib 0.3.1

A dataflow language for UIs and network programming, standard library
Documentation
use crate::{
    deftype, CachedArgs, CachedArgsAsync, CachedVals, EvalCached, EvalCachedAsync,
};
use anyhow::Result;
use arcstr::{literal, ArcStr};
use compact_str::CompactString;
use graphix_compiler::{errf, ExecCtx, Rt, UserEvent};
use netidx_value::Value;
use parking_lot::Mutex;
use poolshark::local::LPooled;
use std::{
    cell::RefCell,
    fmt,
    path::{Path, PathBuf},
    sync::Arc,
};
use tempfile::TempDir;

mod dir;
mod file;
mod metadata;
mod watch;

#[derive(Debug)]
enum Name {
    Prefix(ArcStr),
    Suffix(ArcStr),
}

struct TempDirArgs {
    dir: Option<ArcStr>,
    name: Option<Name>,
    t: Arc<Mutex<Option<TempDir>>>,
}

impl fmt::Debug for TempDirArgs {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{{ dir: {:?}, name: {:?} }}", self.dir, self.name)
    }
}

#[derive(Debug, Default)]
struct GxTempDirEv {
    current: Arc<Mutex<Option<TempDir>>>,
}

impl EvalCachedAsync for GxTempDirEv {
    const NAME: &str = "fs_tempdir";
    deftype!(
        "fs",
        r#"fn(?#in:[null, string],
              ?#name:[null, `Prefix(string), `Suffix(string)],
              Any)
           -> Result<string, `IOError(string)>"#
    );
    type Args = TempDirArgs;

    fn prepare_args(&mut self, cached: &CachedVals) -> Option<Self::Args> {
        if cached.0.iter().any(|v| v.is_none()) {
            None
        } else {
            let dir = cached.get::<Option<ArcStr>>(0).flatten();
            let name = cached
                .get::<Option<(ArcStr, ArcStr)>>(1)
                .and_then(|v| v)
                .and_then(|(tag, v)| match &*tag {
                    "Prefix" => Some(Name::Prefix(v)),
                    "Suffix" => Some(Name::Suffix(v)),
                    _ => None,
                });
            let _ = cached.get::<Value>(2)?;
            Some(TempDirArgs { dir, name, t: self.current.clone() })
        }
    }

    fn eval(args: Self::Args) -> impl Future<Output = Value> + Send {
        async move {
            let td = tokio::task::spawn_blocking(|| match (args.dir, args.name) {
                (None, None) => TempDir::new(),
                (None, Some(Name::Prefix(pfx))) => TempDir::with_prefix(&*pfx),
                (None, Some(Name::Suffix(sfx))) => TempDir::with_suffix(&*sfx),
                (Some(dir), None) => TempDir::new_in(&*dir),
                (Some(dir), Some(Name::Prefix(pfx))) => {
                    TempDir::with_prefix_in(&*pfx, &*dir)
                }
                (Some(dir), Some(Name::Suffix(sfx))) => {
                    TempDir::with_suffix_in(&*sfx, &*dir)
                }
            })
            .await;
            match td {
                Err(e) => errf!("IOError", "failed to spawn create temp dir {e:?}"),
                Ok(Err(e)) => errf!("IOError", "failed to create temp dir {e:?}"),
                Ok(Ok(td)) => {
                    use std::fmt::Write;
                    let mut buf = CompactString::new("");
                    write!(buf, "{}", td.path().display()).unwrap();
                    *args.t.lock() = Some(td);
                    Value::String(ArcStr::from(buf.as_str()))
                }
            }
        }
    }
}

type GxTempDir = CachedArgsAsync<GxTempDirEv>;

fn convert_path(path: &Path) -> ArcStr {
    thread_local! {
        static BUF: RefCell<String> = RefCell::new(String::new());
    }
    BUF.with_borrow_mut(|buf| {
        buf.clear();
        use std::fmt::Write;
        write!(buf, "{}", path.display()).unwrap();
        ArcStr::from(buf.as_str())
    })
}

#[derive(Debug, Default)]
struct JoinPathEv;

impl EvalCached for JoinPathEv {
    const NAME: &str = "fs_join_path";
    deftype!("fs", "fn(string, @args: [string, Array<string>]) -> string");

    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
        let mut parts: LPooled<Vec<ArcStr>> = LPooled::take();
        for part in from.0.iter() {
            match part {
                None => return None,
                Some(Value::String(s)) => parts.push(s.clone()),
                Some(Value::Array(a)) => {
                    for part in a.iter() {
                        match part {
                            Value::String(s) => parts.push(s.clone()),
                            _ => return None,
                        }
                    }
                }
                _ => return None,
            }
        }
        thread_local! {
            static BUF: RefCell<PathBuf> = RefCell::new(PathBuf::new());
        }
        BUF.with_borrow_mut(|path| {
            path.clear();
            for part in parts.drain(..) {
                path.push(&*part)
            }
            Some(Value::String(convert_path(&path)))
        })
    }
}

type JoinPath = CachedArgs<JoinPathEv>;

pub(super) fn register<R: Rt, E: UserEvent>(ctx: &mut ExecCtx<R, E>) -> Result<(ArcStr, ArcStr)> {
    ctx.register_builtin::<GxTempDir>()?;
    ctx.register_builtin::<JoinPath>()?;
    ctx.register_builtin::<metadata::IsFile>()?;
    ctx.register_builtin::<metadata::IsDir>()?;
    ctx.register_builtin::<metadata::Metadata>()?;
    ctx.register_builtin::<watch::SetGlobals>()?;
    ctx.register_builtin::<watch::WatchBuiltIn>()?;
    ctx.register_builtin::<watch::WatchFullBuiltIn>()?;
    ctx.register_builtin::<file::ReadAll>()?;
    ctx.register_builtin::<file::ReadAllBin>()?;
    ctx.register_builtin::<file::WriteAll>()?;
    ctx.register_builtin::<file::WriteAllBin>()?;
    ctx.register_builtin::<file::RemoveFile>()?;
    ctx.register_builtin::<dir::ReadDir>()?;
    ctx.register_builtin::<dir::CreateDir>()?;
    ctx.register_builtin::<dir::RemoveDir>()?;
    Ok((literal!(include_str!("fs.gx")), literal!(include_str!("fs.gxi"))))
}