1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

os: rewrite os.walk and os.walk_with_context to use iteration, instead of recursion

This commit is contained in:
Delyan Angelov 2022-08-22 17:10:46 +03:00
parent 21917f5b00
commit a689641c1b
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
3 changed files with 96 additions and 24 deletions

View File

@ -738,6 +738,41 @@ pub fn is_link(path string) bool {
} }
} }
struct PathKind {
mut:
is_dir bool
is_link bool
}
fn kind_of_existing_path(path string) PathKind {
mut res := PathKind{}
$if windows {
attr := C.GetFileAttributesW(path.to_wide())
if attr != u32(C.INVALID_FILE_ATTRIBUTES) {
if (int(attr) & C.FILE_ATTRIBUTE_DIRECTORY) != 0 {
res.is_dir = true
}
if (int(attr) & 0x400) != 0 {
res.is_link = true
}
}
} $else {
statbuf := C.stat{}
// ref: https://code.woboq.org/gcc/include/sys/stat.h.html
res_stat := unsafe { C.lstat(&char(path.str), &statbuf) }
if res_stat == 0 {
kind := (int(statbuf.st_mode) & s_ifmt)
if kind == s_ifdir {
res.is_dir = true
}
if kind == s_iflnk {
res.is_link = true
}
}
}
return res
}
// chdir changes the current working directory to the new directory in `path`. // chdir changes the current working directory to the new directory in `path`.
pub fn chdir(path string) ? { pub fn chdir(path string) ? {
ret := $if windows { C._wchdir(path.to_wide()) } $else { C.chdir(&char(path.str)) } ret := $if windows { C._wchdir(path.to_wide()) } $else { C.chdir(&char(path.str)) }

View File

@ -569,8 +569,9 @@ fn impl_walk_ext(path string, ext string, mut out []string) {
} }
// walk traverses the given directory `path`. // walk traverses the given directory `path`.
// When a file is encountred it will call the // When a file is encountred, it will call the callback `f` with current file as argument.
// callback function `f` with current file as argument. // Note: walk can be called even for deeply nested folders,
// since it does not recurse, but processes them iteratively.
pub fn walk(path string, f fn (string)) { pub fn walk(path string, f fn (string)) {
if path.len == 0 { if path.len == 0 {
return return
@ -578,29 +579,36 @@ pub fn walk(path string, f fn (string)) {
if !is_dir(path) { if !is_dir(path) {
return return
} }
mut files := ls(path) or { return } mut remaining := []string{cap: 1000}
mut local_path_separator := path_separator clean_path := path.trim_right(path_separator)
if path.ends_with(path_separator) { $if windows {
local_path_separator = '' remaining << clean_path.replace('/', '\\')
} $else {
remaining << clean_path
} }
for file in files { for remaining.len > 0 {
p := path + local_path_separator + file cpath := remaining.pop()
if is_dir(p) && !is_link(p) { pkind := kind_of_existing_path(cpath)
walk(p, f) if pkind.is_link || !pkind.is_dir {
} else if exists(p) { f(cpath)
f(p) continue
}
mut files := ls(cpath) or { continue }
for idx := files.len - 1; idx >= 0; idx-- {
remaining << cpath + path_separator + files[idx]
} }
} }
return
} }
// FnWalkContextCB is used to define the callback functions, passed to os.walk_context // FnWalkContextCB is used to define the callback functions, passed to os.walk_context
pub type FnWalkContextCB = fn (voidptr, string) pub type FnWalkContextCB = fn (voidptr, string)
// walk_with_context traverses the given directory `path`. // walk_with_context traverses the given directory `path`.
// For each encountred file and directory, it will call your `fcb` callback, // For each encountred file *and* directory, it will call your `fcb` callback,
// passing it the arbitrary `context` in its first parameter, // passing it the arbitrary `context` in its first parameter,
// and the path to the file in its second parameter. // and the path to the file in its second parameter.
// Note: walk_with_context can be called even for deeply nested folders,
// since it does not recurse, but processes them iteratively.
pub fn walk_with_context(path string, context voidptr, fcb FnWalkContextCB) { pub fn walk_with_context(path string, context voidptr, fcb FnWalkContextCB) {
if path.len == 0 { if path.len == 0 {
return return
@ -608,19 +616,30 @@ pub fn walk_with_context(path string, context voidptr, fcb FnWalkContextCB) {
if !is_dir(path) { if !is_dir(path) {
return return
} }
mut files := ls(path) or { return } mut remaining := []string{cap: 1000}
mut local_path_separator := path_separator clean_path := path.trim_right(path_separator)
if path.ends_with(path_separator) { $if windows {
local_path_separator = '' remaining << clean_path.replace('/', '\\')
} $else {
remaining << clean_path
} }
for file in files { mut loops := 0
p := path + local_path_separator + file for remaining.len > 0 {
fcb(context, p) loops++
if is_dir(p) && !is_link(p) { cpath := remaining.pop()
walk_with_context(p, context, fcb) // call `fcb` for everything, but the initial folder:
if loops > 1 {
fcb(context, cpath)
}
pkind := kind_of_existing_path(cpath)
if pkind.is_link || !pkind.is_dir {
continue
}
mut files := ls(cpath) or { continue }
for idx := files.len - 1; idx >= 0; idx-- {
remaining << cpath + path_separator + files[idx]
} }
} }
return
} }
// log will print "os.log: "+`s` ... // log will print "os.log: "+`s` ...

View File

@ -29,6 +29,24 @@ pub fn is_link(path string) bool {
return res return res
} }
struct PathKind {
is_dir bool
is_link bool
}
fn kind_of_existing_path(path string) PathKind {
is_link := false
is_dir := false
$if js_node {
#is_link.val = $fs.existsSync(path.str) && $fs.lstatSync(path.str).isSymbolicLink()
#is_dir.val = $fs.existsSync(path,str) && $fs.lstatSync(path.str).isDirectory()
}
return PathKind{
is_dir: is_dir
is_link: is_link
}
}
pub fn exists(path string) bool { pub fn exists(path string) bool {
res := false res := false
$if js_node { $if js_node {