Skip to main content

elly_host/
lib.rs

1//! What every Elly embedder wants and `elly-core` does not have to know about:
2//! the official file-system module resolver ([`FsResolver`]), the `IO`
3//! capability modules ([`io`]), and running a program's `__main` against them
4//! ([`Io::run`]). See `docs/done/2026-10-02_elly-host.md`.
5//!
6//! Everything that touches the outside world goes through [`Sys`], so a host
7//! without a real OS (the wasm playground) can supply its own. [`StdSys`] is the
8//! one `std` implementation, for native targets and `wasm32-wasip1`.
9
10mod io;
11mod resolve;
12
13use std::io::Write;
14use std::path::{Path, PathBuf};
15
16pub use io::{io, Io, Run};
17pub use resolve::{is_relative_spec, search_path, FsResolver, PATH_VAR};
18
19/// Which standard stream a write goes to.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Stream {
22    Out,
23    Err,
24}
25
26/// The system surface a host offers Elly: output, module sources, the
27/// environment and the way out. The resolver and `IO` reach the outside world
28/// only through this.
29pub trait Sys {
30    /// Write `s` to `stream` and flush it.
31    fn write(&self, stream: Stream, s: &str) -> std::io::Result<()>;
32
33    /// The canonical form of `path`, with symlinks resolved, or `None` if there
34    /// is nothing there. A resolved module's canonical name is its real path.
35    fn real_path(&self, path: &Path) -> Option<PathBuf>;
36
37    /// The text of the file at `path`, or `None` if it is not a readable UTF-8
38    /// file.
39    fn read_source(&self, path: &Path) -> Option<String>;
40
41    /// The environment variable `name`, if it is set and UTF-8.
42    fn var(&self, name: &str) -> Option<String>;
43
44    /// The whole environment. Entries whose name or value is not UTF-8 are
45    /// dropped: Elly strings are UTF-8, and a map that silently lost a key is
46    /// better than a startup that refuses to run.
47    fn vars(&self) -> Vec<(String, String)>;
48
49    /// End the process with `code`, immediately.
50    fn exit(&self, code: i32) -> !;
51}
52
53/// [`Sys`] over the standard library.
54pub struct StdSys;
55
56impl Sys for StdSys {
57    fn write(&self, stream: Stream, s: &str) -> std::io::Result<()> {
58        fn write_to(out: &mut impl Write, s: &str) -> std::io::Result<()> {
59            out.write_all(s.as_bytes())?;
60            out.flush()
61        }
62        match stream {
63            Stream::Out => write_to(&mut std::io::stdout().lock(), s),
64            Stream::Err => write_to(&mut std::io::stderr().lock(), s),
65        }
66    }
67
68    fn real_path(&self, path: &Path) -> Option<PathBuf> {
69        path.canonicalize().ok()
70    }
71
72    fn read_source(&self, path: &Path) -> Option<String> {
73        std::fs::read_to_string(path).ok()
74    }
75
76    fn var(&self, name: &str) -> Option<String> {
77        std::env::var(name).ok()
78    }
79
80    fn vars(&self) -> Vec<(String, String)> {
81        // `vars()` would *panic* on a non-UTF-8 entry, which would make a
82        // program's ability to start depend on someone else's environment.
83        std::env::vars_os()
84            .filter_map(|(k, v)| Some((k.into_string().ok()?, v.into_string().ok()?)))
85            .collect()
86    }
87
88    fn exit(&self, code: i32) -> ! {
89        std::process::exit(code)
90    }
91}