Omni.Tools.FileSystem.FS (Omni Tools v0.1.0)

Copy Markdown View Source

Filesystem operations scoped to a base directory.

This module is the reusable core of Omni.Tools.FileSystem — it works independently of the tool machinery. Construct an %FS{} with new/1, then call operations directly:

fs = FS.new(base_dir: "/data/workspace", read_only: true)
{:ok, content} = FS.read(fs, "notes/todo.md")
{:ok, entries} = FS.list(fs)

All user-supplied ids are validated against a path policy before any disk access. See resolve/2 for the rules.

Path resolution follows symlinks (inherits File.* behaviour). This module does not attempt to detect or block symlink escapes — it is not a security boundary. OS-level sandboxing is the right tool for that.

Summary

Types

t()

A configured filesystem scope.

Functions

Deletes a file.

Lists all regular files under the base directory.

Creates a new filesystem scope.

Applies a targeted find-and-replace edit to a file.

Reads the content of a file.

Resolves a user-supplied id to an absolute path under the base directory.

Writes content to a file (creates or overwrites).

Types

t()

@type t() :: %Omni.Tools.FileSystem.FS{
  base_dir: String.t(),
  nested?: boolean(),
  read_only?: boolean()
}

A configured filesystem scope.

Functions

delete(fs, id)

@spec delete(t(), String.t()) :: :ok | {:error, term()}

Deletes a file.

:ok = FS.delete(fs, "old-report.html")

list(fs)

@spec list(t()) :: {:ok, [Omni.Tools.FileSystem.Entry.t()]}

Lists all regular files under the base directory.

In nested mode, walks recursively and returns ids as base-relative paths (e.g. "sub/dir/file.txt"). In flat mode, lists only direct children. Includes dotfiles and dot-directories. Results are sorted by id.

{:ok, entries} = FS.list(fs)

new(opts)

@spec new(keyword()) :: t()

Creates a new filesystem scope.

Options

  • :base_dir (required) — absolute path to an existing directory.
  • :read_only — when true, write/patch/delete operations return {:error, :read_only}. Defaults to false.
  • :nested — when true, ids may contain path separators (subdirectories). When false, only bare filenames are accepted. Defaults to true.

Raises ArgumentError if :base_dir is missing, not absolute, or does not exist on disk.

patch(fs, id, search, replace)

@spec patch(t(), String.t(), String.t(), String.t()) ::
  {:ok, Omni.Tools.FileSystem.Entry.t()} | {:error, term()}

Applies a targeted find-and-replace edit to a file.

The search string must appear exactly once in the file. Returns an error if it appears zero times or more than once — the error includes the count so the caller can refine the search string.

{:ok, entry} = FS.patch(fs, "config.json", ~s("v1"), ~s("v2"))

read(fs, id)

@spec read(t(), String.t()) :: {:ok, binary()} | {:error, term()}

Reads the content of a file.

{:ok, content} = FS.read(fs, "notes/todo.md")

resolve(fs, id)

@spec resolve(t(), String.t()) ::
  {:ok, String.t()} | {:error, {:invalid_id, String.t()}}

Resolves a user-supplied id to an absolute path under the base directory.

Returns {:ok, abs_path} or {:error, {:invalid_id, message}}.

Path policy

  • Must be non-empty.
  • Must be relative (no leading /, ~/, or .. segments).
  • Must not contain null bytes.
  • In flat mode, must not contain path separators (/ or \).

write(fs, id, content)

@spec write(t(), String.t(), binary()) ::
  {:ok, Omni.Tools.FileSystem.Entry.t()} | {:error, term()}

Writes content to a file (creates or overwrites).

In nested mode, parent directories are created automatically. Returns {:ok, %Entry{}} on success.

{:ok, entry} = FS.write(fs, "report.html", "<h1>Hello</h1>")