From d2f19ac494d000bec0fe3f7048f4e1aa0ea7ca70 Mon Sep 17 00:00:00 2001 From: Bastian Buck <59334447+bstnbuck@users.noreply.github.com> Date: Wed, 30 Jun 2021 07:30:18 +0200 Subject: [PATCH] os: add a glob() function (#10497) --- vlib/os/glob_test.v | 62 ++++++++++++++++++++++++++++++++++++++++++ vlib/os/os_c.v | 2 +- vlib/os/os_nix.c.v | 40 +++++++++++++++++++++++++++ vlib/os/os_test.v | 25 +++++++++++++++++ vlib/os/os_windows.c.v | 51 ++++++++++++++++++++++++++++++++++ 5 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 vlib/os/glob_test.v diff --git a/vlib/os/glob_test.v b/vlib/os/glob_test.v new file mode 100644 index 0000000000..cb024c4ad2 --- /dev/null +++ b/vlib/os/glob_test.v @@ -0,0 +1,62 @@ +import os + +fn deep_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() ? + } + assert true +} + +fn test_glob_can_find_files_in_current_folder() ? { + os.chdir(@VMODROOT) + matches := os.glob('*') ? + assert 'README.md' in matches + assert 'v.mod' in matches + assert 'cmd/' in matches + assert 'vlib/' in matches + for f in matches { + assert !f.ends_with('.v') + } +} + +fn test_glob_can_be_used_with_multiple_patterns() ? { + os.chdir(@VMODROOT) + matches := os.glob('*', 'cmd/tools/*') ? + assert 'README.md' in matches + assert 'Makefile' in matches + $if !windows { + assert 'cmd/tools/test_if_v_test_system_works.v' in matches + } + $if windows { + assert 'test_if_v_test_system_works.v' in matches + } +} + +fn test_glob_star() ? { + os.chdir(@VMODROOT) + matches := os.glob('*ake*') ? + assert 'Makefile' in matches + assert 'make.bat' in matches +} + +fn test_glob_not_found() ? { + os.glob('an_unknown_folder/*.v') or { + assert true + return + } +} diff --git a/vlib/os/os_c.v b/vlib/os/os_c.v index 07d35b47cb..22c9423e13 100644 --- a/vlib/os/os_c.v +++ b/vlib/os/os_c.v @@ -183,7 +183,7 @@ pub fn file_size(path string) u64 { } $if x32 { $if debug { - println('Using os.file_size() on 32bit systems may not work on big files.') + eprintln('Using os.file_size() on 32bit systems may not work on big files.') } $if windows { if C._wstat(path.to_wide(), voidptr(&s)) != 0 { diff --git a/vlib/os/os_nix.c.v b/vlib/os/os_nix.c.v index 07762358bb..1e99318b75 100644 --- a/vlib/os/os_nix.c.v +++ b/vlib/os/os_nix.c.v @@ -8,6 +8,7 @@ import strings #include #include #include +#include pub const ( path_separator = '/' @@ -49,6 +50,14 @@ mut: machine &char } +[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 +} + fn C.uname(name voidptr) int fn C.symlink(&char, &char) int @@ -67,6 +76,37 @@ 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) + +pub fn glob(patterns ...string) ?[]string { + mut matches := []string{} + if patterns.len == 0 { + return matches + } + mut globdata := C.glob_t{ + gl_pathv: 0 + } + mut flags := int(C.GLOB_DOOFFS | C.GLOB_MARK) + for i, pattern in patterns { + if i > 0 { + flags |= C.GLOB_APPEND + } + unsafe { + if C.glob(&char(pattern.str), flags, C.NULL, &globdata) != 0 { + return error_with_code(posix_get_error_msg(C.errno), C.errno) + } + } + } + for i := 0; i < int(globdata.gl_pathc); i++ { + unsafe { + matches << cstring_to_vstring(globdata.gl_pathv[i]) + } + } + C.globfree(&globdata) + return matches +} pub fn uname() Uname { mut u := Uname{} diff --git a/vlib/os/os_test.v b/vlib/os/os_test.v index 56089daef1..a251f09c58 100644 --- a/vlib/os/os_test.v +++ b/vlib/os/os_test.v @@ -699,3 +699,28 @@ fn test_truncate() { fn test_hostname() { assert os.hostname().len > 2 } + +fn test_glob() { + os.mkdir('test_dir') or { panic(err) } + for i in 0 .. 4 { + if i == 3 { + mut f := os.create('test_dir/test0_another') or { panic(err) } + f.close() + mut f1 := os.create('test_dir/test') or { panic(err) } + f1.close() + } else { + mut f := os.create('test_dir/test' + i.str()) or { panic(err) } + f.close() + } + } + files := os.glob('test_dir/t*') or { panic(err) } + assert files.len == 5 + assert os.base(files[0]) == 'test' + + for i in 0 .. 3 { + os.rm('test_dir/test' + i.str()) or { panic(err) } + } + os.rm('test_dir/test0_another') or { panic(err) } + os.rm('test_dir/test') or { panic(err) } + os.rmdir_all('test_dir') or { panic(err) } +} diff --git a/vlib/os/os_windows.c.v b/vlib/os/os_windows.c.v index 0c10519d9e..36886d9f63 100644 --- a/vlib/os/os_windows.c.v +++ b/vlib/os/os_windows.c.v @@ -94,6 +94,57 @@ 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) ? { + $if debug { + // FindFirstFile() and FindNextFile() both have a globbing function. + // Unfortunately this is not as pronounced as under Unix, but should provide some functionality + eprintln('os.glob() does not have all the features on Windows as it has on Unix operating systems') + } + mut find_file_data := Win32finddata{} + wpattern := pattern.replace('/', '\\').to_wide() + h_find_files := C.FindFirstFile(wpattern, voidptr(&find_file_data)) + + defer { + C.FindClose(h_find_files) + } + + if h_find_files == C.INVALID_HANDLE_VALUE { + return error('os.glob(): Could not get a file handle: ' + + get_error_msg(int(C.GetLastError()))) + } + + // save first finding + fname := unsafe { string_from_wide(&find_file_data.c_file_name[0]) } + if fname !in ['.', '..'] { + mut fp := fname.replace('\\', '/') + if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 { + fp += '/' + } + matches << fp + } + + // check and save next findings + for i := 0; C.FindNextFile(h_find_files, voidptr(&find_file_data)) > 0; i++ { + filename := unsafe { string_from_wide(&find_file_data.c_file_name[0]) } + if filename in ['.', '..'] { + continue + } + mut fpath := filename.replace('\\', '/') + if find_file_data.dw_file_attributes & u32(C.FILE_ATTRIBUTE_DIRECTORY) > 0 { + fpath += '/' + } + matches << fpath + } +} + pub fn ls(path string) ?[]string { mut find_file_data := Win32finddata{} mut dir_files := []string{}