JustBash.Fs.InMemoryFs (JustBash v0.1.0)

View Source

In-memory filesystem implementation for JustBash.

Provides a complete virtual filesystem with support for:

  • Files (with binary content)
  • Directories
  • Symbolic links
  • File permissions (mode)
  • Modification times

All operations are synchronous and work on an in-memory data structure.

Summary

Functions

Append content to a file, creating it if it doesn't exist.

Get the base name (file name) of a path.

Change file/directory permissions.

Copy a file or directory.

Get the directory name (parent path) of a path.

Check if a path exists in the filesystem.

Get all paths in the filesystem.

Create a hard link.

Get the stat information for a path (does NOT follow symlinks).

Create a directory.

Move/rename a file or directory.

Create a new in-memory filesystem with optional initial files.

Normalize a filesystem path.

Read the contents of a file.

Read directory contents.

Read the target of a symbolic link.

Resolve a path relative to a base path.

Remove a file or directory.

Get the stat information for a path (follows symlinks).

Create a symbolic link.

Write content to a file, creating it if it doesn't exist.

Types

cp_opts()

@type cp_opts() :: [{:recursive, boolean()}]

directory_entry()

@type directory_entry() :: %{
  type: :directory,
  mode: non_neg_integer(),
  mtime: DateTime.t()
}

file_entry()

@type file_entry() :: %{
  type: :file,
  content: binary(),
  mode: non_neg_integer(),
  mtime: DateTime.t()
}

fs_entry()

@type fs_entry() :: file_entry() | directory_entry() | symlink_entry()

mkdir_opts()

@type mkdir_opts() :: [{:recursive, boolean()}]

rm_opts()

@type rm_opts() :: [recursive: boolean(), force: boolean()]

stat_result()

@type stat_result() :: %{
  is_file: boolean(),
  is_directory: boolean(),
  is_symbolic_link: boolean(),
  mode: non_neg_integer(),
  size: non_neg_integer(),
  mtime: DateTime.t()
}

t()

@type t() :: %JustBash.Fs.InMemoryFs{data: %{required(String.t()) => fs_entry()}}

write_opts()

@type write_opts() :: [mode: non_neg_integer(), mtime: DateTime.t()]

Functions

append_file(fs, path, content)

@spec append_file(t(), String.t(), binary()) :: {:ok, t()} | {:error, :eisdir}

Append content to a file, creating it if it doesn't exist.

basename(path)

@spec basename(String.t()) :: String.t()

Get the base name (file name) of a path.

Examples

iex> InMemoryFs.basename("/home/user/file.txt")
"file.txt"

chmod(fs, path, mode)

@spec chmod(t(), String.t(), non_neg_integer()) :: {:ok, t()} | {:error, :enoent}

Change file/directory permissions.

cp(fs, src, dest, opts \\ [])

@spec cp(t(), String.t(), String.t(), cp_opts()) ::
  {:ok, t()} | {:error, :enoent | :eisdir}

Copy a file or directory.

Options

  • :recursive - copy directory contents recursively (required for directories)

Returns

  • {:ok, updated_fs} on success
  • {:error, :enoent} if source doesn't exist
  • {:error, :eisdir} if source is directory and not recursive

dirname(path)

@spec dirname(String.t()) :: String.t()

Get the directory name (parent path) of a path.

Examples

iex> InMemoryFs.dirname("/home/user/file.txt")
"/home/user"
iex> InMemoryFs.dirname("/file.txt")
"/"

exists?(in_memory_fs, path)

@spec exists?(t(), String.t()) :: boolean()

Check if a path exists in the filesystem.

Examples

iex> fs = InMemoryFs.new(%{"/file.txt" => "hello"})
iex> InMemoryFs.exists?(fs, "/file.txt")
true
iex> InMemoryFs.exists?(fs, "/nonexistent")
false

get_all_paths(in_memory_fs)

@spec get_all_paths(t()) :: [String.t()]

Get all paths in the filesystem.

link(fs, existing_path, new_path)

@spec link(t(), String.t(), String.t()) ::
  {:ok, t()} | {:error, :enoent | :eperm | :eexist}

Create a hard link.

lstat(in_memory_fs, path)

@spec lstat(t(), String.t()) :: {:ok, stat_result()} | {:error, :enoent}

Get the stat information for a path (does NOT follow symlinks).

mkdir(fs, path, opts \\ [])

