Extract the basename of a path and strip the extension

The extension is stripped only if extension != null.

assert "/path/to/a_file.ext".basename(".ext")     == "a_file"
assert "path/to/a_file.ext".basename(".ext")      == "a_file"
assert "path/to/a_file.ext".basename              == "a_file.ext"
assert "path/to".basename(".ext")                 == "to"
assert "path/to/".basename(".ext")                == "to"
assert "path/to".basename                         == "to"
assert "path".basename                            == "path"
assert "/path".basename                           == "path"
assert "/".basename                               == "/"
assert "".basename                                == ""

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

assert "C:\\path\\to\\a_file.ext".basename(".ext")    == "a_file"
assert "C:\\".basename                                == "C:"

Property definitions

core :: file $ Text :: basename
	# Extract the basename of a path and strip the `extension`
	#
	# The extension is stripped only if `extension != null`.
	#
	#     assert "/path/to/a_file.ext".basename(".ext")     == "a_file"
	#     assert "path/to/a_file.ext".basename(".ext")      == "a_file"
	#     assert "path/to/a_file.ext".basename              == "a_file.ext"
	#     assert "path/to".basename(".ext")                 == "to"
	#     assert "path/to/".basename(".ext")                == "to"
	#     assert "path/to".basename                         == "to"
	#     assert "path".basename                            == "path"
	#     assert "/path".basename                           == "path"
	#     assert "/".basename                               == "/"
	#     assert "".basename                                == ""
	#
	# On Windows, '\' are replaced by '/':
	#
	# ~~~nitish
	# assert "C:\\path\\to\\a_file.ext".basename(".ext")    == "a_file"
	# assert "C:\\".basename                                == "C:"
	# ~~~
	fun basename(extension: nullable String): String
	do
		var n = self
		if is_windows then n = n.replace("\\", "/")

		var l = length - 1 # Index of the last char
		while l > 0 and self.chars[l] == '/' do l -= 1 # remove all trailing `/`
		if l == 0 then return "/"
		var pos = chars.last_index_of_from('/', l)
		if pos >= 0 then
			n = substring(pos+1, l-pos)
		end

		if extension != null then
			return n.strip_extension(extension)
		else return n.to_s
	end
lib/core/file.nit:956,2--993,4

core :: file $ FlatString :: basename
	redef fun basename(extension) do
		var s = self
		if is_windows then s = s.replace("\\", "/").as(FlatString)

		var bname
		var l = s.last_byte
		var its = s._items
		var min = s._first_byte
		var sl = u'/'
		while l > min and its[l] == sl do l -= 1
		if l == min then return "/"
		var ns = l
		while ns >= min and its[ns] != sl do ns -= 1
		bname = new FlatString.with_infos(its, l - ns, ns + 1)

		return if extension != null then bname.strip_extension(extension) else bname
	end
lib/core/file.nit:1377,2--1393,4