• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / brs / #305

18 Jan 2024 09:05PM UTC coverage: 91.463% (+5.2%) from 86.214%
#305

push

TwitchBronBron
0.45.4

1796 of 2095 branches covered (85.73%)

Branch coverage included in aggregate %.

5275 of 5636 relevant lines covered (93.59%)

8947.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.12
/src/stdlib/File.ts
1
import { Callable, ValueKind, BrsString, BrsBoolean, RoArray, StdlibArgument } from "../brsTypes";
131✔
2
import { Interpreter } from "../interpreter";
3
import { URL } from "url";
131✔
4
import MemoryFileSystem from "memory-fs";
5
import * as nanomatch from "nanomatch";
131✔
6

7
import * as fs from "fs";
131✔
8
import * as path from "path";
131✔
9

10
type Volume = MemoryFileSystem | typeof fs;
11

12
/*
13
 * Returns a memfs volume based on the brs path uri.  For example, passing in
14
 * "tmp:/test.txt" will return the memfs temporary volume on the interpreter.
15
 *
16
 * Returns invalid in no appopriate volume is found for the path
17
 */
18
export function getVolumeByPath(interpreter: Interpreter, path: string): Volume | null {
131✔
19
    try {
65✔
20
        const protocol = new URL(path).protocol;
65✔
21
        if (protocol === "tmp:") {
63✔
22
            return interpreter.temporaryVolume;
33✔
23
        }
24
        if (protocol === "pkg:") {
30✔
25
            return fs;
23✔
26
        }
27
    } catch (err) {
28
        return null;
2✔
29
    }
30
    return null;
7✔
31
}
32

33
/*
34
 * Returns a memfs file path from a brs file uri
35
 *   ex. "tmp:/test/test1.txt" -> "/test/test1.txt"
36
 */
37
export function getPath(fileUri: string) {
131✔
38
    return new URL(fileUri).pathname;
61✔
39
}
40

41
/*
42
 * Returns a memfs file path from a brs file uri. If the brs file uri
43
 * has the "pkg" protocol, append the file path with our root directory
44
 * so that we're searching the correct place.
45
 *   ex. "tmp:/test/test1.txt" -> "/test/test1.txt"
46
 *   ex. "pkg:/test/test1.txt" -> "/path/to/proj/test/test1.txt"
47
 *
48
 */
49
export function getScopedPath(interpreter: Interpreter, fileUri: string) {
131✔
50
    let url = new URL(fileUri);
40✔
51
    let filePath = getPath(fileUri);
40✔
52
    if (url.protocol === "pkg:") {
40✔
53
        return path.join(interpreter.options.root, filePath);
9✔
54
    }
55

56
    return filePath;
31✔
57
}
58

59
/** Copies a file from src to dst, return true if successful */
60
export const CopyFile = new Callable("CopyFile", {
131✔
61
    signature: {
62
        args: [
63
            new StdlibArgument("source", ValueKind.String),
64
            new StdlibArgument("destination", ValueKind.String),
65
        ],
66
        returns: ValueKind.Boolean,
67
    },
68
    impl: (interpreter: Interpreter, src: BrsString, dst: BrsString) => {
69
        const srcVolume = getVolumeByPath(interpreter, src.value);
4✔
70
        if (srcVolume === null) {
4!
71
            return BrsBoolean.False;
×
72
        }
73
        const dstVolume = getVolumeByPath(interpreter, dst.value);
4✔
74
        if (dstVolume === null) {
4✔
75
            return BrsBoolean.False;
1✔
76
        }
77

78
        const srcMemfsPath = getScopedPath(interpreter, src.value);
3✔
79
        const dstMemfsPath = getScopedPath(interpreter, dst.value);
3✔
80
        try {
3✔
81
            let contents = srcVolume.readFileSync(srcMemfsPath);
3✔
82
            dstVolume.writeFileSync(dstMemfsPath, contents);
2✔
83
            return BrsBoolean.True;
2✔
84
        } catch (err) {
85
            return BrsBoolean.False;
1✔
86
        }
87
    },
88
});
89

