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"
" This module provides file I/O operations for internal use by dream_test,\n"
" particularly for snapshot testing and Gherkin file parsing. It wraps\n"
" Erlang's file operations with proper error handling.\n"
"\n"
" ## Error Handling\n"
"\n"
" Unlike many file libraries that return opaque errors, this module provides\n"
" structured `FileError` types that tell you exactly what went wrong:\n"
"\n"
" ```gleam\n"
" case file.read(\"config.json\") {\n"
" Ok(content) -> parse(content)\n"
" Error(NotFound(_)) -> use_defaults()\n"
" Error(PermissionDenied(path)) -> panic as \"Cannot read \" <> path\n"
" Error(error) -> panic as file.error_to_string(error)\n"
" }\n"
" ```\n"
"\n"
" ## Usage Examples\n"
"\n"
" ### Reading Files\n"
"\n"
" ```gleam\n"
" import dream_test/file\n"
"\n"
" case file.read(\"./test/fixtures/expected.json\") {\n"
" Ok(content) -> content\n"
" Error(error) -> {\n"
" io.println(\"Error: \" <> file.error_to_string(error))\n"
" \"\"\n"
" }\n"
" }\n"
" ```\n"
"\n"
" ### Writing Files\n"
"\n"
" ```gleam\n"
" // Creates parent directories automatically\n"
" case file.write(\"./test/snapshots/output.snap\", result) {\n"
" Ok(Nil) -> io.println(\"Saved!\")\n"
" Error(NoSpace(_)) -> io.println(\"Disk full!\")\n"
" Error(error) -> io.println(file.error_to_string(error))\n"
" }\n"
" ```\n"
"\n"
" ### Deleting Files\n"
"\n"
" ```gleam\n"
" // Safe to call even if file doesn't exist\n"
" let _ = file.delete(\"./test/snapshots/old.snap\")\n"
"\n"
" // Delete all snapshots\n"
" case file.delete_files_matching(\"./test/snapshots\", \".snap\") {\n"
" Ok(count) -> io.println(\"Deleted \" <> int.to_string(count) <> \" files\")\n"
" Error(error) -> io.println(file.error_to_string(error))\n"
" }\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", 148).
?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"
" ## Examples\n"
"\n"
" ```gleam\n"
" error_to_string(NotFound(\"/app/config.json\"))\n"
" // -> \"File not found: /app/config.json\"\n"
"\n"
" error_to_string(PermissionDenied(\"/etc/shadow\"))\n"
" // -> \"Permission denied: /etc/shadow\"\n"
"\n"
" error_to_string(FileSystemError(\"/dev/null\", \"ebusy\"))\n"
" // -> \"File error (ebusy): /dev/null\"\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", 201).
?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"
" // Read a configuration file\n"
" case file.read(\"gleam.toml\") {\n"
" Ok(toml) -> parse_config(toml)\n"
" Error(NotFound(_)) -> default_config()\n"
" Error(error) -> panic as file.error_to_string(error)\n"
" }\n"
" ```\n"
"\n"
" ```gleam\n"
" // Read with full error handling\n"
" case file.read(path) {\n"
" Ok(content) -> Ok(content)\n"
" Error(NotFound(_)) -> Error(\"Config file missing\")\n"
" Error(PermissionDenied(_)) -> Error(\"Cannot read config (permission denied)\")\n"
" Error(error) -> Error(file.error_to_string(error))\n"
" }\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", 238).
?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"
" // Write a snapshot file\n"
" case file.write(\"./test/snapshots/user.snap\", json_output) {\n"
" Ok(Nil) -> io.println(\"Snapshot saved\")\n"
" Error(error) -> io.println(\"Failed: \" <> file.error_to_string(error))\n"
" }\n"
" ```\n"
"\n"
" ```gleam\n"
" // Creates nested directories automatically\n"
" file.write(\"./deep/nested/path/file.txt\", \"content\")\n"
" // Creates ./deep/nested/path/ if it doesn't exist\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", 273).
?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"
" // Safe cleanup - doesn't fail if already deleted\n"
" let _ = file.delete(\"./test/temp/output.txt\")\n"
" ```\n"
"\n"
" ```gleam\n"
" // Delete with error handling\n"
" case file.delete(snapshot_path) {\n"
" Ok(Nil) -> io.println(\"Snapshot cleared\")\n"
" Error(PermissionDenied(_)) -> io.println(\"Cannot delete (permission denied)\")\n"
" Error(error) -> io.println(file.error_to_string(error))\n"
" }\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", 314).
?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(FileSystemError)` - Directory access failed\n"
"\n"
" ## Examples\n"
"\n"
" ```gleam\n"
" // Clear all snapshot files\n"
" case file.delete_files_matching(\"./test/snapshots\", \".snap\") {\n"
" Ok(0) -> io.println(\"No snapshots to delete\")\n"
" Ok(n) -> io.println(\"Deleted \" <> int.to_string(n) <> \" snapshots\")\n"
" Error(error) -> io.println(file.error_to_string(error))\n"
" }\n"
" ```\n"
"\n"
" ```gleam\n"
" // Clean up temporary files before test run\n"
" let _ = file.delete_files_matching(\"./test/temp\", \".tmp\")\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).