diff --git a/vlib/os/filepath.v b/vlib/os/filepath.v index d9060869fe..6111e7f798 100644 --- a/vlib/os/filepath.v +++ b/vlib/os/filepath.v @@ -123,6 +123,52 @@ pub fn norm_path(path string) string { return res } +// existing_path returns the existing part of the given `path`. +// An error is returned if there is no existing part of the given `path`. +pub fn existing_path(path string) ?string { + err := error('path does not exist') + if path.len == 0 { + return err + } + if exists(path) { + return path + } + mut volume_len := 0 + $if windows { + volume_len = win_volume_len(path) + } + if volume_len > 0 && is_slash(path[volume_len - 1]) { + volume_len++ + } + mut sc := textscanner.new(path[volume_len..]) + mut recent_path := path[..volume_len] + for sc.next() != -1 { + curr := u8(sc.current()) + peek := sc.peek() + back := sc.peek_back() + if is_curr_dir_ref(back, curr, peek) { + continue + } + range := sc.ilen - sc.remaining() + volume_len + if is_slash(curr) && !is_slash(u8(peek)) { + recent_path = path[..range] + continue + } + if !is_slash(curr) && (peek == -1 || is_slash(u8(peek))) { + curr_path := path[..range] + if exists(curr_path) { + recent_path = curr_path + continue + } + if recent_path.len == 0 { + break + } + return recent_path + } + } + return err +} + // clean_path returns the "cleaned" version of the given `path` // by turning forward slashes into back slashes // on a Windows system and eliminating: @@ -144,8 +190,7 @@ fn clean_path(path string) string { continue } // skip reference to current dir (.) - if (back == -1 || is_slash(u8(back))) && curr == os.dot - && (peek == -1 || is_slash(u8(peek))) { + if is_curr_dir_ref(back, curr, peek) { // skip if the next byte is a path separator if peek != -1 && is_slash(u8(peek)) { sc.skip_n(1) @@ -246,3 +291,13 @@ fn is_normal_path(path string) bool { return (plen == 1 && is_slash(path[0])) || (plen >= 2 && is_slash(path[0]) && !is_slash(path[1])) } + +// is_curr_dir_ref returns `true` if the 3 given integer construct +// a reference to a current directory (.). +// NOTE: a negative integer means that no byte is present +fn is_curr_dir_ref(byte_one int, byte_two int, byte_three int) bool { + if u8(byte_two) != os.dot { + return false + } + return (byte_one < 0 || is_slash(u8(byte_one))) && (byte_three < 0 || is_slash(u8(byte_three))) +} diff --git a/vlib/os/filepath_test.v b/vlib/os/filepath_test.v index 0d65d8738d..bd5c832185 100644 --- a/vlib/os/filepath_test.v +++ b/vlib/os/filepath_test.v @@ -36,11 +36,13 @@ fn test_clean_path() { assert clean_path(r'\./path/dir\\file.exe') == r'\path\dir\file.exe' assert clean_path(r'.') == '' assert clean_path(r'./') == '' + assert clean_path('') == '' assert clean_path(r'\./') == '\\' assert clean_path(r'//\/\/////') == '\\' return } assert clean_path('./../.././././//') == '../..' + assert clean_path('') == '' assert clean_path('.') == '' assert clean_path('./path/to/file.v//./') == 'path/to/file.v' assert clean_path('./') == '' @@ -127,3 +129,26 @@ fn test_abs_path() { assert abs_path('path/../file.v/..') == wd assert abs_path('///') == '/' } + +fn test_existing_path() { + wd := getwd() + $if windows { + assert existing_path('') or { '' } == '' + assert existing_path('..') or { '' } == '..' + assert existing_path('.') or { '' } == '.' + assert existing_path(wd) or { '' } == wd + assert existing_path('\\') or { '' } == '\\' + assert existing_path('$wd\\.\\\\does/not/exist\\.\\') or { '' } == '$wd\\.\\\\' + assert existing_path('$wd\\\\/\\.\\.\\/.') or { '' } == '$wd\\\\/\\.\\.\\/.' + assert existing_path('$wd\\././/\\/oh') or { '' } == '$wd\\././/\\/' + return + } + assert existing_path('') or { '' } == '' + assert existing_path('..') or { '' } == '..' + assert existing_path('.') or { '' } == '.' + assert existing_path(wd) or { '' } == wd + assert existing_path('/') or { '' } == '/' + assert existing_path('$wd/does/.///not/exist///.//') or { '' } == '$wd/' + assert existing_path('$wd//././/.//') or { '' } == '$wd//././/.//' + assert existing_path('$wd//././/.//oh') or { '' } == '$wd//././/.//' +}