From 47bf64473c02ee337815371ee5b2fb34cb629f7d Mon Sep 17 00:00:00 2001 From: pancake Date: Fri, 9 Jul 2021 02:27:16 +0200 Subject: [PATCH] vlib: reimplement glob in V for UNIX to not depend on libc (#10707) --- vlib/os/glob_test.v | 20 ++++- vlib/os/os.v | 9 +++ vlib/os/os_nix.c.v | 162 +++++++++++++++++++++++++++++++---------- vlib/os/os_windows.c.v | 10 +-- 4 files changed, 152 insertions(+), 49 deletions(-) diff --git a/vlib/os/glob_test.v b/vlib/os/glob_test.v index cb024c4ad2..1a28c21b6e 100644 --- a/vlib/os/glob_test.v +++ b/vlib/os/glob_test.v @@ -15,9 +15,25 @@ fn deep_glob() ? { } } +fn redeep_glob() ? { + os.chdir(@VMODROOT) + matches := os.glob('vlib/v/**/*.v') or { panic(err) } + assert matches.len > 10 + assert 'vlib/v/ast/ast.v' in matches + assert 'vlib/v/ast/table.v' in matches + assert 'vlib/v/token/token.v' in matches + for f in matches { + if !f.starts_with('vlib/v/') { + assert false + } + assert f.ends_with('.v') + } +} + fn test_glob_can_find_v_files_3_levels_deep() ? { $if !windows { deep_glob() ? + redeep_glob() ? } assert true } @@ -30,7 +46,9 @@ fn test_glob_can_find_files_in_current_folder() ? { assert 'cmd/' in matches assert 'vlib/' in matches for f in matches { - assert !f.ends_with('.v') + if !f.ends_with(os.path_separator) { + assert !f.ends_with('.v') + } } } diff --git a/vlib/os/os.v b/vlib/os/os.v index 1a2b9afabf..f43a1ac8e0 100644 --- a/vlib/os/os.v +++ b/vlib/os/os.v @@ -627,3 +627,12 @@ pub fn is_atty(fd int) int { return C.isatty(fd) } } + +pub fn glob(patterns ...string) ?[]string { + mut matches := []string{} + for pattern in patterns { + native_glob_pattern(pattern, mut matches) ? + } + matches.sort() + return matches +} diff --git a/vlib/os/os_nix.c.v b/vlib/os/os_nix.c.v index 153ae7d80c..aa2bb3b34b 100644 --- a/vlib/os/os_nix.c.v +++ b/vlib/os/os_nix.c.v @@ -8,7 +8,6 @@ import strings #include #include #include -#include #include pub const ( @@ -42,14 +41,6 @@ pub const ( s_ixoth = 0o0001 // Execute by others ) -[typedef] -struct C.glob_t { -mut: - gl_pathc size_t // number of matched paths - gl_pathv &&char // list of matched pathnames - gl_offs size_t // slots to reserve in gl_pathv -} - struct C.utsname { mut: sysname &char @@ -84,45 +75,138 @@ fn C.getgid() int fn C.getegid() int fn C.ptrace(u32, u32, voidptr, int) u64 -fn C.glob(&char, int, voidptr, voidptr) int -fn C.globfree(voidptr) +enum GlobMatch { + exact + ends_with + starts_with + start_and_ends_with + contains + any +} -pub fn glob(patterns ...string) ?[]string { - $if android { - return error('os.glob() is not supported on android yet') +fn glob_match(dir string, pattern string, next_pattern string, mut matches []string) []string { + mut subdirs := []string{} + if is_file(dir) { + return subdirs } - mut matches := []string{} - if patterns.len == 0 { - return matches - } - mut globdata := C.glob_t{ - gl_pathv: 0 - gl_pathc: size_t(0) - gl_offs: size_t(0) - } - mut flags := int(C.GLOB_DOOFFS | C.GLOB_MARK) - for i, pattern in patterns { - if i > 0 { - flags |= C.GLOB_APPEND - } - unsafe { - $if !android { - if C.glob(&char(pattern.str), flags, C.NULL, &globdata) != 0 { - return error_with_code(posix_get_error_msg(C.errno), C.errno) + mut files := ls(dir) or { return subdirs } + mut mode := GlobMatch.exact + mut pat := pattern + if pat == '*' { + mode = GlobMatch.any + if next_pattern != pattern && next_pattern != '' { + for file in files { + if is_dir('$dir/$file') { + subdirs << '$dir/$file' } } + return subdirs + } + } + if pat == '**' { + files = walk_ext(dir, '') + pat = next_pattern + } + if pat.starts_with('*') { + mode = .ends_with + pat = pat[1..] + } + if pat.ends_with('*') { + mode = if mode == .ends_with { GlobMatch.contains } else { GlobMatch.starts_with } + pat = pat[..pat.len - 1] + } + if pat.contains('*') { + mode = .start_and_ends_with + } + for file in files { + mut fpath := file + f := if file.contains(os.path_separator) { + pathwalk := file.split(os.path_separator) + pathwalk[pathwalk.len - 1] + } else { + fpath = if dir == '.' { file } else { '$dir/$file' } + file + } + if f in ['.', '..'] || f == '' { + continue + } + hit := match mode { + .any { + true + } + .exact { + f == pat + } + .starts_with { + f.starts_with(pat) + } + .ends_with { + f.ends_with(pat) + } + .start_and_ends_with { + p := pat.split('*') + f.starts_with(p[0]) && f.ends_with(p[1]) + } + .contains { + f.contains(pat) + } + } + if hit { + if is_dir(fpath) { + subdirs << fpath + if next_pattern == pattern && next_pattern != '' { + matches << '$fpath$os.path_separator' + } + } else { + matches << fpath + } } } - for i := 0; i < int(globdata.gl_pathc); i++ { - unsafe { - matches << cstring_to_vstring(globdata.gl_pathv[i]) + return subdirs +} + +fn native_glob_pattern(pattern string, mut matches []string) ? { + steps := pattern.split(os.path_separator) + mut cwd := if pattern.starts_with(os.path_separator) { os.path_separator } else { '.' } + mut subdirs := [cwd] + for i := 0; i < steps.len; i++ { + step := steps[i] + step2 := if i + 1 == steps.len { step } else { steps[i + 1] } + if step == '' { + continue } + if is_dir('$cwd$os.path_separator$step') { + dd := if cwd == '/' { + step + } else { + if cwd == '.' || cwd == '' { + step + } else { + if step == '.' || step == '/' { cwd } else { '$cwd/$step' } + } + } + if i + 1 != steps.len { + if dd !in subdirs { + subdirs << dd + } + } + } + mut subs := []string{} + for sd in subdirs { + d := if cwd == '/' { + sd + } else { + if cwd == '.' || cwd == '' { + sd + } else { + if sd == '.' || sd == '/' { cwd } else { '$cwd/$sd' } + } + } + subs << glob_match(d.replace('//', '/'), step, step2, mut matches) + } + subdirs = subs.clone() } - $if !android { - C.globfree(&globdata) - } - return matches } pub fn utime(path string, actime int, modtime int) ? { diff --git a/vlib/os/os_windows.c.v b/vlib/os/os_windows.c.v index 5f6d3d347a..a920601f33 100644 --- a/vlib/os/os_windows.c.v +++ b/vlib/os/os_windows.c.v @@ -102,15 +102,7 @@ fn init_os_args_wide(argc int, argv &&byte) []string { return args_ } -pub fn glob(patterns ...string) ?[]string { - mut matches := []string{} - for pattern in patterns { - windows_glob_pattern(pattern, mut matches) ? - } - return matches -} - -fn windows_glob_pattern(pattern string, mut matches []string) ? { +fn native_glob_pattern(pattern string, mut matches []string) ? { $if debug { // FindFirstFile() and FindNextFile() both have a globbing function. // Unfortunately this is not as pronounced as under Unix, but should provide some functionality