Virtual File System

View Source

An in-memory filesystem that's purely functional: immutable struct, O(1) path lookups, zero side effects. Good for sandboxed environments and poking around in the REPL.

Core API

alias Raxol.Commands.FileSystem

fs = FileSystem.new()

{:ok, fs} = FileSystem.mkdir(fs, "/docs")
{:ok, fs} = FileSystem.create_file(fs, "/docs/readme.txt", "Hello!")
{:ok, entries} = FileSystem.ls(fs, "/docs")        # => ["readme.txt"]
{:ok, content} = FileSystem.cat(fs, "/docs/readme.txt")  # => "Hello!"

{:ok, fs} = FileSystem.cd(fs, "/docs")
FileSystem.pwd(fs)                                  # => "/docs"
FileSystem.exists?(fs, "readme.txt")                # => true

{:ok, info} = FileSystem.stat(fs, "readme.txt")
# => %{type: :file, size: 6, path: "/docs/readme.txt", ...}

{:ok, fs} = FileSystem.rm(fs, "readme.txt")
{:ok, tree} = FileSystem.tree(fs, "/", 3)
# => {"/", :directory, [{"docs", :directory, []}]}

Anything that changes the filesystem returns {:ok, new_fs} or {:error, reason}. Error reasons are atoms: :not_found, :already_exists, :parent_not_found, :not_a_directory, :is_a_directory, :directory_not_empty, :cannot_remove_root. Every non-root node has a directory parent; mkdir/2 and create_file/3 return {:error, :not_a_directory} without changing the filesystem when the parent path is a file.

REPL Integration

Call Evaluator.with_vfs/1 to get a vfs binding and shell-like helpers auto-imported from Raxol.REPL.VfsHelpers:

alias Raxol.REPL.Evaluator

eval = Evaluator.new() |> Evaluator.with_vfs()

{:ok, _, eval} = Evaluator.eval(eval, "vfs = mkdir(vfs, \"/src\")")
{:ok, _, eval} = Evaluator.eval(eval, "vfs = touch(vfs, \"/src/app.ex\", \"defmodule App do\\nend\")")
{:ok, _, eval} = Evaluator.eval(eval, "vfs = ls(vfs)")
{:ok, _, eval} = Evaluator.eval(eval, "vfs = cat(vfs, \"/src/app.ex\")")
{:ok, _, eval} = Evaluator.eval(eval, "tree(vfs)")

Helpers print their output via IO (captured by the evaluator) and return the VFS struct so you can chain them.

HelperWhat it doesMutates VFS?
ls(vfs) / ls(vfs, path)Print directory listingNo
cd(vfs, path)Change working directoryYes
pwd(vfs)Print current directoryNo
cat(vfs, path)Print file contentsNo
mkdir(vfs, path)Create directoryYes
touch(vfs, path) / touch(vfs, path, content)Create fileYes
rm(vfs, path)Remove file or empty dirYes
tree(vfs) / tree(vfs, path, depth)Print directory treeNo
stat(vfs, path)Print node metadataNo

The Evaluator.prelude field imports VfsHelpers before every eval, which is why bare names like ls and mkdir work without any aliasing.

Internals

Internally it's a flat map keyed by absolute path (%{"/" => node, "/docs" => node, ...}). Parent-child relationships are tracked both ways (parents keep a children list). Path resolution handles ., .., absolute paths, relative paths, and - for the previous directory. Timestamps come from System.monotonic_time(:millisecond).

There are also formatting helpers: format_ls/3 for styled directory listings and format_cat/3 for line-numbered file output.

Playground demo

mix raxol.playground has a VFS demo with a shell-like interface: ls, cd, cat, pwd, mkdir, rm, tree, and help all work.