From 1d21740afb7785535d6e1cf01aa92a039ec3cdf0 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 28 Sep 2020 12:29:14 +0100 Subject: [PATCH] Add `filepath.walk` --- core/path/filepath/walk.odin | 88 ++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 core/path/filepath/walk.odin diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin new file mode 100644 index 000000000..4143bc557 --- /dev/null +++ b/core/path/filepath/walk.odin @@ -0,0 +1,88 @@ +package filepath + +import "core:os" +import "core:sort" + +// Walk_Proc is the type of the procedure called for each file or directory visited by 'walk' +// The 'path' parameter contains the parameter to walk as a prefix (this is the same as info.fullpath except on 'root') +// The 'info' parameter is the os.File_Info for the named path +// +// If there was a problem walking to the file or directory named by path, the incoming error will describe the problem +// and the procedure can decide how to handle that error (and walk will not descend into that directory) +// In the case of an error, the info argument will be 0 +// If an error is returned, processing stops +// The sole exception is if 'skip_dir' is returned as true: +// when 'skip_dir' is invoked on a directory. 'walk' skips directory contents +// when 'skip_dir' is invoked on a non-directory. 'walk' skips the remaining files in the containing directory +Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno) -> (err: os.Errno, skip_dir: bool); + +// walk walks the file tree rooted at 'root', calling 'walk_proc' for each file or directory in the tree, including 'root' +// All errors that happen visiting files and directories are filtered by walk_proc +// The files are walked in lexical order to make the output deterministic +// NOTE: Walking large directories can be inefficient due to the lexical sort +// NOTE: walk does not follow symbolic links +// NOTE: os.File_Info uses the 'context.temp_allocator' to allocate, and will delete when it is done +walk :: proc(root: string, walk_proc: Walk_Proc) -> os.Errno { + info, err := os.lstat(root, context.temp_allocator); + defer os.file_info_delete(info, context.temp_allocator); + + skip_dir: bool; + if err != 0 { + err, skip_dir = walk_proc(info, err); + } else { + err, skip_dir = _walk(info, walk_proc); + } + return 0 if skip_dir else err; +} + + +@(private) +_walk :: proc(info: os.File_Info, walk_proc: Walk_Proc) -> (err: os.Errno, skip_dir: bool) { + if !info.is_dir { + if info.fullpath == "" && info.name == "" { + // ignore empty things + return; + } + return walk_proc(info, 0); + } + + fis: []os.File_Info; + err1: os.Errno; + fis, err = read_dir(info.fullpath, context.temp_allocator); + defer os.file_info_slice_delete(fis, context.temp_allocator); + + err1, skip_dir = walk_proc(info, err); + if err != 0 || err1 != 0 || skip_dir { + err = err1; + return; + } + + for fi in fis { + err, skip_dir = _walk(fi, walk_proc); + if err != 0 || skip_dir { + if !fi.is_dir || !skip_dir { + return; + } + } + } + + return; +} + +@(private) +read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> ([]os.File_Info, os.Errno) { + f, err := os.open(dir_name); + if err != 0 { + return nil, err; + } + fis: []os.File_Info; + fis, err = os.read_dir(f, -1, allocator); + os.close(f); + if err != 0 { + return nil, err; + } + sort.quick_sort_proc(fis, proc(a, b: os.File_Info) -> int { + return sort.compare_strings(a.name, b.name); + }); + return fis, 0; +}