os: add norm_path and abs_path function (#14435)
parent
efc5cab8c3
commit
971c55cf30
|
@ -10,7 +10,7 @@ on:
|
|||
- "**.md"
|
||||
|
||||
concurrency:
|
||||
group: build-other-${{ github.event.pull_request.number || github.sha }}
|
||||
group: build-v-apps-and-modules-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
module os
|
||||
|
||||
import strings
|
||||
import strings.textscanner
|
||||
|
||||
// Collection of useful functions for manipulation, validation and analysis of system paths.
|
||||
// The following functions handle paths depending on the operating system,
|
||||
// therefore results may be different for certain operating systems.
|
||||
|
||||
const (
|
||||
fslash = `/`
|
||||
bslash = `\\`
|
||||
dot = `.`
|
||||
fslash = `/`
|
||||
bslash = `\\`
|
||||
dot = `.`
|
||||
qmark = `?`
|
||||
dot_dot = '..'
|
||||
empty = ''
|
||||
dot_str = '.'
|
||||
)
|
||||
|
||||
// is_abs_path returns `true` if the given `path` is absolute.
|
||||
|
@ -16,11 +23,152 @@ pub fn is_abs_path(path string) bool {
|
|||
return false
|
||||
}
|
||||
$if windows {
|
||||
return is_device_path(path) || is_drive_rooted(path) || is_normal_path(path)
|
||||
return is_unc_path(path) || is_drive_rooted(path) || is_normal_path(path)
|
||||
}
|
||||
return path[0] == os.fslash
|
||||
}
|
||||
|
||||
// abs_path joins the current working directory
|
||||
// with the given `path` (if the `path` is relative)
|
||||
// and returns the absolute path representation.
|
||||
pub fn abs_path(path string) string {
|
||||
wd := getwd()
|
||||
if path.len == 0 {
|
||||
return wd
|
||||
}
|
||||
npath := norm_path(path)
|
||||
if npath == os.dot_str {
|
||||
return wd
|
||||
}
|
||||
if !is_abs_path(npath) {
|
||||
mut sb := strings.new_builder(npath.len)
|
||||
sb.write_string(wd)
|
||||
sb.write_string(path_separator)
|
||||
sb.write_string(npath)
|
||||
return norm_path(sb.str())
|
||||
}
|
||||
return npath
|
||||
}
|
||||
|
||||
// norm_path returns the normalized version of the given `path`
|
||||
// by resolving backlinks (..), turning forward slashes into
|
||||
// back slashes on a Windows system and eliminating:
|
||||
// - references to current directories (.)
|
||||
// - redundant path separators
|
||||
// - the last path separator
|
||||
[direct_array_access]
|
||||
pub fn norm_path(path string) string {
|
||||
if path.len == 0 {
|
||||
return '.'
|
||||
}
|
||||
rooted := is_abs_path(path)
|
||||
volume := get_volume(path)
|
||||
volume_len := volume.len
|
||||
cpath := clean_path(path[volume_len..])
|
||||
if cpath.len == 0 && volume_len == 0 {
|
||||
return '.'
|
||||
}
|
||||
spath := cpath.split(path_separator)
|
||||
if os.dot_dot !in spath {
|
||||
return if volume_len != 0 { volume + cpath } else { cpath }
|
||||
}
|
||||
// resolve backlinks (..)
|
||||
spath_len := spath.len
|
||||
mut sb := strings.new_builder(cpath.len)
|
||||
if rooted {
|
||||
sb.write_string(path_separator)
|
||||
}
|
||||
mut new_path := []string{cap: spath_len}
|
||||
mut backlink_count := 0
|
||||
for i := spath_len - 1; i >= 0; i-- {
|
||||
part := spath[i]
|
||||
if part == os.empty {
|
||||
continue
|
||||
}
|
||||
if part == os.dot_dot {
|
||||
backlink_count++
|
||||
continue
|
||||
}
|
||||
if backlink_count != 0 {
|
||||
backlink_count--
|
||||
continue
|
||||
}
|
||||
new_path.prepend(part)
|
||||
}
|
||||
// append backlink(s) to the path if backtracking
|
||||
// is not possible and the given path is not rooted
|
||||
if backlink_count != 0 && !rooted {
|
||||
for i in 0 .. backlink_count {
|
||||
sb.write_string(os.dot_dot)
|
||||
if new_path.len == 0 && i == backlink_count - 1 {
|
||||
break
|
||||
}
|
||||
sb.write_string(path_separator)
|
||||
}
|
||||
}
|
||||
sb.write_string(new_path.join(path_separator))
|
||||
res := sb.str()
|
||||
if res.len == 0 {
|
||||
if volume_len != 0 {
|
||||
return volume
|
||||
}
|
||||
if !rooted {
|
||||
return '.'
|
||||
}
|
||||
return path_separator
|
||||
}
|
||||
if volume_len != 0 {
|
||||
return volume + res
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// clean_path returns the "cleaned" version of the given `path`
|
||||
// by turning forward slashes into back slashes
|
||||
// on a Windows system and eliminating:
|
||||
// - references to current directories (.)
|
||||
// - redundant separators
|
||||
// - the last path separator
|
||||
fn clean_path(path string) string {
|
||||
if path.len == 0 {
|
||||
return ''
|
||||
}
|
||||
mut sb := strings.new_builder(path.len)
|
||||
mut sc := textscanner.new(path)
|
||||
for sc.next() != -1 {
|
||||
curr := u8(sc.current())
|
||||
back := sc.peek_back()
|
||||
peek := sc.peek()
|
||||
// skip current path separator if last byte was a path separator
|
||||
if back != -1 && is_slash(u8(back)) && is_slash(curr) {
|
||||
continue
|
||||
}
|
||||
// skip reference to current dir (.)
|
||||
if (back == -1 || is_slash(u8(back))) && curr == os.dot
|
||||
&& (peek == -1 || is_slash(u8(peek))) {
|
||||
// skip if the next byte is a path separator
|
||||
if peek != -1 && is_slash(u8(peek)) {
|
||||
sc.skip_n(1)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// turn foward slash into a back slash on a Windows system
|
||||
$if windows {
|
||||
if curr == os.fslash {
|
||||
sb.write_u8(os.bslash)
|
||||
continue
|
||||
}
|
||||
}
|
||||
sb.write_u8(u8(sc.current()))
|
||||
}
|
||||
res := sb.str()
|
||||
// eliminate the last path separator
|
||||
if res.len > 1 && is_slash(res[res.len - 1]) {
|
||||
return res[..res.len - 1]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// win_volume_len returns the length of the
|
||||
// Windows volume/drive from the given `path`.
|
||||
fn win_volume_len(path string) int {
|
||||
|
@ -32,7 +180,7 @@ fn win_volume_len(path string) int {
|
|||
return 2
|
||||
}
|
||||
// its UNC path / DOS device path?
|
||||
if path.len >= 5 && starts_w_slash_slash(path) && !is_slash(path[2]) {
|
||||
if plen >= 5 && starts_w_slash_slash(path) && !is_slash(path[2]) {
|
||||
for i := 3; i < plen; i++ {
|
||||
if is_slash(path[i]) {
|
||||
if i + 1 >= plen || is_slash(path[i + 1]) {
|
||||
|
@ -51,6 +199,20 @@ fn win_volume_len(path string) int {
|
|||
return 0
|
||||
}
|
||||
|
||||
fn get_volume(path string) string {
|
||||
$if !windows {
|
||||
return ''
|
||||
}
|
||||
volume := path[..win_volume_len(path)]
|
||||
if volume.len == 0 {
|
||||
return ''
|
||||
}
|
||||
if volume[0] == os.fslash {
|
||||
return volume.replace('/', '\\')
|
||||
}
|
||||
return volume
|
||||
}
|
||||
|
||||
fn is_slash(b u8) bool {
|
||||
$if windows {
|
||||
return b == os.bslash || b == os.fslash
|
||||
|
@ -58,7 +220,7 @@ fn is_slash(b u8) bool {
|
|||
return b == os.fslash
|
||||
}
|
||||
|
||||
fn is_device_path(path string) bool {
|
||||
fn is_unc_path(path string) bool {
|
||||
return win_volume_len(path) >= 5 && starts_w_slash_slash(path)
|
||||
}
|
||||
|
||||
|
|
|
@ -27,3 +27,103 @@ fn test_is_abs_path() {
|
|||
assert !is_abs_path('./')
|
||||
assert !is_abs_path('.')
|
||||
}
|
||||
|
||||
fn test_clean_path() {
|
||||
$if windows {
|
||||
assert clean_path(r'\\path\to\files/file.v') == r'\path\to\files\file.v'
|
||||
assert clean_path(r'\/\//\/') == '\\'
|
||||
assert clean_path(r'./path\\dir/\\./\/\\/file.v\.\\\.') == r'path\dir\file.v'
|
||||
assert clean_path(r'\./path/dir\\file.exe') == r'\path\dir\file.exe'
|
||||
assert clean_path(r'.') == ''
|
||||
assert clean_path(r'./') == ''
|
||||
assert clean_path(r'\./') == '\\'
|
||||
assert clean_path(r'//\/\/////') == '\\'
|
||||
return
|
||||
}
|
||||
assert clean_path('./../.././././//') == '../..'
|
||||
assert clean_path('.') == ''
|
||||
assert clean_path('./path/to/file.v//./') == 'path/to/file.v'
|
||||
assert clean_path('./') == ''
|
||||
assert clean_path('/.') == '/'
|
||||
assert clean_path('//path/./to/.///files/file.v///') == '/path/to/files/file.v'
|
||||
assert clean_path('path/./to/.///files/.././file.v///') == 'path/to/files/../file.v'
|
||||
assert clean_path('\\') == '\\'
|
||||
assert clean_path('//////////') == '/'
|
||||
}
|
||||
|
||||
fn test_norm_path() {
|
||||
$if windows {
|
||||
assert norm_path(r'C:/path/to//file.v\\') == r'C:\path\to\file.v'
|
||||
assert norm_path(r'C:path\.\..\\\.\to//file.v') == r'C:to\file.v'
|
||||
assert norm_path(r'D:path\.\..\..\\\\.\to//dir/..\') == r'D:..\to'
|
||||
assert norm_path(r'D:/path\.\..\/..\file.v') == r'D:\file.v'
|
||||
assert norm_path(r'') == '.'
|
||||
assert norm_path(r'/') == '\\'
|
||||
assert norm_path(r'\/') == '\\'
|
||||
assert norm_path(r'path\../dir\..') == '.'
|
||||
assert norm_path(r'.\.\') == '.'
|
||||
assert norm_path(r'G:.\.\dir\././\.\.\\\\///to/././\file.v/./\\') == r'G:dir\to\file.v'
|
||||
assert norm_path(r'G:\..\..\.\.\file.v\\\.') == r'G:\file.v'
|
||||
assert norm_path(r'\\Server\share\\\dir/..\file.v\./.') == r'\\Server\share\file.v'
|
||||
assert norm_path(r'\\.\device\\\dir/to/./file.v\.') == r'\\.\device\dir\to\file.v'
|
||||
assert norm_path(r'C:dir/../dir2/../../../file.v') == r'C:..\..\file.v'
|
||||
assert norm_path(r'\\.\C:\\\Users/\Documents//..') == r'\\.\C:\Users'
|
||||
assert norm_path(r'\\.\C:\Users') == r'\\.\C:\Users'
|
||||
assert norm_path(r'\\') == '\\'
|
||||
assert norm_path(r'//') == '\\'
|
||||
assert norm_path(r'\\\') == '\\'
|
||||
assert norm_path(r'.') == '.'
|
||||
assert norm_path(r'\\Server') == '\\Server'
|
||||
assert norm_path(r'\\Server\') == '\\Server'
|
||||
return
|
||||
}
|
||||
assert norm_path('/path/././../to/file//file.v/.') == '/to/file/file.v'
|
||||
assert norm_path('path/././to/files/../../file.v/.') == 'path/file.v'
|
||||
assert norm_path('path/././/../../to/file.v/.') == '../to/file.v'
|
||||
assert norm_path('/path/././/../..///.././file.v/././') == '/file.v'
|
||||
assert norm_path('path/././//../../../to/dir//.././file.v/././') == '../../to/file.v'
|
||||
assert norm_path('path/../dir/..') == '.'
|
||||
assert norm_path('../dir/..') == '..'
|
||||
assert norm_path('/../dir/..') == '/'
|
||||
assert norm_path('//././dir/../files/././/file.v') == '/files/file.v'
|
||||
assert norm_path('/\\../dir/////////.') == '/\\../dir'
|
||||
assert norm_path('/home/') == '/home'
|
||||
assert norm_path('/home/////./.') == '/home'
|
||||
assert norm_path('...') == '...'
|
||||
}
|
||||
|
||||
fn test_abs_path() {
|
||||
wd := getwd()
|
||||
wd_w_sep := wd + path_separator
|
||||
$if windows {
|
||||
assert abs_path('path/to/file.v') == '${wd_w_sep}path\\to\\file.v'
|
||||
assert abs_path('path/to/file.v') == '${wd_w_sep}path\\to\\file.v'
|
||||
assert abs_path('/') == r'\'
|
||||
assert abs_path(r'C:\path\to\files\file.v') == r'C:\path\to\files\file.v'
|
||||
assert abs_path(r'C:\/\path\.\to\../files\file.v\.\\\.\') == r'C:\path\files\file.v'
|
||||
assert abs_path(r'\\Host\share\files\..\..\.') == r'\\Host\share\'
|
||||
assert abs_path(r'\\.\HardDiskvolume2\files\..\..\.') == r'\\.\HardDiskvolume2\'
|
||||
assert abs_path(r'\\?\share') == r'\\?\share'
|
||||
assert abs_path(r'\\.\') == r'\'
|
||||
assert abs_path(r'G:/\..\\..\.\.\file.v\\.\.\\\\') == r'G:\file.v'
|
||||
assert abs_path('files') == '${wd_w_sep}files'
|
||||
assert abs_path('') == wd
|
||||
assert abs_path('.') == wd
|
||||
assert abs_path('files/../file.v') == '${wd_w_sep}file.v'
|
||||
assert abs_path('///') == r'\'
|
||||
assert abs_path('/path/to/file.v') == r'\path\to\file.v'
|
||||
assert abs_path('D:/') == r'D:\'
|
||||
assert abs_path(r'\\.\HardiskVolume6') == r'\\.\HardiskVolume6'
|
||||
return
|
||||
}
|
||||
assert abs_path('/') == '/'
|
||||
assert abs_path('.') == wd
|
||||
assert abs_path('files') == '${wd_w_sep}files'
|
||||
assert abs_path('') == wd
|
||||
assert abs_path('files/../file.v') == '${wd_w_sep}file.v'
|
||||
assert abs_path('///') == '/'
|
||||
assert abs_path('/path/to/file.v') == '/path/to/file.v'
|
||||
assert abs_path('/path/to/file.v/../..') == '/path'
|
||||
assert abs_path('path/../file.v/..') == wd
|
||||
assert abs_path('///') == '/'
|
||||
}
|
||||
|
|
|
@ -270,7 +270,7 @@ pub fn (b &Builder) import_graph() &depgraph.DepGraph {
|
|||
deps << 'builtin'
|
||||
if b.pref.backend == .c {
|
||||
// TODO JavaScript backend doesn't handle os for now
|
||||
if b.pref.is_vsh && p.mod.name !in ['os', 'dl'] {
|
||||
if b.pref.is_vsh && p.mod.name !in ['os', 'dl', 'strings.textscanner'] {
|
||||
deps << 'os'
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue