mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-05 04:27:51 +00:00
Adds a directory walker, a method of exposing and retrieving errors from the existing read directory iterator, allows reusing of the existing read directory iterator, and adds a file clone procedure
231 lines
4.6 KiB
Odin
231 lines
4.6 KiB
Odin
package os2
|
|
|
|
import "core:container/queue"
|
|
|
|
/*
|
|
A recursive directory walker.
|
|
|
|
Note that none of the fields should be accessed directly.
|
|
*/
|
|
Walker :: struct {
|
|
todo: queue.Queue(string),
|
|
skip_dir: bool,
|
|
err: struct {
|
|
path: [dynamic]byte,
|
|
err: Error,
|
|
},
|
|
iter: Read_Directory_Iterator,
|
|
}
|
|
|
|
walker_init_path :: proc(w: ^Walker, path: string) {
|
|
cloned_path, err := clone_string(path, file_allocator())
|
|
if err != nil {
|
|
walker_set_error(w, path, err)
|
|
return
|
|
}
|
|
|
|
walker_clear(w)
|
|
|
|
if _, err = queue.push(&w.todo, cloned_path); err != nil {
|
|
walker_set_error(w, cloned_path, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
walker_init_file :: proc(w: ^Walker, f: ^File) {
|
|
handle, err := clone(f)
|
|
if err != nil {
|
|
path, _ := clone_string(name(f), file_allocator())
|
|
walker_set_error(w, path, err)
|
|
return
|
|
}
|
|
|
|
walker_clear(w)
|
|
|
|
read_directory_iterator_init(&w.iter, handle)
|
|
}
|
|
|
|
/*
|
|
Initializes a walker, either using a path or a file pointer to a directory the walker will start at.
|
|
|
|
You are allowed to repeatedly call this to reuse it for later walks.
|
|
|
|
For an example on how to use the walker, see `walker_walk`.
|
|
*/
|
|
walker_init :: proc {
|
|
walker_init_path,
|
|
walker_init_file,
|
|
}
|
|
|
|
@(require_results)
|
|
walker_create_path :: proc(path: string) -> (w: Walker) {
|
|
walker_init_path(&w, path)
|
|
return
|
|
}
|
|
|
|
@(require_results)
|
|
walker_create_file :: proc(f: ^File) -> (w: Walker) {
|
|
walker_init_file(&w, f)
|
|
return
|
|
}
|
|
|
|
/*
|
|
Creates a walker, either using a path or a file pointer to a directory the walker will start at.
|
|
|
|
For an example on how to use the walker, see `walker_walk`.
|
|
*/
|
|
walker_create :: proc {
|
|
walker_create_path,
|
|
walker_create_file,
|
|
}
|
|
|
|
/*
|
|
Returns the last error that occurred during the walker's operations.
|
|
|
|
Can be called while iterating, or only at the end to check if anything failed.
|
|
*/
|
|
@(require_results)
|
|
walker_error :: proc(w: ^Walker) -> (path: string, err: Error) {
|
|
return string(w.err.path[:]), w.err.err
|
|
}
|
|
|
|
@(private)
|
|
walker_set_error :: proc(w: ^Walker, path: string, err: Error) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
resize(&w.err.path, len(path))
|
|
copy(w.err.path[:], path)
|
|
|
|
w.err.err = err
|
|
}
|
|
|
|
@(private)
|
|
walker_clear :: proc(w: ^Walker) {
|
|
w.iter.f = nil
|
|
w.skip_dir = false
|
|
|
|
w.err.path.allocator = file_allocator()
|
|
clear(&w.err.path)
|
|
|
|
w.todo.data.allocator = file_allocator()
|
|
for path in queue.pop_front_safe(&w.todo) {
|
|
delete(path, file_allocator())
|
|
}
|
|
}
|
|
|
|
walker_destroy :: proc(w: ^Walker) {
|
|
walker_clear(w)
|
|
queue.destroy(&w.todo)
|
|
delete(w.err.path)
|
|
read_directory_iterator_destroy(&w.iter)
|
|
}
|
|
|
|
// Marks the current directory to be skipped (not entered into).
|
|
walker_skip_dir :: proc(w: ^Walker) {
|
|
w.skip_dir = true
|
|
}
|
|
|
|
/*
|
|
Returns the next file info in the iterator, files are iterated in breadth-first order.
|
|
|
|
If an error occurred opening a directory, you may get zero'd info struct and
|
|
`walker_error` will return the error.
|
|
|
|
Example:
|
|
package main
|
|
|
|
import "core:fmt"
|
|
import "core:strings"
|
|
import os "core:os/os2"
|
|
|
|
main :: proc() {
|
|
w := os.walker_create("core")
|
|
defer os.walker_destroy(&w)
|
|
|
|
for info in os.walker_walk(&w) {
|
|
// Optionally break on the first error:
|
|
// _ = walker_error(&w) or_break
|
|
|
|
// Or, handle error as we go:
|
|
if path, err := os.walker_error(&w); err != nil {
|
|
fmt.eprintfln("failed walking %s: %s", path, err)
|
|
continue
|
|
}
|
|
|
|
// Or, do not handle errors during iteration, and just check the error at the end.
|
|
|
|
|
|
|
|
// Skip a directory:
|
|
if strings.has_suffix(info.fullpath, ".git") {
|
|
os.walker_skip_dir(&w)
|
|
continue
|
|
}
|
|
|
|
fmt.printfln("%#v", info)
|
|
}
|
|
|
|
// Handle error if one happened during iteration at the end:
|
|
if path, err := os.walker_error(&w); err != nil {
|
|
fmt.eprintfln("failed walking %s: %v", path, err)
|
|
}
|
|
}
|
|
*/
|
|
@(require_results)
|
|
walker_walk :: proc(w: ^Walker) -> (fi: File_Info, ok: bool) {
|
|
if w.skip_dir {
|
|
w.skip_dir = false
|
|
if skip, sok := queue.pop_back_safe(&w.todo); sok {
|
|
delete(skip, file_allocator())
|
|
}
|
|
}
|
|
|
|
if w.iter.f == nil {
|
|
if queue.len(w.todo) == 0 {
|
|
return
|
|
}
|
|
|
|
next := queue.pop_front(&w.todo)
|
|
|
|
handle, err := open(next)
|
|
if err != nil {
|
|
walker_set_error(w, next, err)
|
|
return {}, true
|
|
}
|
|
|
|
read_directory_iterator_init(&w.iter, handle)
|
|
|
|
delete(next, file_allocator())
|
|
}
|
|
|
|
info, _, iter_ok := read_directory_iterator(&w.iter)
|
|
|
|
if path, err := read_directory_iterator_error(&w.iter); err != nil {
|
|
walker_set_error(w, path, err)
|
|
}
|
|
|
|
if !iter_ok {
|
|
close(w.iter.f)
|
|
w.iter.f = nil
|
|
return walker_walk(w)
|
|
}
|
|
|
|
if info.type == .Directory {
|
|
path, err := clone_string(info.fullpath, file_allocator())
|
|
if err != nil {
|
|
walker_set_error(w, "", err)
|
|
return
|
|
}
|
|
|
|
_, err = queue.push_back(&w.todo, path)
|
|
if err != nil {
|
|
walker_set_error(w, path, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
return info, iter_ok
|
|
}
|