Skip to content

Commit

Permalink
Add normpath, splitroot based on python implementation
Browse files Browse the repository at this point in the history
Signed-off-by: John Diggins <[email protected]>
  • Loading branch information
jdiggins committed Jun 15, 2024
1 parent 1130fdb commit bf58b3a
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 1 deletion.
13 changes: 12 additions & 1 deletion stdlib/src/os/path/__init__.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,15 @@
# limitations under the License.
# ===----------------------------------------------------------------------=== #

from .path import exists, isdir, isfile, islink, lexists, getsize, join, dirname
from .path import (
exists,
isdir,
isfile,
islink,
lexists,
getsize,
join,
dirname,
splitroot,
normpath,
)
107 changes: 107 additions & 0 deletions stdlib/src/os/path/path.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,110 @@ fn join(path: String, *paths: String) -> String:
# paths_str.append(cur_path[].__fspath__())

# return join(path.__fspath__(), *paths_str)


# ===----------------------------------------------------------------------=== #
# splitroot
# ===----------------------------------------------------------------------=== #


fn splitroot(path: String) -> Tuple[String, String, String]:
"""Split a pathname into drive, root and tail.
Args:
path: The path to a directory or file.
Returns:
The tuple with the drive, root and tail of the path.
"""
alias empty = "".__str__()

if path[:1] != sep:
return (empty, empty, path)
elif path[1:2] != sep or path[2:3] == sep:
return (empty, sep.__str__(), path[1:])
else:
return (empty, path[:2], path[2:])


fn splitroot[
PathLike: os.PathLike, //
](path: PathLike) -> Tuple[String, String, String]:
"""Split a pathname into drive, root and tail.
Parameters:
PathLike: The type conforming to the os.PathLike trait.
Args:
path: The path to a directory or file.
Returns:
The tuple with the drive, root and tail of the path.
"""
return splitroot(path.__fspath__())


# ===----------------------------------------------------------------------=== #
# normpath
# ===----------------------------------------------------------------------=== #


fn normpath(path: String) raises -> String:
"""Normalize path, eliminating double slashes, etc.
It should be understood that this may change the meaning of the path
if it contains symbolic links.
Args:
path: The path to a directory or file.
Returns:
The normalized path.
"""
alias dot = "."
alias dotdot = ".."
if not path:
return dot
var initial_slashes: String
var _path: String
_, initial_slashes, _path = splitroot(path)
var comps = _path.split(sep)
var new_comps: List[String] = List[String]()

for comp in comps:
if not comp[] or comp[] == dot:
continue
if (
comp[] != dotdot
or (not initial_slashes and not new_comps)
or (new_comps and new_comps[-1] == dotdot)
):
new_comps.append(comp[])
elif new_comps:
_ = new_comps.pop()

# TODO: use initial_slashes + sep.join(*new_comps) to reconstruct path when unpacking is supported
var normalized_path: String = initial_slashes
if new_comps:
normalized_path += new_comps[0]
for comp in new_comps[1:]:
normalized_path += (
sep.__str__() + comp[] if normalized_path else comp[]
)
return normalized_path or dot


fn normpath[PathLike: os.PathLike, //](path: PathLike) raises -> String:
"""Normalize path, eliminating double slashes, etc.
It should be understood that this may change the meaning of the path
if it contains symbolic links.
Parameters:
PathLike: The type conforming to the os.PathLike trait.
Args:
path: The path to a directory or file.
Returns:
The normalized path.
"""
return normpath(path.__fspath__())
78 changes: 78 additions & 0 deletions stdlib/test/os/path/test_normpath.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# ===----------------------------------------------------------------------=== #
# 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


import os
from os.path import normpath

from testing import assert_equal


def main():
# Root directories
assert_equal("/", normpath("/"))
assert_equal("//", normpath("//"))
assert_equal("/", normpath("///"))
assert_equal("/dir", normpath("/dir"))
assert_equal("//dir", normpath("//dir"))
assert_equal("/dir", normpath("///dir"))

# Empty strings
assert_equal(".", normpath(""))

# Dots
assert_equal(".", normpath("."))
assert_equal("..", normpath(".."))
assert_equal(".....", normpath("....."))
assert_equal("../../..", normpath("../../.."))
assert_equal("../../..", normpath("../..//../"))
assert_equal("..", normpath("..../..//../"))

# Absolute paths
assert_equal("/file", normpath("/file"))
assert_equal("/dir/file", normpath("/dir/file"))
assert_equal("/dir/subdir/file", normpath("/dir/subdir/file"))

# Relative paths
assert_equal("dir/file", normpath("dir/file"))
assert_equal("dir/subdir/file", normpath("dir/subdir/file"))

# Trailing slashes
assert_equal("/path/to", normpath("/path/to/"))
assert_equal("/path/to/dir", normpath("/path/to/dir/"))

# Multiple slashes
assert_equal("/path/to/file", normpath("/path/to//file"))
assert_equal("/path/to", normpath("/path//to"))

# Paths with spaces
assert_equal("/path to/file", normpath("/path to/file"))
assert_equal("/path to/dir/file", normpath("/path to/dir/file"))

# Paths with special characters
assert_equal("/path-to/file", normpath("/path-to/file"))
assert_equal("/path_to/dir/file", normpath("/path_to/dir/file"))

# Paths with dots
assert_equal("/path/to/file", normpath("/path/./to/file"))
assert_equal("/to/file", normpath("/path/../to/file"))
assert_equal("/file", normpath("/path/../file"))
assert_equal("/path/file", normpath("/path/to/../file"))
assert_equal("file", normpath("path/../to/../file"))

# Unix hidden files
assert_equal("/path/to/.hiddenfile", normpath("/path/to/.hiddenfile"))
assert_equal(
"/path/to/.dir/.hiddenfile", normpath("/path/to/.dir/.hiddenfile")
)
68 changes: 68 additions & 0 deletions stdlib/test/os/path/test_splitroot.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# ===----------------------------------------------------------------------=== #
# 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


import os
from os.path import splitroot

from testing import assert_equal


def main():
# Root directories
var s = splitroot("/")
assert_equal("/", s[1])
assert_equal("", s[2])
s = splitroot("//")
assert_equal("//", s[1])
assert_equal("", s[2])
s = splitroot("///")
assert_equal("/", s[1])
assert_equal("//", s[2])

# Empty strings
s = splitroot("")
assert_equal("", s[1])
assert_equal("", s[2])

# Absolute paths
s = splitroot("/file")
assert_equal("/", s[1])
assert_equal("file", s[2])
s = splitroot("//file")
assert_equal("//", s[1])
assert_equal("file", s[2])
s = splitroot("///file")
assert_equal("/", s[1])
assert_equal("//file", s[2])
s = splitroot("/dir/file")
assert_equal("/", s[1])
assert_equal("dir/file", s[2])

# Relative paths
s = splitroot("file")
assert_equal("", s[1])
assert_equal("file", s[2])
s = splitroot("file/dir")
assert_equal("", s[1])
assert_equal("file/dir", s[2])
s = splitroot(".")
assert_equal("", s[1])
assert_equal(".", s[2])
s = splitroot("..")
assert_equal("", s[1])
assert_equal("..", s[2])
s = splitroot("entire/.//.tail/..//captured////")
assert_equal("", s[1])
assert_equal("entire/.//.tail/..//captured////", s[2])

0 comments on commit bf58b3a

Please sign in to comment.