walk.odin 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. package filepath
  2. import "core:os"
  3. import "core:slice"
  4. // Walk_Proc is the type of the procedure called for each file or directory visited by 'walk'
  5. // The 'path' parameter contains the parameter to walk as a prefix (this is the same as info.fullpath except on 'root')
  6. // The 'info' parameter is the os.File_Info for the named path
  7. //
  8. // If there was a problem walking to the file or directory named by path, the incoming error will describe the problem
  9. // and the procedure can decide how to handle that error (and walk will not descend into that directory)
  10. // In the case of an error, the info argument will be 0
  11. // If an error is returned, processing stops
  12. // The sole exception is if 'skip_dir' is returned as true:
  13. // when 'skip_dir' is invoked on a directory. 'walk' skips directory contents
  14. // when 'skip_dir' is invoked on a non-directory. 'walk' skips the remaining files in the containing directory
  15. Walk_Proc :: #type proc(info: os.File_Info, in_err: os.Errno) -> (err: os.Errno, skip_dir: bool);
  16. // walk walks the file tree rooted at 'root', calling 'walk_proc' for each file or directory in the tree, including 'root'
  17. // All errors that happen visiting files and directories are filtered by walk_proc
  18. // The files are walked in lexical order to make the output deterministic
  19. // NOTE: Walking large directories can be inefficient due to the lexical sort
  20. // NOTE: walk does not follow symbolic links
  21. // NOTE: os.File_Info uses the 'context.temp_allocator' to allocate, and will delete when it is done
  22. walk :: proc(root: string, walk_proc: Walk_Proc) -> os.Errno {
  23. info, err := os.lstat(root, context.temp_allocator);
  24. defer os.file_info_delete(info, context.temp_allocator);
  25. skip_dir: bool;
  26. if err != 0 {
  27. err, skip_dir = walk_proc(info, err);
  28. } else {
  29. err, skip_dir = _walk(info, walk_proc);
  30. }
  31. return 0 if skip_dir else err;
  32. }
  33. @(private)
  34. _walk :: proc(info: os.File_Info, walk_proc: Walk_Proc) -> (err: os.Errno, skip_dir: bool) {
  35. if !info.is_dir {
  36. if info.fullpath == "" && info.name == "" {
  37. // ignore empty things
  38. return;
  39. }
  40. return walk_proc(info, 0);
  41. }
  42. fis: []os.File_Info;
  43. err1: os.Errno;
  44. fis, err = read_dir(info.fullpath, context.temp_allocator);
  45. defer os.file_info_slice_delete(fis, context.temp_allocator);
  46. err1, skip_dir = walk_proc(info, err);
  47. if err != 0 || err1 != 0 || skip_dir {
  48. err = err1;
  49. return;
  50. }
  51. for fi in fis {
  52. err, skip_dir = _walk(fi, walk_proc);
  53. if err != 0 || skip_dir {
  54. if !fi.is_dir || !skip_dir {
  55. return;
  56. }
  57. }
  58. }
  59. return;
  60. }
  61. @(private)
  62. read_dir :: proc(dir_name: string, allocator := context.temp_allocator) -> ([]os.File_Info, os.Errno) {
  63. f, err := os.open(dir_name);
  64. if err != 0 {
  65. return nil, err;
  66. }
  67. fis: []os.File_Info;
  68. fis, err = os.read_dir(f, -1, allocator);
  69. os.close(f);
  70. if err != 0 {
  71. return nil, err;
  72. }
  73. slice.sort_by(fis, proc(a, b: os.File_Info) -> bool {
  74. return a.name < b.name;
  75. });
  76. return fis, 0;
  77. }