90
/** Copies a file from src to dst, return true if successful */
91
export const MoveFile = new Callable("MoveFile", {
131✔
92
    signature: {
93
        args: [
94
            new StdlibArgument("source", ValueKind.String),
95
            new StdlibArgument("destination", ValueKind.String),
96
        ],
97
        returns: ValueKind.Boolean,
98
    },
99
    impl: (interpreter: Interpreter, src: BrsString, dst: BrsString) => {
100
        const srcVolume = getVolumeByPath(interpreter, src.value);
4✔
101
        if (srcVolume === null) {
4!
102
            return BrsBoolean.False;
×
103
        }
104
        const dstVolume = getVolumeByPath(interpreter, dst.value);
4✔
105
        if (dstVolume === null) {
4✔
106
            return BrsBoolean.False;
1✔
107
        }
108

109
        const srcMemfsPath = getScopedPath(interpreter, src.value);
3✔
110
        const dstMemfsPath = getScopedPath(interpreter, dst.value);
3✔
111
        try {
3✔
112
            let contents = srcVolume.readFileSync(srcMemfsPath);
3✔
113
            dstVolume.writeFileSync(dstMemfsPath, contents);
2✔
114
            srcVolume.unlinkSync(srcMemfsPath);
2✔
115
            return BrsBoolean.True;
2✔
116
        } catch (err) {
117
            return BrsBoolean.False;
1✔
118
        }
119
    },
120
});
121

122
/** Deletes a file, return true if successful */
123
export const DeleteFile = new Callable("DeleteFile", {
131✔
124
    signature: {
125
        args: [new StdlibArgument("file", ValueKind.String)],
126
        returns: ValueKind.Boolean,
127
    },
128
    impl: (interpreter: Interpreter, file: BrsString) => {
129
        const volume = getVolumeByPath(interpreter, file.value);
3✔
130
        if (volume === null) {
3!
131
            return BrsBoolean.False;
×
132
        }
133

134
        const memfsPath = getScopedPath(interpreter, file.value);
3✔
135
        try {
3✔
136
            volume.unlinkSync(memfsPath);
3✔
137
            return BrsBoolean.True;
2✔
138
        } catch (err) {
139
            return BrsBoolean.False;
1✔
140
        }
141
    },
142
});
143

144
/** Deletes a directory (if empty), return true if successful */
145
export const DeleteDirectory = new Callable("DeleteDirectory", {
131✔
146
    signature: {
147
        args: [new StdlibArgument("dir", ValueKind.String)],
148
        returns: ValueKind.Boolean,
149
    },
150
    impl: (interpreter: Interpreter, dir: BrsString) => {
151
        const volume = getVolumeByPath(interpreter, dir.value);
4✔
152
        if (volume === null) {
4!
153
            return BrsBoolean.False;
×
154
        }
155

156
        const memfsPath = getScopedPath(interpreter, dir.value);
4✔
157
        try {
4✔
158
            volume.rmdirSync(memfsPath);
4✔
159
            return BrsBoolean.True;
2✔
160
        } catch (err) {
161
            return BrsBoolean.False;
2✔
162
        }
163
    },
164
});
165

166
/** Creates a directory, return true if successful */
167
export const CreateDirectory = new Callable("CreateDirectory", {
131✔
168
    signature: {
169
        args: [new StdlibArgument("dir", ValueKind.String)],
170
        returns: ValueKind.Boolean,
171
    },
172
    impl: (interpreter: Interpreter, dir: BrsString) => {
173
        const volume = getVolumeByPath(interpreter, dir.value);
4✔
174
        if (volume === null) {
4!
175
            return BrsBoolean.False;
×
176
        }
177

178
        const memfsPath = getScopedPath(interpreter, dir.value);
4✔
179
        try {
4✔
180
            volume.mkdirSync(memfsPath);
4✔
181
            return BrsBoolean.True;
3✔
182
        } catch (err) {
183
            return BrsBoolean.False;
1✔
184
        }
185
    },
186
});
187

188
/** Stubbed function for formatting a drive; always returns false */
189
export const FormatDrive = new Callable("FormatDrive", {
131✔
190
    signature: {
191
        args: [
192
            new StdlibArgument("drive", ValueKind.String),
193
            new StdlibArgument("fs_type", ValueKind.String),
194
        ],
195
        returns: ValueKind.Boolean,
196
    },
197
    impl: (interpreter: Interpreter, dir: BrsString) => {
198
        if (process.env.NODE_ENV !== "test") {
2!
199
            console.error("`FormatDrive` is not implemented in `brs`.");
×
200
        }
201
        return BrsBoolean.False;
2✔
202
    },
203
});
204

