dir_walker.odin 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. package os2
  2. import "core:container/queue"
  3. /*
  4. A recursive directory walker.
  5. Note that none of the fields should be accessed directly.
  6. */
  7. Walker :: struct {
  8. todo: queue.Queue(string),
  9. skip_dir: bool,
  10. err: struct {
  11. path: [dynamic]byte,
  12. err: Error,
  13. },
  14. iter: Read_Directory_Iterator,
  15. }
  16. walker_init_path :: proc(w: ^Walker, path: string) {
  17. cloned_path, err := clone_string(path, file_allocator())
  18. if err != nil {
  19. walker_set_error(w, path, err)
  20. return
  21. }
  22. walker_clear(w)
  23. if _, err = queue.push(&w.todo, cloned_path); err != nil {
  24. walker_set_error(w, cloned_path, err)
  25. return
  26. }
  27. }
  28. walker_init_file :: proc(w: ^Walker, f: ^File) {
  29. handle, err := clone(f)
  30. if err != nil {
  31. path, _ := clone_string(name(f), file_allocator())
  32. walker_set_error(w, path, err)
  33. return
  34. }
  35. walker_clear(w)
  36. read_directory_iterator_init(&w.iter, handle)
  37. }
  38. /*
  39. Initializes a walker, either using a path or a file pointer to a directory the walker will start at.
  40. You are allowed to repeatedly call this to reuse it for later walks.
  41. For an example on how to use the walker, see `walker_walk`.
  42. */
  43. walker_init :: proc {
  44. walker_init_path,
  45. walker_init_file,
  46. }
  47. @(require_results)
  48. walker_create_path :: proc(path: string) -> (w: Walker) {
  49. walker_init_path(&w, path)
  50. return
  51. }
  52. @(require_results)
  53. walker_create_file :: proc(f: ^File) -> (w: Walker) {
  54. walker_init_file(&w, f)
  55. return
  56. }
  57. /*
  58. Creates a walker, either using a path or a file pointer to a directory the walker will start at.
  59. For an example on how to use the walker, see `walker_walk`.
  60. */
  61. walker_create :: proc {
  62. walker_create_path,
  63. walker_create_file,
  64. }
  65. /*
  66. Returns the last error that occurred during the walker's operations.
  67. Can be called while iterating, or only at the end to check if anything failed.
  68. */
  69. @(require_results)
  70. walker_error :: proc(w: ^Walker) -> (path: string, err: Error) {
  71. return string(w.err.path[:]), w.err.err
  72. }
  73. @(private)
  74. walker_set_error :: proc(w: ^Walker, path: string, err: Error) {
  75. if err == nil {
  76. return
  77. }
  78. resize(&w.err.path, len(path))
  79. copy(w.err.path[:], path)
  80. w.err.err = err
  81. }
  82. @(private)
  83. walker_clear :: proc(w: ^Walker) {
  84. w.iter.f = nil
  85. w.skip_dir = false
  86. w.err.path.allocator = file_allocator()
  87. clear(&w.err.path)
  88. w.todo.data.allocator = file_allocator()
  89. for path in queue.pop_front_safe(&w.todo) {
  90. delete(path, file_allocator())
  91. }
  92. }
  93. walker_destroy :: proc(w: ^Walker) {
  94. walker_clear(w)
  95. queue.destroy(&w.todo)
  96. delete(w.err.path)
  97. read_directory_iterator_destroy(&w.iter)
  98. }
  99. // Marks the current directory to be skipped (not entered into).
  100. walker_skip_dir :: proc(w: ^Walker) {
  101. w.skip_dir = true
  102. }
  103. /*
  104. Returns the next file info in the iterator, files are iterated in breadth-first order.
  105. If an error occurred opening a directory, you may get zero'd info struct and
  106. `walker_error` will return the error.
  107. Example:
  108. package main
  109. import "core:fmt"
  110. import "core:strings"
  111. import os "core:os/os2"
  112. main :: proc() {
  113. w := os.walker_create("core")
  114. defer os.walker_destroy(&w)
  115. for info in os.walker_walk(&w) {
  116. // Optionally break on the first error:
  117. // _ = walker_error(&w) or_break
  118. // Or, handle error as we go:
  119. if path, err := os.walker_error(&w); err != nil {
  120. fmt.eprintfln("failed walking %s: %s", path, err)
  121. continue
  122. }
  123. // Or, do not handle errors during iteration, and just check the error at the end.
  124. // Skip a directory:
  125. if strings.has_suffix(info.fullpath, ".git") {
  126. os.walker_skip_dir(&w)
  127. continue
  128. }
  129. fmt.printfln("%#v", info)
  130. }
  131. // Handle error if one happened during iteration at the end:
  132. if path, err := os.walker_error(&w); err != nil {
  133. fmt.eprintfln("failed walking %s: %v", path, err)
  134. }
  135. }
  136. */
  137. @(require_results)
  138. walker_walk :: proc(w: ^Walker) -> (fi: File_Info, ok: bool) {
  139. if w.skip_dir {
  140. w.skip_dir = false
  141. if skip, sok := queue.pop_back_safe(&w.todo); sok {
  142. delete(skip, file_allocator())
  143. }
  144. }
  145. if w.iter.f == nil {
  146. if queue.len(w.todo) == 0 {
  147. return
  148. }
  149. next := queue.pop_front(&w.todo)
  150. handle, err := open(next)
  151. if err != nil {
  152. walker_set_error(w, next, err)
  153. return {}, true
  154. }
  155. read_directory_iterator_init(&w.iter, handle)
  156. delete(next, file_allocator())
  157. }
  158. info, _, iter_ok := read_directory_iterator(&w.iter)
  159. if path, err := read_directory_iterator_error(&w.iter); err != nil {
  160. walker_set_error(w, path, err)
  161. }
  162. if !iter_ok {
  163. close(w.iter.f)
  164. w.iter.f = nil
  165. return walker_walk(w)
  166. }
  167. if info.type == .Directory {
  168. path, err := clone_string(info.fullpath, file_allocator())
  169. if err != nil {
  170. walker_set_error(w, "", err)
  171. return
  172. }
  173. _, err = queue.push_back(&w.todo, path)
  174. if err != nil {
  175. walker_set_error(w, path, err)
  176. return
  177. }
  178. }
  179. return info, iter_ok
  180. }