Skip to content

Commit

Permalink
[External] [stdlib] Implement mkdir and rmdir (#39230)
Browse files Browse the repository at this point in the history
[External] [stdlib] Implement `mkdir` and `rmdir`

Add functionality to the `os` module for creating and
removing directories via `mkdir` and `rmdir`.

Signed-off-by: Artemio Garza Reyna <artemiogr97@gmail.com>

Co-authored-by: Artemio Garza Reyna <artemiogr97@gmail.com>
Closes #2430
MODULAR_ORIG_COMMIT_REV_ID: 8571848227dfd72f1672699a227e21354d5cf3e1
  • Loading branch information
artemiogr97 authored and JoeLoser committed May 11, 2024
1 parent ea1f2a5 commit fab2ec5
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 2 deletions.
4 changes: 4 additions & 0 deletions docs/changelog.md
Expand Up @@ -137,6 +137,10 @@ what we publish.
- Debugger users can now set breakpoints on function calls in O0 builds even if
the call has been inlined by the compiler.

- The `os` module now provides functionalty for adding and removing directories
using `mkdir` and `rmdir`.
([PR #2430](https://github.com/modularml/mojo/pull/2430) by [@artemiogr97](https://github.com/artemiogr97))

### 🦋 Changed

- The `abs` and `round` functions have moved from `math` to `builtin`, so you no
Expand Down
12 changes: 11 additions & 1 deletion stdlib/src/os/__init__.mojo
Expand Up @@ -15,5 +15,15 @@
from .atomic import Atomic
from .env import setenv, getenv
from .fstat import lstat, stat, stat_result
from .os import abort, listdir, remove, unlink, SEEK_SET, SEEK_CUR, SEEK_END
from .os import (
abort,
listdir,
remove,
unlink,
SEEK_SET,
SEEK_CUR,
SEEK_END,
mkdir,
rmdir,
)
from .pathlike import PathLike
65 changes: 64 additions & 1 deletion stdlib/src/os/os.mojo
Expand Up @@ -277,7 +277,7 @@ fn remove(path: String) raises:
path: The path to the file.
"""
var error = external_call["unlink", Int](path.unsafe_ptr())
var error = external_call["unlink", Int32](path.unsafe_ptr())

if error != 0:
# TODO get error message, the following code prints it
Expand Down Expand Up @@ -327,3 +327,66 @@ fn unlink[pathlike: os.PathLike](path: pathlike) raises:
"""
remove(path.__fspath__())


# ===----------------------------------------------------------------------=== #
# mkdir/rmdir
# ===----------------------------------------------------------------------=== #


fn mkdir(path: String, mode: Int = 0o777) raises:
"""Creates a directory at the specified path.
If the directory can not be created an error is raised.
Absolute and relative paths are allowed, relative paths are resolved from cwd.
Args:
path: The path to the directory.
mode: The mode to create the directory with.
"""

var error = external_call["mkdir", Int32](path.unsafe_ptr(), mode)
if error != 0:
raise Error("Can not create directory: " + path)


fn mkdir[pathlike: os.PathLike](path: pathlike, mode: Int = 0o777) raises:
"""Creates a directory at the specified path.
If the directory can not be created an error is raised.
Absolute and relative paths are allowed, relative paths are resolved from cwd.
Parameters:
pathlike: The a type conforming to the os.PathLike trait.
Args:
path: The path to the directory.
mode: The mode to create the directory with.
"""

mkdir(path.__fspath__(), mode)


fn rmdir(path: String) raises:
"""Removes the specified directory.
If the path is not 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 directory.
"""
var error = external_call["rmdir", Int32](path.unsafe_ptr())
if error != 0:
raise Error("Can not remove directory: " + path)


fn rmdir[pathlike: os.PathLike](path: pathlike) raises:
"""Removes the specified directory.
If the path is not a directory or it can not be deleted, an error is raised.
Absolute and relative paths are allowed, relative paths are resolved from cwd.
Parameters:
pathlike: The a type conforming to the os.PathLike trait.
Args:
path: The path to the directory.
"""
rmdir(path.__fspath__())
122 changes: 122 additions & 0 deletions stdlib/test/os/test_mkdir_and_rmdir.mojo
@@ -0,0 +1,122 @@
# ===----------------------------------------------------------------------=== #
# 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 %s

from os import mkdir, rmdir, remove
from os.path import exists
from pathlib import Path

from testing import assert_true, assert_false, assert_raises


fn create_dir_and_test_delete_string[
func_create: fn (String, Int) raises -> None,
func_delete: fn (String) raises -> None,
](dir_name: String) raises:
# verify that the test dir does not exist before starting the test
assert_false(
exists(dir_name),
"Unexpected dir " + dir_name + " it should not exist",
)

func_create(dir_name, 0o777)
assert_true(exists(dir_name))

func_delete(dir_name)
# trying to delete non existing dir
with assert_raises(contains="Can not remove directory: "):
func_delete(dir_name)


fn create_dir_and_test_delete_path[
func_create: fn[pathlike: PathLike] (pathlike, Int) raises -> None,
func_delete: fn[pathlike: PathLike] (pathlike) raises -> None,
](dir_path: Path) raises:
# verify that the test dir does not exist before starting the test
assert_false(
exists(dir_path),
"Unexpected dir " + dir_path.__fspath__() + " it should not exist",
)

func_create(dir_path, 0o777)
assert_true(exists(dir_path))

func_delete(dir_path)
# trying to delete non existing dir
with assert_raises(contains="Can not remove directory: "):
func_delete(dir_path)


fn test_mkdir_and_rmdir() raises:
var cwd_path = Path()
var my_dir_path = cwd_path / "my_dir"
var my_dir_name = str(my_dir_path)

create_dir_and_test_delete_path[mkdir, rmdir](my_dir_path)
create_dir_and_test_delete_string[mkdir, rmdir](my_dir_name)

# test relative path
create_dir_and_test_delete_string[mkdir, rmdir]("my_relative_dir")
create_dir_and_test_delete_path[mkdir, rmdir](Path("my_relative_dir"))


fn test_mkdir_mode() raises:
var cwd_path = Path()
var my_dir_path = cwd_path / "my_dir"
var file_name = my_dir_path / "file.txt"

assert_false(
exists(my_dir_path),
"Unexpected dir " + my_dir_path.__fspath__() + " it should not exist",
)

# creating dir without writing permission
mkdir(my_dir_path, 0o111)

# TODO: This test is failing on Graviton internally in CI, revisit.
# with assert_raises(contains="Permission denied"):
# var file = open(file_name, "w")
# file.close()
# if exists(file_name):
# remove(file_name)

if exists(my_dir_path):
rmdir(my_dir_path)


fn test_rmdir_not_empty() raises:
var cwd_path = Path()
var my_dir_path = cwd_path / "my_dir"
var file_name = my_dir_path / "file.txt"

assert_false(
exists(my_dir_path),
"Unexpected dir " + my_dir_path.__fspath__() + " it should not exist",
)

mkdir(my_dir_path)
with open(file_name, "w"):
pass

with assert_raises(contains="Can not remove directory: "):
rmdir(my_dir_path)

remove(file_name)
rmdir(my_dir_path)
assert_false(exists(my_dir_path), "Failed to remove dir")


def main():
test_mkdir_and_rmdir()
test_mkdir_mode()
test_rmdir_not_empty()

0 comments on commit fab2ec5

Please sign in to comment.