205
/** Returns an array of paths in a directory */
206
export const ListDir = new Callable("ListDir", {
131✔
207
    signature: {
208
        args: [new StdlibArgument("path", ValueKind.String)],
209
        returns: ValueKind.Object,
210
    },
211
    impl: (interpreter: Interpreter, pathArg: BrsString) => {
212
        const volume = getVolumeByPath(interpreter, pathArg.value);
3✔
213
        if (volume === null) {
3!
214
            return new RoArray([]);
×
215
        }
216

217
        let localPath = getScopedPath(interpreter, pathArg.value);
3✔
218
        try {
3✔
219
            let subPaths = volume.readdirSync(localPath).map((s) => new BrsString(s));
4✔
220
            return new RoArray(subPaths);
2✔
221
        } catch (err) {
222
            return new RoArray([]);
1✔
223
        }
224
    },
225
});
226

227
/** Reads ascii file from file system. */
228
export const ReadAsciiFile = new Callable("ReadAsciiFile", {
131✔
229
    signature: {
230
        args: [new StdlibArgument("filepath", ValueKind.String)],
231
        returns: ValueKind.String,
232
    },
233
    impl: (interpreter: Interpreter, filepath: BrsString, text: BrsString) => {
234
        const volume = getVolumeByPath(interpreter, filepath.value);
2✔
235
        if (volume === null) {
2!
236
            return new BrsString("");
×
237
        }
238

239
        const memfsPath = getScopedPath(interpreter, filepath.value);
2✔
240
        return new BrsString(volume.readFileSync(memfsPath).toString());
2✔
241
    },
242
});
243

244
/** Writes a string to a temporary file. */
245
export const WriteAsciiFile = new Callable("WriteAsciiFile", {
131✔
246
    signature: {
247
        args: [
248
            new StdlibArgument("filepath", ValueKind.String),
249
            new StdlibArgument("text", ValueKind.String),
250
        ],
251
        returns: ValueKind.Boolean,
252
    },
253
    impl: (interpreter: Interpreter, filepath: BrsString, text: BrsString) => {
254
        const volume = getVolumeByPath(interpreter, filepath.value);
4✔
255
        if (volume === null) {
4✔
256
            return BrsBoolean.False;
2✔
257
        }
258

259
        const memfsPath = getScopedPath(interpreter, filepath.value);
2✔
260
        volume.writeFileSync(memfsPath, text.value);
2✔
261
        return BrsBoolean.True;
2✔
262
    },
263
});
264

265
/** Searches a directory for filenames that match a certain pattern. */
266
export const MatchFiles = new Callable("MatchFiles", {
131✔
267
    signature: {
268
        args: [
269
            new StdlibArgument("path", ValueKind.String),
270
            new StdlibArgument("pattern_in", ValueKind.String),
271
        ],
272
        returns: ValueKind.Object,
273
    },
274
    impl: (interpreter: Interpreter, pathArg: BrsString, patternIn: BrsString) => {
275
        let volume = getVolumeByPath(interpreter, pathArg.value);
9✔
276
        if (volume == null) {
9✔
277
            // TODO: replace with RoList when that's implemented
278
            return new RoArray([]);
1✔
279
        }
280

281
        let localPath = getScopedPath(interpreter, pathArg.value);
8✔
282
        try {
8✔
283
            let knownFiles = fs.readdirSync(localPath, "utf8");
8✔
284
            let matchedFiles = nanomatch.match(knownFiles, patternIn.value, {
7✔
285
                nocase: true,
286
                nodupes: true,
287
                noglobstar: true,
288
                nonegate: true,
289
            });
290

291
            matchedFiles = (matchedFiles || []).map((match: string) => new BrsString(match));
20!
292

293
            // TODO: replace with RoList when that's implemented
294
            return new RoArray(matchedFiles);
7✔
295
        } catch (err) {
296
            // TODO: replace with RoList when that's implemented
297
            return new RoArray([]);
1✔
298
        }
299
    },
300
});
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc