From 20737dcb02ebf091c77b931e5e8b8abb2d4bb164 Mon Sep 17 00:00:00 2001 From: artemiogr97 <57588855+artemiogr97@users.noreply.github.com> Date: Fri, 19 Apr 2024 21:45:35 +0200 Subject: [PATCH] [stdlib] Implement `os.remove` and `os.unlink` (#2310) Add `os.remove` and `os.unlink` functions. Fixes https://github.com/modularml/mojo/issues/2306 --------- Signed-off-by: Artemio Garza Reyna --- stdlib/src/os/__init__.mojo | 2 +- stdlib/src/os/os.mojo | 58 ++++++++++++++++++++++++ stdlib/test/os/test_remove.mojo | 79 +++++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 stdlib/test/os/test_remove.mojo diff --git a/stdlib/src/os/__init__.mojo b/stdlib/src/os/__init__.mojo index 0eaa7005f..b1b7832dc 100644 --- a/stdlib/src/os/__init__.mojo +++ b/stdlib/src/os/__init__.mojo @@ -15,5 +15,5 @@ from .atomic import Atomic from .env import setenv, getenv from .fstat import lstat, stat, stat_result -from .os import abort, listdir +from .os import abort, listdir, remove, unlink from .pathlike import PathLike diff --git a/stdlib/src/os/os.mojo b/stdlib/src/os/os.mojo index 48b80ddb0..511d36151 100644 --- a/stdlib/src/os/os.mojo +++ b/stdlib/src/os/os.mojo @@ -247,3 +247,61 @@ fn abort[ print(message, flush=True) return abort[result]() + + +# ===----------------------------------------------------------------------=== # +# remove/unlink +# ===----------------------------------------------------------------------=== # +fn remove(path: String) raises: + """Removes the specified file. + If the path is a directory or it can not be deleted, an error is raised. + Absolute and relative paths are allowed, relative paths are resolved from cwd. + + Args: + path: The path to the file. + + """ + var error = external_call["unlink", Int](path._as_ptr()) + + if error != 0: + # TODO get error message, the following code prints it + # var error_str = String("Something went wrong") + # _ = external_call["perror", Pointer[NoneType]](error_str._as_ptr()) + # _ = error_str + raise Error("Can not remove file: " + path) + + +fn remove[pathlike: os.PathLike](path: pathlike) raises: + """Removes the specified file. + If the path is a directory or it can not be deleted, an error is raised. + Absolute and relative paths are allowed, relative paths are resolved from cwd. + + Args: + path: The path to the file. + + """ + remove(path.__fspath__()) + + +fn unlink(path: String) raises: + """Removes the specified file. + If the path is a directory or it can not be deleted, an error is raised. + Absolute and relative paths are allowed, relative paths are resolved from cwd. + + Args: + path: The path to the file. + + """ + remove(path) + + +fn unlink[pathlike: os.PathLike](path: pathlike) raises: + """Removes the specified file. + If the path is a directory or it can not be deleted, an error is raised. + Absolute and relative paths are allowed, relative paths are resolved from cwd. + + Args: + path: The path to the file. + + """ + remove(path.__fspath__()) diff --git a/stdlib/test/os/test_remove.mojo b/stdlib/test/os/test_remove.mojo new file mode 100644 index 000000000..bb9a9821c --- /dev/null +++ b/stdlib/test/os/test_remove.mojo @@ -0,0 +1,79 @@ +# ===----------------------------------------------------------------------=== # +# Copyright (c) 2024, Modular Inc. All rights reserved. +# +# Licensed under the Apache License v2.0 with LLVM Exceptions: +# https://llvm.org/LICENSE.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ===----------------------------------------------------------------------=== # +# RUN: %mojo-no-debug %s + +from os import remove, unlink +from os.path import exists +from pathlib import Path + +from testing import assert_true, assert_false, assert_raises + + +fn create_file_and_test_delete_string[ + func: fn (String) raises -> None, name: StringLiteral +](filename: String) raises: + try: + with open(filename, "w"): + pass + except: + assert_true(False, "Failed to create file for test") + + assert_true(exists(filename)) + func(filename) + assert_false(exists(filename), "test with '" + name + "' failed") + + +fn create_file_and_test_delete_path[ + func: fn[pathlike: PathLike] (pathlike) raises -> None, + name: StringLiteral, +](filepath: Path) raises: + try: + with open(filepath.__fspath__(), "w"): + pass + except: + assert_true(False, "Failed to create file for test") + + assert_true(exists(filepath)) + func(filepath) + assert_false(exists(filepath), "test with '" + name + "' failed") + + +fn test_remove() raises: + var cwd_path = Path() + var my_file_path = cwd_path / "my_file.test" + var my_file_name = str(my_file_path) + + # verify that the test file does not exist before starting the test + assert_false( + exists(my_file_name), + "Unexpected file " + my_file_name + " it should not exist", + ) + + # tyring to delete non existing file + with assert_raises(contains="Can not remove file: "): + remove(my_file_name) + with assert_raises(contains="Can not remove file: "): + remove(my_file_path) + + create_file_and_test_delete_string[remove, "remove"](my_file_name) + create_file_and_test_delete_string[unlink, "unlink"](my_file_name) + create_file_and_test_delete_path[remove, "remove"](my_file_path) + create_file_and_test_delete_path[unlink, "unlink"](my_file_path) + + # test with relative path + my_file_name = Path("my_relative_file.test") + create_file_and_test_delete_string[remove, "remove"](my_file_name) + + +def main(): + test_remove()