Simplify a file path by remove useless ., removing //, and resolving ..

  • .. are not resolved if they start the path
  • starting . is simplified unless the path is empty
  • starting / is not removed
  • trailing / is removed

Note that the method only work on the string:

  • no I/O access is performed
  • the validity of the path is not checked
assert "some/./complex/../../path/from/../to/a////file//".simplify_path      ==  "path/to/a/file"
assert "../dir/file".simplify_path       ==  "../dir/file"
assert "dir/../../".simplify_path        ==  ".."
assert "dir/..".simplify_path            ==  "."
assert "//absolute//path/".simplify_path ==  "/absolute/path"
assert "//absolute//../".simplify_path   ==  "/"
assert "/".simplify_path                 == "/"
assert "../".simplify_path               == ".."
assert "./".simplify_path                == "."
assert "././././././".simplify_path      == "."
assert "./../dir".simplify_path        == "../dir"
assert "./dir".simplify_path               == "dir"

On Windows, '' are replaced by '/':

assert "C:\\some\\.\\complex\\../../path/to/a_file.ext".simplify_path == "C:/path/to/a_file.ext"
assert "C:\\".simplify_path              == "C:"

Property definitions

core :: file $ Text :: simplify_path
	# Simplify a file path by remove useless `.`, removing `//`, and resolving `..`
	#
	# * `..` are not resolved if they start the path
	# * starting `.` is simplified unless the path is empty
	# * starting `/` is not removed
	# * trailing `/` is removed
	#
	# Note that the method only work on the string:
	#
	#  * no I/O access is performed
	#  * the validity of the path is not checked
	#
	# ~~~
	# assert "some/./complex/../../path/from/../to/a////file//".simplify_path	     ==  "path/to/a/file"
	# assert "../dir/file".simplify_path       ==  "../dir/file"
	# assert "dir/../../".simplify_path        ==  ".."
	# assert "dir/..".simplify_path            ==  "."
	# assert "//absolute//path/".simplify_path ==  "/absolute/path"
	# assert "//absolute//../".simplify_path   ==  "/"
	# assert "/".simplify_path                 == "/"
	# assert "../".simplify_path               == ".."
	# assert "./".simplify_path                == "."
	# assert "././././././".simplify_path      == "."
	# assert "./../dir".simplify_path		   == "../dir"
	# assert "./dir".simplify_path			   == "dir"
	# ~~~
	#
	# On Windows, '\' are replaced by '/':
	#
	# ~~~nitish
	# assert "C:\\some\\.\\complex\\../../path/to/a_file.ext".simplify_path == "C:/path/to/a_file.ext"
	# assert "C:\\".simplify_path              == "C:"
	# ~~~
	fun simplify_path: String
	do
		var s = self
		if is_windows then s = s.replace("\\", "/")
		var a = s.split_with("/")
		var a2 = new Array[String]
		for x in a do
			if x == "." and not a2.is_empty then continue # skip `././`
			if x == "" and not a2.is_empty then continue # skip `//`
			if x == ".." and not a2.is_empty and a2.last != ".." then
				if a2.last == "." then # do not skip `./../`
					a2.pop # reduce `./../` in `../`
				else # reduce `dir/../` in `/`
					a2.pop
					continue
				end
			else if not a2.is_empty and a2.last == "." then
				a2.pop # reduce `./dir` in `dir`
			end
			a2.push(x)
		end
		if a2.is_empty then return "."
		if a2.length == 1 and a2.first == "" then return "/"
		return a2.join("/")
	end
lib/core/file.nit:1040,2--1097,4