Skip to main content

graphix_package_fs/
lib.rs

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