123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- 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
- }
|