@spec mkdir(t(), String.t(), mkdir_opts()) :: {:ok, t()} | {:error, :eexist | :enoent}

Create a directory.

Options

  • :recursive - create parent directories if needed (default: false)

Returns

  • {:ok, updated_fs} on success
  • {:error, :eexist} if path already exists (and not recursive with existing dir)
  • {:error, :enoent} if parent doesn't exist (and not recursive)

mv(fs, src, dest)

@spec mv(t(), String.t(), String.t()) :: {:ok, t()} | {:error, :enoent}

Move/rename a file or directory.

new(initial_files \\ %{})

@spec new(map()) :: t()

Create a new in-memory filesystem with optional initial files.

Options

Initial files can be provided as a map:

  • Simple form: %{"/path/to/file" => "content"}
  • Extended form: %{"/path/to/file" => %{content: "content", mode: 0o755, mtime: ~U[...]}}

Examples

iex> fs = InMemoryFs.new()
iex> fs = InMemoryFs.new(%{"/home/user/file.txt" => "hello"})
iex> fs = InMemoryFs.new(%{"/bin/script" => %{content: "#!/bin/bash", mode: 0o755}})

normalize_path(path)

@spec normalize_path(String.t()) :: String.t()

Normalize a filesystem path.

Handles:

  • Empty paths and "/" -> "/"
  • Trailing slashes removal
  • Resolving "." and ".." components
  • Ensuring leading "/"

Examples

iex> InMemoryFs.normalize_path("/home/user/../user/./file")
"/home/user/file"

read_file(fs, path)

@spec read_file(t(), String.t()) ::
  {:ok, binary()} | {:error, :enoent | :eisdir | :eloop}

Read the contents of a file.

Returns

  • {:ok, content} - binary content of the file
  • {:error, :enoent} - file does not exist
  • {:error, :eisdir} - path is a directory
  • {:error, :eloop} - too many symlink levels

readdir(in_memory_fs, path)

@spec readdir(t(), String.t()) :: {:ok, [String.t()]} | {:error, :enoent | :enotdir}

Read directory contents.

Returns

  • {:ok, entries} - list of entry names (not full paths), sorted
  • {:error, :enoent} - directory does not exist
  • {:error, :enotdir} - path is not a directory

readlink(in_memory_fs, path)

@spec readlink(t(), String.t()) :: {:ok, String.t()} | {:error, :enoent | :einval}

Read the target of a symbolic link.

resolve_path(base, path)

@spec resolve_path(String.t(), String.t()) :: String.t()

Resolve a path relative to a base path.

Examples

iex> InMemoryFs.resolve_path("/home/user", "file.txt")
"/home/user/file.txt"
iex> InMemoryFs.resolve_path("/home/user", "/etc/passwd")
"/etc/passwd"

rm(fs, path, opts \\ [])

@spec rm(t(), String.t(), rm_opts()) :: {:ok, t()} | {:error, :enoent | :enotempty}

Remove a file or directory.

Options

  • :recursive - remove directory contents recursively (default: false)
  • :force - don't error if path doesn't exist (default: false)

Returns

  • {:ok, updated_fs} on success
  • {:error, :enoent} if path doesn't exist (and not force)
  • {:error, :enotempty} if directory has contents (and not recursive)

stat(fs, path)

@spec stat(t(), String.t()) :: {:ok, stat_result()} | {:error, :enoent}

Get the stat information for a path (follows symlinks).

Returns

{:ok, stat} with stat containing:

  • :is_file - boolean
  • :is_directory - boolean
  • :is_symbolic_link - always false (stat follows symlinks)
  • :mode - file permissions
  • :size - byte size (0 for directories)
  • :mtime - modification time

Errors

  • {:error, :enoent} - path does not exist

symlink(fs, target, link_path)

@spec symlink(t(), String.t(), String.t()) :: {:ok, t()} | {:error, :eexist}

Create a symbolic link.

write_file(fs, path, content, opts \\ [])

@spec write_file(t(), String.t(), binary(), write_opts()) ::
  {:ok, t()} | {:error, :eisdir}

Write content to a file, creating it if it doesn't exist.

Parent directories are created automatically.

Options

  • :mode - file permissions (default: 0o644)
  • :mtime - modification time (default: now)

Returns

  • {:ok, updated_fs} on success
  • {:error, :eisdir} if path is a directory