///|
/// Synchronous virtual file system used by the (pure) processor for include
/// directives, docinfo files, stylesheets and embedded images. Real file
/// access is provided by the `io` package, which preloads files asynchronously.
pub(open) trait Vfs {
/// Contents of the file at `path`, or None if it does not exist / is unreadable.
fn read(Self, String) -> Bytes?
/// Whether `path` names a regular file.
fn is_file(Self, String) -> Bool
/// Whether `path` names a directory.
fn is_dir(Self, String) -> Bool
/// The working directory (absolute, POSIX separators).
fn cwd(Self) -> String
/// The user's home directory.
fn home(Self) -> String
}
///|
/// A file system without any files.
pub struct NullVfs {
priv working_dir : String
}
///|
pub fn NullVfs::new(cwd? : String = "/") -> &Vfs {
({ working_dir: cwd, } : NullVfs)
}
///|
pub impl Vfs for NullVfs with fn read(_self, _path) {
None
}
///|
pub impl Vfs for NullVfs with fn is_file(_self, _path) {
false
}
///|
pub impl Vfs for NullVfs with fn is_dir(_self, _path) {
false
}
///|
pub impl Vfs for NullVfs with fn cwd(self) {
self.working_dir
}
///|
pub impl Vfs for NullVfs with fn home(_self) {
"/"
}
///|
/// An in-memory file system: a map from absolute path to contents.
pub struct MemoryVfs {
files : Map[String, Bytes]
working_dir : String
home_dir : String
}
///|
pub fn MemoryVfs::new(cwd? : String = "/", home? : String = "/") -> MemoryVfs {
{ files: {}, working_dir: cwd, home_dir: home, }
}
///|
/// Adds a text file (encoded as UTF-8).
pub fn MemoryVfs::add_text(
self : MemoryVfs,
path : String,
text : String,
) -> Unit {
self.files[path] = @utf8.encode(text)
}
///|
pub fn MemoryVfs::add_bytes(
self : MemoryVfs,
path : String,
data : Bytes,
) -> Unit {
self.files[path] = data
}
///|
pub impl Vfs for MemoryVfs with fn read(self, path) {
self.files.get(path)
}
///|
pub impl Vfs for MemoryVfs with fn is_file(self, path) {
self.files.contains(path)
}
///|
pub impl Vfs for MemoryVfs with fn is_dir(self, path) {
let prefix = if path.has_suffix("/") { path } else { path + "/" }
for k, _ in self.files {
if k.has_prefix(prefix) {
return true
}
}
path == "/" || path == self.working_dir
}
///|
pub impl Vfs for MemoryVfs with fn cwd(self) {
self.working_dir
}
///|
pub impl Vfs for MemoryVfs with fn home(self) {
self.home_dir
}
///|
/// Decodes file contents as text: strips a UTF-8 BOM, decodes UTF-16 with a
/// BOM, otherwise decodes UTF-8 (invalid sequences become U+FFFD).
pub fn decode_text(data : Bytes) -> String {
let n = data.length()
if n >= 2 && data[0] == 0xFF && data[1] == 0xFE {
let sb = StringBuilder()
let mut i = 2
while i + 1 < n {
let u = data[i].to_int() | (data[i + 1].to_int() << 8)
sb.write_char(u.unsafe_to_char())
i += 2
}
return fix_surrogates(sb.to_string())
}
if n >= 2 && data[0] == 0xFE && data[1] == 0xFF {
let sb = StringBuilder()
let mut i = 2
while i + 1 < n {
let u = (data[i].to_int() << 8) | data[i + 1].to_int()
sb.write_char(u.unsafe_to_char())
i += 2
}
return fix_surrogates(sb.to_string())
}
let start = if n >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
3
} else {
0
}
@utf8.decode_lossy(data[start:])
}
///|
fn fix_surrogates(s : String) -> String {
s
}
///|
/// Decodes file contents using the named encoding (Ruby `File.read` with an
/// encoding mode). Supports UTF-8 (default), ISO-8859-1/Latin-1 and US-ASCII;
/// unknown encodings fall back to UTF-8, like Ruby ignoring invalid names.
pub fn decode_text_with(data : Bytes, encoding : String?) -> String {
match encoding.map(e => @rb.downcase_ascii(e)) {
Some("iso-8859-1")
| Some("iso8859-1")
| Some("latin1")
| Some("binary")
| Some("ascii-8bit") => {
let sb = StringBuilder(size_hint=data.length())
for b in data {
sb.write_char(b.to_int().unsafe_to_char())
}
sb.to_string()
}
_ => decode_text(data)
}
}