Current section

Files

Jump to
dream_test src dream_test@file.erl
Raw

src/dream_test@file.erl

-module(dream_test@file).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/dream_test/file.gleam").
-export([error_to_string/1, read/1, write/2, delete/1, delete_files_matching/2]).
-export_type([file_error/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" File operations for dream_test.\n"
"\n"
" Dream Test uses this module internally for snapshot testing and Gherkin file\n"
" parsing. It wraps Erlang file operations with a small, structured error type\n"
" (`FileError`) so callers can decide how to handle failures.\n"
"\n"
" ## Example\n"
"\n"
" Use this in test code (for example inside a snippet `tests()` function).\n"
"\n"
" ```gleam\n"
" let path = tmp_path()\n"
"\n"
" // Setup: create the file (no assertions during setup)\n"
" use _ <- result.try(\n"
" write(path, \"hello\") |> result.map_error(error_to_string),\n"
" )\n"
"\n"
" read(path)\n"
" |> should\n"
" |> be_equal(Ok(\"hello\"))\n"
" |> or_fail_with(\"expected to read back written content\")\n"
" ```\n"
).
-type file_error() :: {not_found, binary()} |
{permission_denied, binary()} |
{is_directory, binary()} |
{no_space, binary()} |
{file_system_error, binary(), binary()}.
-file("src/dream_test/file.gleam", 107).
?DOC(
" Convert a `FileError` to a human-readable string.\n"
"\n"
" Formats the error with both the error type and the affected path,\n"
" suitable for logging or displaying to users.\n"
"\n"
" ## Parameters\n"
"\n"
" - `error`: the `FileError` value to format\n"
"\n"
" ## Returns\n"
"\n"
" A human-readable message string. This function is pure (no I/O).\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" error_to_string(NotFound(\"/x\"))\n"
" |> should\n"
" |> be_equal(\"File not found: /x\")\n"
" |> or_fail_with(\"expected NotFound formatting\")\n"
" ```\n"
).
-spec error_to_string(file_error()) -> binary().
error_to_string(Error) ->
case Error of
{not_found, Path} ->
<<"File not found: "/utf8, Path/binary>>;
{permission_denied, Path@1} ->
<<"Permission denied: "/utf8, Path@1/binary>>;
{is_directory, Path@2} ->
<<"Is a directory: "/utf8, Path@2/binary>>;
{no_space, Path@3} ->
<<"No space left on device: "/utf8, Path@3/binary>>;
{file_system_error, Path@4, Reason} ->
<<<<<<"File error ("/utf8, Reason/binary>>/binary, "): "/utf8>>/binary,
Path@4/binary>>
end.
-file("src/dream_test/file.gleam", 155).
?DOC(
" Read the entire contents of a file as a UTF-8 string.\n"
"\n"
" Returns the file contents on success, or a `FileError` describing\n"
" what went wrong.\n"
"\n"
" ## Parameters\n"
"\n"
" - `path` - Path to the file (absolute or relative to cwd)\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(String)` - The file contents\n"
" - `Error(NotFound)` - File doesn't exist\n"
" - `Error(PermissionDenied)` - Can't read the file\n"
" - `Error(IsDirectory)` - Path is a directory\n"
" - `Error(FileSystemError)` - Other OS error\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let path = tmp_path()\n"
"\n"
" // Setup: create the file (no assertions during setup)\n"
" use _ <- result.try(\n"
" write(path, \"hello\") |> result.map_error(error_to_string),\n"
" )\n"
"\n"
" read(path)\n"
" |> should\n"
" |> be_equal(Ok(\"hello\"))\n"
" |> or_fail_with(\"expected to read back written content\")\n"
" ```\n"
).
-spec read(binary()) -> {ok, binary()} | {error, file_error()}.
read(Path) ->
dream_test_file_ffi:read_file(Path).
-file("src/dream_test/file.gleam", 187).
?DOC(
" Write a string to a file, creating parent directories if needed.\n"
"\n"
" If the file exists, it will be completely overwritten.\n"
" If parent directories don't exist, they will be created automatically.\n"
"\n"
" ## Parameters\n"
"\n"
" - `path` - Destination path (absolute or relative to cwd)\n"
" - `content` - String content to write\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(Nil)` - File written successfully\n"
" - `Error(PermissionDenied)` - Can't write to the path\n"
" - `Error(NoSpace)` - Disk is full\n"
" - `Error(IsDirectory)` - Path is a directory\n"
" - `Error(FileSystemError)` - Other OS error\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let path = tmp_path()\n"
"\n"
" // Setup: create the file (no assertions during setup)\n"
" use _ <- result.try(\n"
" write(path, \"hello\") |> result.map_error(error_to_string),\n"
" )\n"
" ```\n"
).
-spec write(binary(), binary()) -> {ok, nil} | {error, file_error()}.
write(Path, Content) ->
dream_test_file_ffi:write_file(Path, Content).
-file("src/dream_test/file.gleam", 226).
?DOC(
" Delete a file.\n"
"\n"
" This operation is **idempotent**: deleting a file that doesn't exist\n"
" returns `Ok(Nil)`, not an error. This makes cleanup code simpler.\n"
"\n"
" ## Parameters\n"
"\n"
" - `path` - Path to the file to delete\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(Nil)` - File was deleted (or didn't exist)\n"
" - `Error(PermissionDenied)` - Can't delete the file\n"
" - `Error(IsDirectory)` - Path is a directory (use rmdir)\n"
" - `Error(FileSystemError)` - Other OS error\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let path = tmp_path()\n"
"\n"
" // Setup: create the file, then delete it (no assertions during setup)\n"
" use _ <- result.try(\n"
" write(path, \"hello\") |> result.map_error(error_to_string),\n"
" )\n"
" use _ <- result.try(delete(path) |> result.map_error(error_to_string))\n"
"\n"
" read(path)\n"
" |> should\n"
" |> be_equal(Error(NotFound(path)))\n"
" |> or_fail_with(\"expected deleted file to be NotFound\")\n"
" ```\n"
).
-spec delete(binary()) -> {ok, nil} | {error, file_error()}.
delete(Path) ->
dream_test_file_ffi:delete_file(Path).
-file("src/dream_test/file.gleam", 272).
?DOC(
" Delete all files in a directory that have a specific extension.\n"
"\n"
" Searches the given directory (non-recursively) for files matching\n"
" the extension pattern and deletes them. Returns the count of files\n"
" actually deleted.\n"
"\n"
" ## Parameters\n"
"\n"
" - `directory` - Path to the directory to search\n"
" - `extension` - File extension including the dot (e.g., \".snap\", \".tmp\")\n"
"\n"
" ## Returns\n"
"\n"
" - `Ok(Int)` - Number of files deleted\n"
" - `Error(FileError)` - Directory access failed (the specific variant depends on the underlying file system error)\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" let directory = \"./test/tmp/file_helpers_\" <> int.to_string(unique_port())\n"
" let a = directory <> \"/a.snap\"\n"
" let b = directory <> \"/b.snap\"\n"
" let keep = directory <> \"/keep.txt\"\n"
"\n"
" // Setup: create 2 matching files and 1 non-matching file\n"
" use _ <- result.try(write(a, \"a\") |> result.map_error(error_to_string))\n"
" use _ <- result.try(write(b, \"b\") |> result.map_error(error_to_string))\n"
" use _ <- result.try(\n"
" write(keep, \"keep\") |> result.map_error(error_to_string),\n"
" )\n"
"\n"
" delete_files_matching(directory, \".snap\")\n"
" |> should\n"
" |> be_equal(Ok(2))\n"
" |> or_fail_with(\"expected two deleted snapshots\")\n"
" ```\n"
"\n"
" ## Notes\n"
"\n"
" - Only searches the immediate directory (not subdirectories)\n"
" - Files that can't be deleted are silently skipped\n"
" - The count reflects only successfully deleted files\n"
).
-spec delete_files_matching(binary(), binary()) -> {ok, integer()} |
{error, file_error()}.
delete_files_matching(Directory, Extension) ->
dream_test_file_ffi:delete_files_matching(Directory, Extension).