diff --git a/CHANGELOG.md b/CHANGELOG.md index fc448e9..ff7abbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## v2.4.0 - 7 March 2026 +- Use `raw` option for write and append operations on Erlang +- Add `delete_file` function for specifically deleting a file (uses `raw` on Erlang as well) + ## v2.3.2 - 26 December 2025 - Fix bug where unknown errors were not properly converted to the Unknown variant in Erlang ffi. diff --git a/gleam.toml b/gleam.toml index b974558..92e39e5 100644 --- a/gleam.toml +++ b/gleam.toml @@ -1,5 +1,5 @@ name = "simplifile" -version = "2.3.2" +version = "2.4.0" description = "Basic file operations that work on all targets" licences = ["Apache-2.0"] diff --git a/src/simplifile.gleam b/src/simplifile.gleam index b38c40c..5ab3810 100644 --- a/src/simplifile.gleam +++ b/src/simplifile.gleam @@ -334,6 +334,13 @@ pub fn write( @external(javascript, "./simplifile_js.mjs", "delete_") pub fn delete(file_or_dir_at path: String) -> Result(Nil, FileError) +/// Delete a file. +/// On Erlang, if you're specifically deleting a single file, this function +/// should be more efficient than `delete` because it can pass the raw option. +@external(erlang, "simplifile_erl", "delete_file") +@external(javascript, "./simplifile_js.mjs", "deleteFile") +pub fn delete_file(at path: String) -> Result(Nil, FileError) + /// Delete all files/directories specified in a list of paths. /// Recursively deletes provided directories. /// Does not return an error if one or more of the provided paths diff --git a/src/simplifile_erl.erl b/src/simplifile_erl.erl index a1d138f..f997758 100644 --- a/src/simplifile_erl.erl +++ b/src/simplifile_erl.erl @@ -103,20 +103,20 @@ read_bits(Filename) -> %% Write bytes to a file write_bits(Filename, Contents) -> case bit_size(Contents) rem 8 of - 0 -> posix_result(file:write_file(Filename, Contents)); + 0 -> posix_result(file:write_file(Filename, Contents, [raw])); _ -> {error, einval} end. %% Append bytes to a file append_bits(Filename, Contents) -> case bit_size(Contents) rem 8 of - 0 -> posix_result(file:write_file(Filename, Contents, [append])); + 0 -> posix_result(file:write_file(Filename, Contents, [append, raw])); _ -> {error, einval} end. %% Delete the file at the given path delete_file(Filename) -> - posix_result(file:delete(Filename)). + posix_result(file:delete(Filename, [raw])). %% Create a directory at the given path. Missing parent directories are not created. create_directory(Dir) -> diff --git a/src/simplifile_js.mjs b/src/simplifile_js.mjs index 1c003df..6b82923 100644 --- a/src/simplifile_js.mjs +++ b/src/simplifile_js.mjs @@ -182,6 +182,18 @@ export function delete_(fileOrDirPath) { }); } +/** + * Specifically delete a single file. + * + * @param {string} filePath + * @returns {Ok | GError} + */ +export function deleteFile(filePath) { + return gleamResult(() => { + fs.unlinkSync(path.normalize(filePath)); + }) +} + /** * List the contents of a directory. * diff --git a/test/simplifile_test.gleam b/test/simplifile_test.gleam index 0cbe3c2..6eaa930 100644 --- a/test/simplifile_test.gleam +++ b/test/simplifile_test.gleam @@ -13,11 +13,11 @@ import simplifile.{ Esrch, Estale, Etxtbsy, Exdev, Execute, File, FilePermissions, NotUtf8, Read, Unknown, Write, append, append_bits, copy, copy_directory, copy_file, create_directory, create_directory_all, create_file, create_link, - create_symlink, delete, delete_all, file_info, file_info_permissions, - file_info_permissions_octal, file_info_type, file_permissions_to_octal, - get_files, is_directory, is_file, is_symlink, link_info, read, read_bits, - read_directory, rename, set_permissions, set_permissions_octal, write, - write_bits, + create_symlink, delete, delete_all, delete_file, file_info, + file_info_permissions, file_info_permissions_octal, file_info_type, + file_permissions_to_octal, get_files, is_directory, is_file, is_symlink, + link_info, read, read_bits, read_directory, rename, set_permissions, + set_permissions_octal, write, write_bits, } pub fn main() { @@ -357,6 +357,31 @@ pub fn delete_test() { let assert Ok(False) = is_file("./tmp/" <> the_target) } +pub fn delete_file_test() { + // Basic delete + let assert Ok(_) = write("Hello", to: "./tmp/existing_file_2.txt") + let assert Ok(_) = delete_file("./tmp/existing_file_2.txt") + let assert Error(Enoent) = read("./tmp/existing_file_2.txt") + + // Deleting a file that doesn't exist throws an error + let assert Error(Enoent) = delete_file("./idontexist") + + // Delete a symlink doesn't delete the target + let the_target = "target_of_created_symlink_2" + let the_symlink = "./tmp/created_symlink_2" + let assert Ok(_) = + "" + |> write(to: "./tmp/" <> the_target) + let assert Ok(_) = create_symlink(the_target, the_symlink) + let assert Ok(True) = is_file("./tmp/" <> the_target) + let assert Ok(True) = is_symlink(the_symlink) + let assert Ok(_) = delete_file(the_symlink) + let assert Ok(False) = is_symlink(the_symlink) + let assert Ok(True) = is_file("./tmp/" <> the_target) + let assert Ok(_) = delete_file(at: "./tmp/" <> the_target) + let assert Ok(False) = is_file("./tmp/" <> the_target) +} + pub fn delete_all_test() { let files = list.map([1, 2, 3, 4, 5], fn(item) {