summaryrefslogtreecommitdiff
path: root/fs/walk.ts
blob: 92e4ba593f078a7da04300d0efe4a9e9bf3905e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import {
  FileInfo,
  cwd,
  readDir,
  readDirSync,
  readlink,
  readlinkSync,
  stat,
  statSync
} from "deno";
import { relative } from "path.ts";

export interface WalkOptions {
  maxDepth?: number;
  exts?: string[];
  match?: RegExp[];
  skip?: RegExp[];
  // FIXME don't use `any` here?
  onError?: (err: any) => void;
  followSymlinks?: Boolean;
}

/** Generate all files in a directory recursively.
 *
 *      for await (const fileInfo of walk()) {
 *        console.log(fileInfo.path);
 *        assert(fileInfo.isFile());
 *      };
 */
export async function* walk(
  dir: string = ".",
  options: WalkOptions = {}
): AsyncIterableIterator<FileInfo> {
  options.maxDepth -= 1;
  let ls: FileInfo[] = [];
  try {
    ls = await readDir(dir);
  } catch (err) {
    if (options.onError) {
      options.onError(err);
    }
  }
  for (let f of ls) {
    if (f.isSymlink()) {
      if (options.followSymlinks) {
        f = await resolve(f);
      } else {
        continue;
      }
    }
    if (f.isFile()) {
      if (include(f, options)) {
        yield f;
      }
    } else {
      if (!(options.maxDepth < 0)) {
        yield* walk(f.path, options);
      }
    }
  }
}

/** Generate all files in a directory recursively.
 *
 *      for (const fileInfo of walkSync()) {
 *        console.log(fileInfo.path);
 *        assert(fileInfo.isFile());
 *      };
 */
export function* walkSync(
  dir: string = ".",
  options: WalkOptions = {}
): IterableIterator<FileInfo> {
  options.maxDepth -= 1;
  let ls: FileInfo[] = [];
  try {
    ls = readDirSync(dir);
  } catch (err) {
    if (options.onError) {
      options.onError(err);
    }
  }
  for (let f of ls) {
    if (f.isSymlink()) {
      if (options.followSymlinks) {
        f = resolveSync(f);
      } else {
        continue;
      }
    }
    if (f.isFile()) {
      if (include(f, options)) {
        yield f;
      }
    } else {
      if (!(options.maxDepth < 0)) {
        yield* walkSync(f.path, options);
      }
    }
  }
}

function include(f: FileInfo, options: WalkOptions): Boolean {
  if (options.exts && !options.exts.some(ext => f.path.endsWith(ext))) {
    return false;
  }
  if (options.match && !options.match.some(pattern => pattern.test(f.path))) {
    return false;
  }
  if (options.skip && options.skip.some(pattern => pattern.test(f.path))) {
    return false;
  }
  return true;
}

async function resolve(f: FileInfo): Promise<FileInfo> {
  // This is the full path, unfortunately if we were to make it relative
  // it could resolve to a symlink and cause an infinite loop.
  const fpath = await readlink(f.path);
  f = await stat(fpath);
  // workaround path not being returned by stat
  f.path = fpath;
  return f;
}

function resolveSync(f: FileInfo): FileInfo {
  // This is the full path, unfortunately if we were to make it relative
  // it could resolve to a symlink and cause an infinite loop.
  const fpath = readlinkSync(f.path);
  f = statSync(fpath);
  // workaround path not being returned by stat
  f.path = fpath;
  return f;
}