Skip to main content

graphix_package_fs/
lib.rs

1use arcstr::ArcStr;
2use compact_str::CompactString;
3use graphix_compiler::errf;
4use graphix_package_core::{
5    deftype, CachedArgs, CachedArgsAsync, CachedVals, EvalCached, EvalCachedAsync,
6};
7use netidx_value::Value;
8use parking_lot::Mutex;
9use poolshark::local::LPooled;
10use std::{
11    cell::RefCell,
12    fmt,
13    path::{Path, PathBuf},
14    sync::Arc,
15};
16use tempfile::TempDir;
17
18pub(crate) mod dir;
19pub(crate) mod file;
20pub(crate) mod metadata;
21pub(crate) mod watch;
22
23#[derive(Debug)]
24enum Name {
25    Prefix(ArcStr),
26    Suffix(ArcStr),
27}
28
29pub(crate) struct TempDirArgs {
30    dir: Option<ArcStr>,
31    name: Option<Name>,
32    t: Arc<Mutex<Option<TempDir>>>,
33}
34
35impl fmt::Debug for TempDirArgs {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        write!(f, "{{ dir: {:?}, name: {:?} }}", self.dir, self.name)
38    }
39}
40
41#[derive(Debug, Default)]
42pub(crate) struct GxTempDirEv {
43    current: Arc<Mutex<Option<TempDir>>>,
44}
45
46impl EvalCachedAsync for GxTempDirEv {
47    const NAME: &str = "fs_tempdir";
48    deftype!(
49        r#"fn(?#in:[null, string],
50              ?#name:[null, `Prefix(string), `Suffix(string)],
51              Any)
52           -> Result<string, `IOError(string)>"#
53    );
54    type Args = TempDirArgs;
55
56    fn prepare_args(&mut self, cached: &CachedVals) -> Option<Self::Args> {
57        if cached.0.iter().any(|v| v.is_none()) {
58            None
59        } else {
60            let dir = cached.get::<Option<ArcStr>>(0).flatten();
61            let name = cached
62                .get::<Option<(ArcStr, ArcStr)>>(1)
63                .and_then(|v| v)
64                .and_then(|(tag, v)| match &*tag {
65                    "Prefix" => Some(Name::Prefix(v)),
66                    "Suffix" => Some(Name::Suffix(v)),
67                    _ => None,
68                });
69            let _ = cached.get::<Value>(2)?;
70            Some(TempDirArgs { dir, name, t: self.current.clone() })
71        }
72    }
73
74    fn eval(args: Self::Args) -> impl Future<Output = Value> + Send {
75        async move {
76            let td = tokio::task::spawn_blocking(|| match (args.dir, args.name) {
77                (None, None) => TempDir::new(),
78                (None, Some(Name::Prefix(pfx))) => TempDir::with_prefix(&*pfx),
79                (None, Some(Name::Suffix(sfx))) => TempDir::with_suffix(&*sfx),
80                (Some(dir), None) => TempDir::new_in(&*dir),
81                (Some(dir), Some(Name::Prefix(pfx))) => {
82                    TempDir::with_prefix_in(&*pfx, &*dir)
83                }
84                (Some(dir), Some(Name::Suffix(sfx))) => {
85                    TempDir::with_suffix_in(&*sfx, &*dir)
86                }
87            })
88            .await;
89            match td {
90                Err(e) => errf!("IOError", "failed to spawn create temp dir {e:?}"),
91                Ok(Err(e)) => errf!("IOError", "failed to create temp dir {e:?}"),
92                Ok(Ok(td)) => {
93                    use std::fmt::Write;
94                    let mut buf = CompactString::new("");
95                    write!(buf, "{}", td.path().display()).unwrap();
96                    *args.t.lock() = Some(td);
97                    Value::String(ArcStr::from(buf.as_str()))
98                }
99            }
100        }
101    }
102}
103
104pub(crate) type GxTempDir = CachedArgsAsync<GxTempDirEv>;
105
106pub(crate) fn convert_path(path: &Path) -> ArcStr {
107    thread_local! {
108        static BUF: RefCell<String> = RefCell::new(String::new());
109    }
110    BUF.with_borrow_mut(|buf| {
111        buf.clear();
112        use std::fmt::Write;
113        write!(buf, "{}", path.display()).unwrap();
114        ArcStr::from(buf.as_str())
115    })
116}
117
118#[derive(Debug, Default)]
119pub(crate) struct JoinPathEv;
120
121impl EvalCached for JoinPathEv {
122    const NAME: &str = "fs_join_path";
123    deftype!("fn(string, @args: [string, Array<string>]) -> string");
124
125    fn eval(&mut self, from: &CachedVals) -> Option<Value> {
126        let mut parts: LPooled<Vec<ArcStr>> = LPooled::take();
127        for part in from.0.iter() {
128            match part {
129                None => return None,
130                Some(Value::String(s)) => parts.push(s.clone()),
131                Some(Value::Array(a)) => {
132                    for part in a.iter() {
133                        match part {
134                            Value::String(s) => parts.push(s.clone()),
135                            _ => return None,
136                        }
137                    }
138                }
139                _ => return None,
140            }
141        }
142        thread_local! {
143            static BUF: RefCell<PathBuf> = RefCell::new(PathBuf::new());
144        }
145        BUF.with_borrow_mut(|path| {
146            path.clear();
147            for part in parts.drain(..) {
148                path.push(&*part)
149            }
150            Some(Value::String(convert_path(&path)))
151        })
152    }
153}
154
155pub(crate) type JoinPath = CachedArgs<JoinPathEv>;
156
157#[cfg(test)]
158mod test;
159
160graphix_derive::defpackage! {
161    builtins => [
162        GxTempDir,
163        JoinPath,
164        metadata::IsFile,
165        metadata::IsDir,
166        metadata::Metadata,
167        watch::SetGlobals,
168        watch::WatchBuiltIn,
169        watch::WatchFullBuiltIn,
170        file::ReadAll,
171        file::ReadAllBin,
172        file::WriteAll,
173        file::WriteAllBin,
174        file::RemoveFile,
175        dir::ReadDir,
176        dir::CreateDir,
177        dir::RemoveDir,
178    ],
179}