@naanlang/naan 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +46 -0
- package/dist/env_web.js +431 -0
- package/dist/naan.min.js +73 -0
- package/frameworks/browser/browser.nlg +30 -0
- package/frameworks/browser/pouchdb/pouchdb.min.js +7 -0
- package/frameworks/browser/spark-md5/spark-md5.min.js +1 -0
- package/frameworks/browser/sworker.js +192 -0
- package/frameworks/browser/terminals.nlg +787 -0
- package/frameworks/browser/workers.nlg +386 -0
- package/frameworks/client/apiclient.nlg +183 -0
- package/frameworks/client/client.nlg +29 -0
- package/frameworks/client/psm_client.nlg +572 -0
- package/frameworks/common/common.nlg +464 -0
- package/frameworks/common/preferences.nlg +131 -0
- package/frameworks/common/repltools.nlg +100 -0
- package/frameworks/common/watching.nlg +97 -0
- package/frameworks/node/apiserver.nlg +399 -0
- package/frameworks/node/filesystem.nlg +1012 -0
- package/frameworks/node/gitter.nlg +234 -0
- package/frameworks/node/node.nlg +81 -0
- package/frameworks/node/psm_server.nlg +465 -0
- package/frameworks/node/worker.nlg +560 -0
- package/frameworks/project/build.nlg +1229 -0
- package/frameworks/project/jszip.js +30 -0
- package/frameworks/project/jszip.nlg +87 -0
- package/frameworks/project/proj_cliser.nlg +139 -0
- package/frameworks/project/proj_console.nlg +137 -0
- package/frameworks/project/proj_folder.nlg +143 -0
- package/frameworks/project/proj_lambda.nlg +137 -0
- package/frameworks/project/proj_static.nlg +138 -0
- package/frameworks/project/project.nlg +29 -0
- package/frameworks/project/projects.nlg +587 -0
- package/frameworks/project/uglifyjs.js +1 -0
- package/frameworks/project/uglifyjs.nlg +79 -0
- package/frameworks/running/debugcom.nlg +163 -0
- package/frameworks/running/debugnub.nlg +565 -0
- package/frameworks/running/debugutil.nlg +510 -0
- package/frameworks/running/executors.nlg +767 -0
- package/frameworks/running/running.nlg +151 -0
- package/frameworks/running/sourcecode.nlg +346 -0
- package/frameworks/running/taskexec.nlg +196 -0
- package/frameworks/storage/dbt_pouch.nlg +773 -0
- package/frameworks/storage/file_manager.nlg +772 -0
- package/frameworks/storage/psm.nlg +326 -0
- package/frameworks/storage/psm_dbtables.nlg +814 -0
- package/frameworks/storage/resources.nlg +445 -0
- package/frameworks/storage/storage.nlg +52 -0
- package/lib/browser/env_web.js +431 -0
- package/lib/browser/env_webworker.js +146 -0
- package/lib/browser/require.js +1653 -0
- package/lib/core/naanlib.js +73 -0
- package/lib/env_node.js +282 -0
- package/lib/env_nodeworker.js +165 -0
- package/lib/node_repl.js +25 -0
- package/lib/node_repl_init.nlg +52 -0
- package/package.json +13 -0
- package/plugins/gitClient/clientops.nlg +317 -0
- package/plugins/gitClient/gitClient.nlg +63 -0
- package/plugins/gitClient/gitutils.nlg +1331 -0
- package/plugins/gitClient/minimatch.js +945 -0
- package/plugins/gitClient/minimatch_license.txt +15 -0
- package/plugins/gitClient/minimatch_readme.md +208 -0
- package/plugins/nodeLib/nodeLib.nlg +26 -0
- package/plugins/nodeLib/node_cp.nlg +65 -0
- package/plugins/nodeLib/node_fs.nlg +137 -0
- package/plugins/nodeLib/node_https.nlg +172 -0
- package/plugins/nodeLib/node_util.nlg +180 -0
- package/plugins/serviceAws/aws/aws-sdk.min.js +88 -0
- package/plugins/serviceAws/aws/lambda_rest_index.js +122 -0
- package/plugins/serviceAws/aws/lambda_rest_init.nlg +58 -0
- package/plugins/serviceAws/aws/lambda_ws_index.js +498 -0
- package/plugins/serviceAws/aws/lambda_ws_init.nlg +49 -0
- package/plugins/serviceAws/aws_cloudwatchlogs.nlg +122 -0
- package/plugins/serviceAws/aws_cognito.nlg +76 -0
- package/plugins/serviceAws/aws_dynamo.nlg +509 -0
- package/plugins/serviceAws/aws_lambda.nlg +95 -0
- package/plugins/serviceAws/aws_s3.nlg +134 -0
- package/plugins/serviceAws/aws_utils.nlg +86 -0
- package/plugins/serviceAws/dbt_aws.nlg +769 -0
- package/plugins/serviceAws/lambda_naan_control.nlg +175 -0
- package/plugins/serviceAws/psm_aws.nlg +258 -0
- package/plugins/serviceAws/serviceAws.nlg +38 -0
- package/plugins/serviceGitHub/gitops.nlg +522 -0
- package/plugins/serviceGitHub/psm_github.nlg +205 -0
- package/plugins/serviceGitHub/serviceGitHub.nlg +65 -0
- package/plugins/serviceGitLab/gitops.nlg +528 -0
- package/plugins/serviceGitLab/psm_gitlab.nlg +205 -0
- package/plugins/serviceGitLab/serviceGitLab.nlg +65 -0
- package/test/harness.nlg +312 -0
- package/test/node_test.js +33 -0
- package/test/test_01_core.nlg +768 -0
- package/test/test_02_context.nlg +269 -0
- package/test/test_03_jsinterop.nlg +137 -0
- package/test/test_04_numerics.nlg +450 -0
- package/test/test_05_strings.nlg +63 -0
- package/test/test_06_lingoparse.nlg +500 -0
- package/test/test_07_lingo.nlg +623 -0
|
@@ -0,0 +1,1012 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* filesystem.nlg
|
|
3
|
+
* Naanlib/frameworks/node
|
|
4
|
+
*
|
|
5
|
+
* File and directory operations on the host filesystem.
|
|
6
|
+
*
|
|
7
|
+
* column positioning: // // !
|
|
8
|
+
*
|
|
9
|
+
* Copyright (c) 2017-2021 by Richard C. Zulch
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Filesystem
|
|
16
|
+
*
|
|
17
|
+
* Access to the NodeJS filesystem. Methods take a callback, which if not provided will cause
|
|
18
|
+
* execution to block until complete. The return value is always a tuple of (error, data).
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
closure Filesystem(rootpath, local files, badCreatePathRegEx) {
|
|
23
|
+
if !rootpath
|
|
24
|
+
return (list(Error("mandatory rootpath missing")))
|
|
25
|
+
else if rootpath == ""
|
|
26
|
+
rootpath = js.g.process.cwd() // NodeJS uses CWD for empty rootpath
|
|
27
|
+
files = new(object, this)
|
|
28
|
+
files.rootpath = rootpath
|
|
29
|
+
files.path = JSpath
|
|
30
|
+
|
|
31
|
+
// commonParents
|
|
32
|
+
//
|
|
33
|
+
// Find the common parents between two paths, or "" if they have nothing in common.
|
|
34
|
+
|
|
35
|
+
function commonParents(short, long, local swap) {
|
|
36
|
+
if short.length > long.length {
|
|
37
|
+
swap = long
|
|
38
|
+
long = short
|
|
39
|
+
short = swap
|
|
40
|
+
}
|
|
41
|
+
loop {
|
|
42
|
+
if long.startsWith(short)
|
|
43
|
+
return (short)
|
|
44
|
+
swap = JSpath.dirname(short)
|
|
45
|
+
if swap == short
|
|
46
|
+
break
|
|
47
|
+
short = swap
|
|
48
|
+
}
|
|
49
|
+
return ("")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// localPath
|
|
53
|
+
//
|
|
54
|
+
// Convert a path argument to a local path, returning a standard result tuple. If the path is
|
|
55
|
+
// invalid this will report why. Otherwise this returns the correct localpath, which is the path
|
|
56
|
+
// we use with the underlying API.
|
|
57
|
+
|
|
58
|
+
badCreatePathRegEx = RegExp("[:]|[/\\\\][/\\\\]|[/\\\\]$|(^|[/\\\\])[.][.]?$")
|
|
59
|
+
|
|
60
|
+
closure localPath(path, create, local pattern, testpath) {
|
|
61
|
+
if !path
|
|
62
|
+
return (list(Error("filesystem path false")))
|
|
63
|
+
else if !string(path)
|
|
64
|
+
return (list(Error("filesystem path invalid:", typeof(path))))
|
|
65
|
+
if JSpath.isAbsolute(path)
|
|
66
|
+
path = JSpath.resolve(path) // puts C: in front of \abs\win\path
|
|
67
|
+
else
|
|
68
|
+
path = JSpath.normalize(path) // normalize for current platform
|
|
69
|
+
if JSpath === JSpath.win32
|
|
70
|
+
pattern = "\\:\\" // unchanged and unique on win32
|
|
71
|
+
else
|
|
72
|
+
pattern = "/:/" // unchanged and unique on posix
|
|
73
|
+
if ((testpath = commonParents(path, rootpath)) != "")
|
|
74
|
+
list(false, JSpath.resolve(rootpath, path)) // ### shouldn't be so lax on this test
|
|
75
|
+
else if ((testpath = JSpath.resolve(pattern, path)).indexOf(pattern.slice(0,-1)) < 0)
|
|
76
|
+
list(Error("filesystem path reaches above root:", path))
|
|
77
|
+
else if create && testpath.indexOf(pattern) < 0
|
|
78
|
+
list(Error("filesystem create path cannot be root:", path))
|
|
79
|
+
else if create && badCreatePathRegEx.test(path)
|
|
80
|
+
list(Error("filesystem create path invalid", path))
|
|
81
|
+
else
|
|
82
|
+
list(false, JSpath.resolve(rootpath, path))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// winDrives
|
|
86
|
+
//
|
|
87
|
+
// Return an array of the drive letters on windows, or call back with false otherwise.
|
|
88
|
+
|
|
89
|
+
files.winDrives = closure winDrives(callback) {
|
|
90
|
+
if !callback
|
|
91
|
+
return (syncAdapter(winDrives))
|
|
92
|
+
if js.g.process.platform != "win32"
|
|
93
|
+
return (asyncResult(callback, false)) // for unixen, return nothing
|
|
94
|
+
nodecp.execFile("wmic",
|
|
95
|
+
["logicaldisk", "get", "name"], // get name field for each mapped drive
|
|
96
|
+
{ }, // execution options
|
|
97
|
+
function(err, stdout, stderr) {
|
|
98
|
+
if stdout && string(stdout)
|
|
99
|
+
callback(false, new(stdout.split("\r\r\n")
|
|
100
|
+
.filter(function(value){RegExp("[A-Za-z]:").test(value)})
|
|
101
|
+
.map(function(value){value.trim()})))
|
|
102
|
+
else
|
|
103
|
+
callback(Error("exec(wmic) failed", err))
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// testHidden
|
|
109
|
+
//
|
|
110
|
+
// Test if the specified file or directory is hidden. On Windows this executes the attrib utility
|
|
111
|
+
// to obtain the hidden attribute.
|
|
112
|
+
|
|
113
|
+
files.testHidden = closure testHidden(path, callback, local darwinHiddenDirs, found, error, localpath) {
|
|
114
|
+
if !callback
|
|
115
|
+
return (syncAdapter(testHidden, path))
|
|
116
|
+
`(error, localpath) = localPath(path)
|
|
117
|
+
if error
|
|
118
|
+
return (asyncResult(callback, error))
|
|
119
|
+
darwinHiddenDirs = {
|
|
120
|
+
"/bin": true,
|
|
121
|
+
"/cores": true,
|
|
122
|
+
"/dev": true,
|
|
123
|
+
"/etc": true,
|
|
124
|
+
"/home": true,
|
|
125
|
+
"/net": true,
|
|
126
|
+
"/network": true,
|
|
127
|
+
"/private": true,
|
|
128
|
+
"/sbin": true,
|
|
129
|
+
"/tmp": true,
|
|
130
|
+
"/usr": true,
|
|
131
|
+
"/var": true }
|
|
132
|
+
if js.g.process.platform == "darwin" && darwinHiddenDirs[localpath]
|
|
133
|
+
return (asyncResult(callback, false, true)) // Darwin has specific named hidden folders
|
|
134
|
+
if js.g.process.platform != "win32" {
|
|
135
|
+
found = JSpath.basename(localpath)
|
|
136
|
+
return (asyncResult(callback, false, RegExp("^\\.").test(found))) } // for unixen, leading dot for hidden
|
|
137
|
+
nodecp.execFile("attrib",
|
|
138
|
+
["/D", localpath], // "/D" to include directories
|
|
139
|
+
{ }, // execution options
|
|
140
|
+
function(err, stdout, stderr) {
|
|
141
|
+
if stdout && string(stdout) {
|
|
142
|
+
found = stdout.match(RegExp("^[A-Z ]{11}"))
|
|
143
|
+
callback(false, found[0].indexOf("H") >= 0) } // "H" for hidden
|
|
144
|
+
else
|
|
145
|
+
callback(Error("exec(attrib) failed", err))
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// readLines
|
|
151
|
+
//
|
|
152
|
+
// Read the contents of a file into an array of lines with the following options:
|
|
153
|
+
// pattern: -- only return lines matching pattern
|
|
154
|
+
// fixed -- pattern is a fixed string, not a regular expression
|
|
155
|
+
// ignoreCase: -- ignore case during match
|
|
156
|
+
// invert: -- return lines that DO NOT match the regex
|
|
157
|
+
// returnText: -- return text of matched lines
|
|
158
|
+
//
|
|
159
|
+
|
|
160
|
+
files.readLines = closure readLines(path, options, callback,
|
|
161
|
+
local localpath, error, instream, pattern, matchRE, liner, lines, lineno) {
|
|
162
|
+
if !callback
|
|
163
|
+
return (syncAdapter(readLines, path, options))
|
|
164
|
+
`(error, localpath) = localPath(path)
|
|
165
|
+
if error
|
|
166
|
+
return (asyncResult(callback, error))
|
|
167
|
+
instream = fs.createReadStream(localpath)
|
|
168
|
+
instream.on("error", function (err) {
|
|
169
|
+
callback(Error("stream failed:", localpath, err))
|
|
170
|
+
})
|
|
171
|
+
liner = NodeStreamLiner()
|
|
172
|
+
instream.pipe(liner)
|
|
173
|
+
liner.on("end", function() {
|
|
174
|
+
callback(false, lines)
|
|
175
|
+
})
|
|
176
|
+
lines = []
|
|
177
|
+
if options.pattern {
|
|
178
|
+
pattern = options.pattern
|
|
179
|
+
if options.fixed
|
|
180
|
+
pattern = pattern.replace(RegExp("\\.|\\0|\\\\|\\[|\\||\\(|\\*|\\+|\\?|\\{"), function(match) {
|
|
181
|
+
"\\".concat(match) // escape all special RegExp characters
|
|
182
|
+
})
|
|
183
|
+
try {
|
|
184
|
+
if options.ignoreCase
|
|
185
|
+
matchRE = RegExp(pattern, "i")
|
|
186
|
+
else
|
|
187
|
+
matchRE = RegExp(pattern)
|
|
188
|
+
} catch {
|
|
189
|
+
if true
|
|
190
|
+
return (callback(Error("search expression error", exception)))
|
|
191
|
+
}
|
|
192
|
+
lineno = 0
|
|
193
|
+
liner.on("readable", function (local line, matched) {
|
|
194
|
+
while (line = liner.read()) {
|
|
195
|
+
++lineno
|
|
196
|
+
matched = matchRE.test(line)
|
|
197
|
+
if matched && !options.invert || !matched && options.invert
|
|
198
|
+
if options.returnText
|
|
199
|
+
lines.push([lineno, line])
|
|
200
|
+
else
|
|
201
|
+
lines.push(lineno) }
|
|
202
|
+
})
|
|
203
|
+
} else
|
|
204
|
+
liner.on("readable", function (local line) {
|
|
205
|
+
while (line = liner.read())
|
|
206
|
+
lines.push(line)
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// readFile
|
|
211
|
+
//
|
|
212
|
+
// Read the contents of a file into a string.
|
|
213
|
+
|
|
214
|
+
files.readFile = closure readFile(path, options, callback, local nodeOptions, error, localpath) {
|
|
215
|
+
if !callback
|
|
216
|
+
return (syncAdapter(readFile, path, options))
|
|
217
|
+
`(error, localpath) = localPath(path)
|
|
218
|
+
if error
|
|
219
|
+
return (asyncResult(callback, error))
|
|
220
|
+
nodeOptions = { encoding: "utf8" }
|
|
221
|
+
if options.encoding == "binary"
|
|
222
|
+
nodeOptions.encoding = undefined
|
|
223
|
+
else if options.encoding
|
|
224
|
+
nodeOptions.encoding = options.encoding
|
|
225
|
+
fs.readFile(localpath, nodeOptions, function(err, strdata) {
|
|
226
|
+
if err
|
|
227
|
+
callback(Error("fs.readFile failed:", localpath, err))
|
|
228
|
+
else
|
|
229
|
+
callback(false, strdata)
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// writeFile
|
|
234
|
+
//
|
|
235
|
+
// Write a string to the specified file, with the following options:
|
|
236
|
+
// md5: <hash> -- existing file content must match hash to overwrite
|
|
237
|
+
// encoding: <type> -- default utf8
|
|
238
|
+
// The non-error result is a dictionary containing the new file contents hash.
|
|
239
|
+
|
|
240
|
+
files.writeFile = closure writeFile(path, data, options, callback, local error, localpath, nodeOptions, md5data, pending, result) {
|
|
241
|
+
if !callback
|
|
242
|
+
return (syncAdapter(writeFile, path, data, options))
|
|
243
|
+
`(error, localpath) = localPath(path)
|
|
244
|
+
if error
|
|
245
|
+
return (asyncResult(callback, error))
|
|
246
|
+
nodeOptions = { encoding: "utf8" }
|
|
247
|
+
if options.encoding
|
|
248
|
+
nodeOptions.encoding = options.encoding
|
|
249
|
+
md5data = HashMD5(data)
|
|
250
|
+
if options.md5 { // read file to verify it is unchanged
|
|
251
|
+
pending = new(nonce)
|
|
252
|
+
fs.readFile(localpath, nodeOptions, function(err, strdata) {
|
|
253
|
+
if err.code == "ENOENT"
|
|
254
|
+
result = false // no file to check
|
|
255
|
+
else
|
|
256
|
+
result = HashMD5(strdata)
|
|
257
|
+
pending.signal(result)
|
|
258
|
+
})
|
|
259
|
+
result = pending.wait() // result is existing hash, or false if none
|
|
260
|
+
if result && options.md5 != result { // current contents don't match check
|
|
261
|
+
if md5data == result
|
|
262
|
+
return (callback(false, { md5: md5data })) // but, oddly, current contents match data
|
|
263
|
+
else
|
|
264
|
+
return (callback(Error("existing file match failed:", { // someone beat us to the write
|
|
265
|
+
path: localpath
|
|
266
|
+
current: result
|
|
267
|
+
check: options.md5
|
|
268
|
+
}))) } }
|
|
269
|
+
fs.writeFile(localpath, data, nodeOptions, function (err) {
|
|
270
|
+
if err
|
|
271
|
+
callback(Error("fs.writeFile failed:", localpath, err))
|
|
272
|
+
else
|
|
273
|
+
callback(false, { md5: md5data })
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// delete
|
|
278
|
+
//
|
|
279
|
+
// Delete the file at the specified path, or a directory if the options dictionary includes:
|
|
280
|
+
// recursive: true -- "recursively" delete directory and children
|
|
281
|
+
|
|
282
|
+
files.delete = closure delete(path, options, callback, local error, localpath) {
|
|
283
|
+
if !callback
|
|
284
|
+
return (syncAdapter(delete, path, options))
|
|
285
|
+
`(error, localpath) = localPath(path)
|
|
286
|
+
if error
|
|
287
|
+
return (asyncResult(callback, error))
|
|
288
|
+
if options.recursive
|
|
289
|
+
fs.rmdir(localpath, function(err) {
|
|
290
|
+
if err
|
|
291
|
+
callback(Error("fs.rmdir failed:", localpath, err))
|
|
292
|
+
else
|
|
293
|
+
callback(false, { ok: true })
|
|
294
|
+
})
|
|
295
|
+
else
|
|
296
|
+
fs.unlink(localpath, function(err) {
|
|
297
|
+
if err
|
|
298
|
+
callback(Error("fs.unlink failed:", localpath, err))
|
|
299
|
+
else
|
|
300
|
+
callback(false, { ok: true })
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// info
|
|
305
|
+
//
|
|
306
|
+
// Report info on a file or directory.
|
|
307
|
+
|
|
308
|
+
files.info = closure info(path, callback, local error, localpath) {
|
|
309
|
+
if !callback
|
|
310
|
+
return (syncAdapter(info, path))
|
|
311
|
+
`(error, localpath) = localPath(path)
|
|
312
|
+
if error
|
|
313
|
+
return (asyncResult(callback, error))
|
|
314
|
+
fs.lstat(localpath, function(err, stat, local data, info, nodetype) {
|
|
315
|
+
if err
|
|
316
|
+
return (callback(Error("fs.stat failed:", localpath, err)))
|
|
317
|
+
info = {
|
|
318
|
+
type: "other"
|
|
319
|
+
length: stat.size
|
|
320
|
+
birthtimeMs: stat.birthtimeMs
|
|
321
|
+
birthtime: stat.birthtime
|
|
322
|
+
mtimeMs: stat.mtimeMs
|
|
323
|
+
mtime: stat.mtime
|
|
324
|
+
nodejs_stat: stat }
|
|
325
|
+
`(err, data) = files.testHidden(localpath)
|
|
326
|
+
if data.hidden
|
|
327
|
+
info.hidden = true
|
|
328
|
+
nodetype = mod(stat.mode / 4096, 16)
|
|
329
|
+
if nodetype == 4
|
|
330
|
+
info.type = "directory"
|
|
331
|
+
else if nodetype == 8
|
|
332
|
+
info.type = "file"
|
|
333
|
+
else if nodetype == 10
|
|
334
|
+
info.type = "symlink"
|
|
335
|
+
callback(false, info)
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// setTimes
|
|
340
|
+
//
|
|
341
|
+
// Set timestamps on a file or directory.
|
|
342
|
+
|
|
343
|
+
files.setTimes = closure setTimes(path, atime, mtime, callback, local error, localpath) {
|
|
344
|
+
if !callback
|
|
345
|
+
return (syncAdapter(setTimes, path, atime, mtime))
|
|
346
|
+
`(error, localpath) = localPath(path)
|
|
347
|
+
if error
|
|
348
|
+
return (asyncResult(callback, error))
|
|
349
|
+
if !numeric(atime, mtime)
|
|
350
|
+
return (asyncResult(callback, Error("invalid arguments:", atime, mtime)))
|
|
351
|
+
localpath = JSpath.resolve(rootpath, localpath)
|
|
352
|
+
fs.utimes(localpath, atime, mtime, function(err, stat) {
|
|
353
|
+
if err
|
|
354
|
+
callback(Error("fs.utimes failed:", localpath, err))
|
|
355
|
+
else
|
|
356
|
+
callback(false, { ok: true})
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// statfetch
|
|
361
|
+
//
|
|
362
|
+
// Update each element in the input array with info, in parallel if possible.
|
|
363
|
+
|
|
364
|
+
closure statfetch(input, parentpath, local kqdex, filepath, pending) {
|
|
365
|
+
// queuemore
|
|
366
|
+
//
|
|
367
|
+
// Make more requests, or signal when all are completed
|
|
368
|
+
|
|
369
|
+
function queuemore() {
|
|
370
|
+
while kqdex < input.length {
|
|
371
|
+
if pending.active >= 10
|
|
372
|
+
return // too busy
|
|
373
|
+
closure (kdex, local name) { // separate closure to keep record context
|
|
374
|
+
name = input[kdex].name
|
|
375
|
+
if name { // this record is fetchable
|
|
376
|
+
++pending.active
|
|
377
|
+
filepath = JSpath.join(parentpath, name)
|
|
378
|
+
files.info(filepath, function(error, info) {
|
|
379
|
+
if error
|
|
380
|
+
input[kdex].error = error // this record failed
|
|
381
|
+
else
|
|
382
|
+
input[kdex].info = info // this record succeeded
|
|
383
|
+
--pending.active
|
|
384
|
+
queuemore()
|
|
385
|
+
}) }
|
|
386
|
+
} (kqdex++) // issue one request
|
|
387
|
+
}
|
|
388
|
+
if pending.active == 0
|
|
389
|
+
pending.signal() // no more to do, and all done
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
pending = new(nonce)
|
|
393
|
+
pending.active = 0
|
|
394
|
+
kqdex = 0
|
|
395
|
+
queuemore()
|
|
396
|
+
pending.wait()
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// dirList
|
|
400
|
+
//
|
|
401
|
+
// Return array of files in a directory if it exists. If options.head is true then this reads
|
|
402
|
+
// info for each file as well.
|
|
403
|
+
|
|
404
|
+
files.dirList = closure dirList(path, options, callback, local error, localpath, output, nodelist, file) {
|
|
405
|
+
if path == "" && js.g.process.platform == "win32" && rootpath == "\\"
|
|
406
|
+
return (files.winDrives(callback)) // top level is drive letters in Windows
|
|
407
|
+
if !callback
|
|
408
|
+
return (syncAdapter(dirList, path, options))
|
|
409
|
+
`(error, localpath) = localPath(path)
|
|
410
|
+
if error
|
|
411
|
+
return (asyncResult(callback, error))
|
|
412
|
+
output = {
|
|
413
|
+
name: JSpath.basename(localpath)
|
|
414
|
+
path: path
|
|
415
|
+
data: {
|
|
416
|
+
platform: js.g.process.platform,
|
|
417
|
+
semantics: cond(if JSpath eq JSpath.win32 "win32", if true "posix")
|
|
418
|
+
pathsep: JSpath.sep
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
fs.readdir(localpath, { }, function(err, files) {
|
|
422
|
+
if err
|
|
423
|
+
return (callback(Error("fs.readdir failed:", localpath, err)))
|
|
424
|
+
output.children = []
|
|
425
|
+
for file in files
|
|
426
|
+
output.children.push({
|
|
427
|
+
name: file
|
|
428
|
+
})
|
|
429
|
+
if options.head
|
|
430
|
+
statfetch(output.children, localpath) // read all the info records
|
|
431
|
+
callback(false, output)
|
|
432
|
+
})
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// mkdir
|
|
436
|
+
//
|
|
437
|
+
// Make a directory at the specified path. If the directory already exists then the result is
|
|
438
|
+
// { exists: true }, otherwise if it was just created it's { created: true}
|
|
439
|
+
|
|
440
|
+
files.mkdir = closure mkdir(path, callback, local error, localpath) {
|
|
441
|
+
if !callback
|
|
442
|
+
return (syncAdapter(mkdir, path))
|
|
443
|
+
`(error, localpath) = localPath(path)
|
|
444
|
+
if error
|
|
445
|
+
return (asyncResult(callback, error))
|
|
446
|
+
fs.mkdir(localpath, { recursive: true }, function(errmkdir, local result) {
|
|
447
|
+
if errmkdir.code == "EEXIST"
|
|
448
|
+
fs.stat(localpath, function(errstat, stat) {
|
|
449
|
+
if errstat || !stat.isDirectory()
|
|
450
|
+
return (callback(Error("mkdir conflict:", errmkdir, localpath)))
|
|
451
|
+
else
|
|
452
|
+
return (callback(false, { exists: true })) // directory already exists
|
|
453
|
+
})
|
|
454
|
+
else if errmkdir
|
|
455
|
+
callback(Error("mkdir failed", errmkdir))
|
|
456
|
+
else
|
|
457
|
+
return (callback(false, { created: true })) // directory just created
|
|
458
|
+
})
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// tree
|
|
462
|
+
//
|
|
463
|
+
// Create a directory tree to the specified depth by recursive enumeration. The depthlimit is 1
|
|
464
|
+
// to enumerate only the children of the specified path, or false to include all recursive children.
|
|
465
|
+
// Errors are recorded in the resulting tree.
|
|
466
|
+
// Tree starts with the specified path and does not traverse device boundaries that it knows
|
|
467
|
+
// about. You'll need to list the drive letters (Win) and the volumes (Mac) yourself before asking
|
|
468
|
+
// for a tree on one of them.
|
|
469
|
+
//
|
|
470
|
+
// The minimum structure of an actual treedex is the following:
|
|
471
|
+
// {
|
|
472
|
+
// name: <name of this node>
|
|
473
|
+
// data: {
|
|
474
|
+
// <store-specific data definitions, like timestamps, etc.>
|
|
475
|
+
// }
|
|
476
|
+
// parents: <array of node names describing path from this node to root>
|
|
477
|
+
// error: <string> // error that occured while attempting to enumerate children
|
|
478
|
+
// children: <undefined> // definitively no children
|
|
479
|
+
// children: pending // children may exist, but not yet enumerated
|
|
480
|
+
// children: [] // empty array for non-leaf with no children, e.g. empty folder
|
|
481
|
+
// // (if error with empty array then enumeration failed)
|
|
482
|
+
// children: [ // each child node is like us, but no ownerID or parents array
|
|
483
|
+
// {
|
|
484
|
+
// name: <name of the child; must be non-empty and unique among children>
|
|
485
|
+
// iconID: <token identifying the icon to use>
|
|
486
|
+
// data: {
|
|
487
|
+
// <store-specific data definitions>
|
|
488
|
+
// }
|
|
489
|
+
// view: {
|
|
490
|
+
// <optional view-specific data definitions>
|
|
491
|
+
// }
|
|
492
|
+
// error: // as above, for grandchildren
|
|
493
|
+
// children: // as above, for grandchildren
|
|
494
|
+
// },
|
|
495
|
+
// ...
|
|
496
|
+
// ]
|
|
497
|
+
// }
|
|
498
|
+
|
|
499
|
+
files.tree = closure tree(path, depthlimit, callback, local error, localpath, root, pending, queue, data, rootstat) {
|
|
500
|
+
if !callback
|
|
501
|
+
return (syncAdapter(tree, path, depthlimit))
|
|
502
|
+
|
|
503
|
+
// recurTree
|
|
504
|
+
//
|
|
505
|
+
// Recursively build the tree
|
|
506
|
+
|
|
507
|
+
closure recurTree(localpath, depth, parent) {
|
|
508
|
+
if depthlimit && depth >= depthlimit
|
|
509
|
+
return
|
|
510
|
+
++pending.active
|
|
511
|
+
fs.readdir(localpath, { }, function(err, nodes, local child, node, stat) {
|
|
512
|
+
if err.code == "ENOTDIR"
|
|
513
|
+
parent.children = undefined // we are a leaf (in the wind)
|
|
514
|
+
else if err {
|
|
515
|
+
parent.children = [], // enumeration failed
|
|
516
|
+
parent.error = err.code } // keep lightweight
|
|
517
|
+
else { // we have children
|
|
518
|
+
parent.children = []
|
|
519
|
+
stat = files.info(localpath).1
|
|
520
|
+
if stat {
|
|
521
|
+
if stat.type == "symlink" {
|
|
522
|
+
parent.children = "symlink" // don't follow links
|
|
523
|
+
nodes = false }
|
|
524
|
+
else if stat.nodejs_stat.dev && stat.nodejs_stat.dev != rootstat.dev {
|
|
525
|
+
parent.children = "mountpoint" // don't enter mountpoints
|
|
526
|
+
nodes = false } }
|
|
527
|
+
for node in nodes {
|
|
528
|
+
child = {
|
|
529
|
+
name: node
|
|
530
|
+
children: "pending" // temporary unless depth limit reached
|
|
531
|
+
}
|
|
532
|
+
parent.children.push(child)
|
|
533
|
+
queue.push({
|
|
534
|
+
path: JSpath.join(localpath, child.name),
|
|
535
|
+
depth: depth+1,
|
|
536
|
+
parent: child
|
|
537
|
+
}) } }
|
|
538
|
+
--pending.active
|
|
539
|
+
while queue.length > 0 && pending.active < 20 {
|
|
540
|
+
node = queue.pop()
|
|
541
|
+
recurTree(node.path, node.depth, node.parent) }
|
|
542
|
+
if pending.active == 0 && queue.length == 0
|
|
543
|
+
pending.signal(list(false, root))
|
|
544
|
+
})
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
`(error, localpath) = localPath(path)
|
|
548
|
+
if error
|
|
549
|
+
return (asyncResult(callback, error))
|
|
550
|
+
if not integer(depthlimit)
|
|
551
|
+
depthlimit = false // default is all children
|
|
552
|
+
queue = []
|
|
553
|
+
root = {
|
|
554
|
+
name: JSpath.basename(localpath)
|
|
555
|
+
data: {
|
|
556
|
+
platform: js.g.process.platform,
|
|
557
|
+
semantics: cond(if JSpath eq JSpath.win32 "win32", if true "posix")
|
|
558
|
+
pathsep: JSpath.sep
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
root.parents = new(JSpath.dirname(localpath).split(JSpath.sep))
|
|
562
|
+
pending = new(nonce)
|
|
563
|
+
pending.active = 0
|
|
564
|
+
`(error, data) = files.info(localpath)
|
|
565
|
+
if !error {
|
|
566
|
+
rootstat = data.nodejs_stat
|
|
567
|
+
recurTree(localpath, 0, root)
|
|
568
|
+
`(error, data) = pending.wait() // returns `(false, root) when done
|
|
569
|
+
}
|
|
570
|
+
callback(error, data)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// dirSearch
|
|
574
|
+
//
|
|
575
|
+
// Search the files in a directory for a pattern.
|
|
576
|
+
|
|
577
|
+
files.dirSearch = closure dirSearch(path, options, callback,
|
|
578
|
+
local error, localpaths, output) {
|
|
579
|
+
if !callback
|
|
580
|
+
return (syncAdapter(dirSearch, path, options))
|
|
581
|
+
`(error, localpath) = localPath(path)
|
|
582
|
+
if error
|
|
583
|
+
return (asyncResult(callback, error))
|
|
584
|
+
output = []
|
|
585
|
+
debuglog("dirSearch begins", localpath)
|
|
586
|
+
asyncArray(localpaths, 10, closure(localpath, index, elements, local error, direnum, node, lines, line) {
|
|
587
|
+
`(error, direnum) = dirList(localpath, { })
|
|
588
|
+
if direnum.children
|
|
589
|
+
for node in direnum.children
|
|
590
|
+
localpaths.push(JSpath.join(localpath, node.name)) // more files or directories to examine
|
|
591
|
+
else {
|
|
592
|
+
`(error, lines) = readLines(localpath, options)
|
|
593
|
+
if error && localpaths.length == 1 {
|
|
594
|
+
error = Error("dirSearch failed:", localpath) // can't access the root
|
|
595
|
+
output = false }
|
|
596
|
+
else
|
|
597
|
+
for line in lines {
|
|
598
|
+
if array(line)
|
|
599
|
+
output.push([localpath].concat(line)) // [localpath, line, text]
|
|
600
|
+
else
|
|
601
|
+
output.push([localpath, line]) // [localpath, line]
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}).wait()
|
|
605
|
+
debuglog(" dirSearched", localpath, error, localpaths.length, output.length)
|
|
606
|
+
callback(error, output)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// copy
|
|
610
|
+
//
|
|
611
|
+
// Copy / move / rename a file or directory. The source is the full path of the item. The
|
|
612
|
+
// destination is:
|
|
613
|
+
// - a new name if it has no embedded slashes, or
|
|
614
|
+
// - a new location if it ends in a slash, otherwise
|
|
615
|
+
// - a new pathname for the source if it does not end in a slash.
|
|
616
|
+
// This works by creating a new file or directory of files in the new location and then
|
|
617
|
+
// optionally deleting the files in the existing location. If options.move is true then we
|
|
618
|
+
// delete the source after the copy.
|
|
619
|
+
// Supported options are:
|
|
620
|
+
// {
|
|
621
|
+
// move: <boolean> -- delete after successful copy
|
|
622
|
+
// overwrite: <boolean> -- overwrite existing file, if any
|
|
623
|
+
// }
|
|
624
|
+
|
|
625
|
+
files.copy = closure copy(srcpath, dest, options, callback, local error, localsrc, localdest, pending) {
|
|
626
|
+
if !callback
|
|
627
|
+
return (syncAdapter(copy, srcpath, dest, options))
|
|
628
|
+
`(error, localsrc) = localPath(srcpath)
|
|
629
|
+
if error
|
|
630
|
+
return (asyncResult(callback, error))
|
|
631
|
+
// ### the following localdest doesn't detect existing files in all cases
|
|
632
|
+
if dest.indexOf(JSpath.sep) < 0
|
|
633
|
+
localdest = JSpath.join(JSpath.dirname(localsrc), dest) // new name for existing source in same path location
|
|
634
|
+
else {
|
|
635
|
+
`(error, localdest) = localPath(dest)
|
|
636
|
+
if error
|
|
637
|
+
return (asyncResult(callback, error))
|
|
638
|
+
if dest.slice(-1) == JSpath.sep // new location, else entirely new pathname
|
|
639
|
+
localdest = JSpath.join(localdest, JSpath.basename(srcpath))
|
|
640
|
+
}
|
|
641
|
+
fs.lstat(localdest, closure(err, stat, local cmd, args) {
|
|
642
|
+
if !err && !options.overwrite
|
|
643
|
+
callback(Error("copy destination already exists",
|
|
644
|
+
{
|
|
645
|
+
status: 409
|
|
646
|
+
name: "conflict"
|
|
647
|
+
source: srcpath
|
|
648
|
+
destination: dest
|
|
649
|
+
existing: localdest
|
|
650
|
+
}))
|
|
651
|
+
else {
|
|
652
|
+
if options.move {
|
|
653
|
+
cmd = "mv"
|
|
654
|
+
args = [localsrc, localdest] }
|
|
655
|
+
else {
|
|
656
|
+
cmd = "cp"
|
|
657
|
+
args = ["-pR", localsrc, localdest] } // preserve file modes and attributes; copy directories
|
|
658
|
+
nodecp.execFile(cmd, args,
|
|
659
|
+
{ }, // execution options
|
|
660
|
+
function(err, stdout, stderr) {
|
|
661
|
+
if err {
|
|
662
|
+
debuglog("cp failed", err)
|
|
663
|
+
debuglog(" ", srcpath, "|", localsrc, "->", dest, "|", localdest)
|
|
664
|
+
}
|
|
665
|
+
if err
|
|
666
|
+
callback(Error(cmd.concat(" failed"), err, stderr))
|
|
667
|
+
else
|
|
668
|
+
callback(false, { ok: true })
|
|
669
|
+
}
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// reveal
|
|
676
|
+
//
|
|
677
|
+
// Reveal the specified filesystem path(s) in the MacOS Finder. The path should be a string
|
|
678
|
+
// or array thereof. This returns an array listing any errors. Needs a Windows version too.
|
|
679
|
+
|
|
680
|
+
files.reveal = closure reveal(path, callback, local errors, pathargs, item) {
|
|
681
|
+
if !callback
|
|
682
|
+
return (syncAdapter(reveal, path))
|
|
683
|
+
|
|
684
|
+
function addpath(path, local error, localpath) {
|
|
685
|
+
`(error, localpath) = localPath(path)
|
|
686
|
+
if error
|
|
687
|
+
errors.push(error)
|
|
688
|
+
else
|
|
689
|
+
pathargs.push("file://localhost".concat(localpath))
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
errors = []
|
|
693
|
+
pathargs = ["-R", "--"] // reveal, then stop using '-' for ags
|
|
694
|
+
if array(path)
|
|
695
|
+
for item in path
|
|
696
|
+
addpath(item)
|
|
697
|
+
else
|
|
698
|
+
addpath(path)
|
|
699
|
+
nodecp.execFile("open",
|
|
700
|
+
pathargs, // command-line options
|
|
701
|
+
{ }, // execution options
|
|
702
|
+
function(err, stdout, stderr) {
|
|
703
|
+
if err
|
|
704
|
+
callback(Error("open failed", err, stderr))
|
|
705
|
+
else
|
|
706
|
+
callback(false, errors)
|
|
707
|
+
}
|
|
708
|
+
)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
//
|
|
712
|
+
// minimize
|
|
713
|
+
//
|
|
714
|
+
// Execute the uglifyjs utility on the specified source, returning the result as a standard
|
|
715
|
+
// tuple: `(error, minified)
|
|
716
|
+
//
|
|
717
|
+
files.minimize = closure minimize(input, callback, local child, stdinStream) {
|
|
718
|
+
if !callback
|
|
719
|
+
return (syncAdapter(minimize, input))
|
|
720
|
+
child = nodecp.execFile("uglifyjs",
|
|
721
|
+
["--compress", "drop_debugger=false", "--mangle", "eval"], // ### uglifyjs options should be parameterized
|
|
722
|
+
function(err, stdout, stderr) {
|
|
723
|
+
debuglog("#minimize:", input.length, "->", stdout.length, "bytes")
|
|
724
|
+
if err
|
|
725
|
+
callback(Error("uglifyjs failed", err, stderr))
|
|
726
|
+
else
|
|
727
|
+
callback(false, stdout)
|
|
728
|
+
}
|
|
729
|
+
)
|
|
730
|
+
child.stdin.on("error", function(err) {
|
|
731
|
+
// make sure error isn't unhandled, so that a failed uglifyjs doesn't
|
|
732
|
+
// kill our process when the stdin write occurs after the pipe closes.
|
|
733
|
+
})
|
|
734
|
+
child.stdin.end(input)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
//
|
|
738
|
+
// unzip
|
|
739
|
+
//
|
|
740
|
+
// Execute the unzip utility, returning the result as a standard tuple: `(error, result)
|
|
741
|
+
//
|
|
742
|
+
files.unzip = closure unzip(inpath, outpath, callback) {
|
|
743
|
+
if !callback
|
|
744
|
+
return (syncAdapter(unzip, inpath, outpath))
|
|
745
|
+
nodecp.execFile("unzip",
|
|
746
|
+
["-qo", inpath, "-d", outpath], // quiet; no queries
|
|
747
|
+
{ }, // execution options
|
|
748
|
+
function(err, stdout, stderr) {
|
|
749
|
+
if err
|
|
750
|
+
callback(Error("unzip failed", err, stderr))
|
|
751
|
+
else
|
|
752
|
+
callback(false, { ok: true })
|
|
753
|
+
}
|
|
754
|
+
)
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
//
|
|
758
|
+
// touch
|
|
759
|
+
//
|
|
760
|
+
// Execute the touch utility, returning the result as a standard tuple: `(error, result)
|
|
761
|
+
//
|
|
762
|
+
files.touch = closure touch(outpath, callback) {
|
|
763
|
+
if !callback
|
|
764
|
+
return (syncAdapter(touch, outpath))
|
|
765
|
+
nodecp.execFile("touch",
|
|
766
|
+
[outpath],
|
|
767
|
+
{ }, // execution options
|
|
768
|
+
function(err, stdout, stderr) {
|
|
769
|
+
if err
|
|
770
|
+
callback(Error("touch failed", err, stderr))
|
|
771
|
+
else
|
|
772
|
+
callback(false, { ok: true })
|
|
773
|
+
}
|
|
774
|
+
)
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
//
|
|
778
|
+
// shellOpen
|
|
779
|
+
//
|
|
780
|
+
// Execute the MacOS open utility, returning a standard tuple: `(error, result)
|
|
781
|
+
//
|
|
782
|
+
files.shellOpen = closure shellOpen(path, args, callback) {
|
|
783
|
+
if !callback
|
|
784
|
+
return (syncAdapter(shellOpen, path, args))
|
|
785
|
+
if args
|
|
786
|
+
args = [path].concat("--args", args) // only add target args if specified
|
|
787
|
+
else
|
|
788
|
+
args = [path]
|
|
789
|
+
nodecp.execFile("open",
|
|
790
|
+
args, // command line args to open
|
|
791
|
+
{ }, // execution options
|
|
792
|
+
function(err, stdout, stderr) {
|
|
793
|
+
if err
|
|
794
|
+
callback(Error("shellOpen failed", err, stderr))
|
|
795
|
+
else
|
|
796
|
+
callback(false, { ok: true })
|
|
797
|
+
}
|
|
798
|
+
)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// grepLines
|
|
802
|
+
//
|
|
803
|
+
// Return an array of text matches in a file or recursive directory of files with the following
|
|
804
|
+
// options:
|
|
805
|
+
// pattern: -- pattern to find
|
|
806
|
+
// ignoreCase: -- ignore case during match
|
|
807
|
+
// invert: -- return lines that DO NOT match the regex
|
|
808
|
+
// returnText: -- return text of matched lines
|
|
809
|
+
// maxCount: -- integer maximum number of results (default 10,000)
|
|
810
|
+
// grep-specific options:
|
|
811
|
+
// ignoreBinary: -- ignore binary files
|
|
812
|
+
//
|
|
813
|
+
files.grepLines = closure grepLines(path, options, callback,
|
|
814
|
+
local error, localpath, cmdargs, execOptions, grep, result, lineRE, maxcount) {
|
|
815
|
+
if !callback
|
|
816
|
+
return (syncAdapter(grepLines, path, options))
|
|
817
|
+
`(error, localpath) = localPath(path)
|
|
818
|
+
if error
|
|
819
|
+
return (asyncResult(callback, error))
|
|
820
|
+
cmdargs = ["-rn"] // recursively search directories for files; report line numbers
|
|
821
|
+
if options.ignoreCase
|
|
822
|
+
cmdargs.push("-i")
|
|
823
|
+
if options.invert
|
|
824
|
+
cmdargs.push("-v")
|
|
825
|
+
if options.ignoreBinary
|
|
826
|
+
cmdargs.push("-I")
|
|
827
|
+
else
|
|
828
|
+
cmdargs.push("--binary-files=text")
|
|
829
|
+
if options.fixed
|
|
830
|
+
cmdargs.push("-F") // fixed pattern (not a regular expression)
|
|
831
|
+
else
|
|
832
|
+
cmdargs.push("-E") // work like e-grep
|
|
833
|
+
cmdargs.push("-e") // ensure that patterns starting with "-" are accepted
|
|
834
|
+
cmdargs.push(options.pattern)
|
|
835
|
+
cmdargs.push(JSpath.resolve(rootpath, localpath))
|
|
836
|
+
execOptions = { cmdargs: cmdargs }
|
|
837
|
+
result = []
|
|
838
|
+
lineRE = RegExp("([^:]*):([0-9]+):(.*)$") // <path>:<line-number>:<matching-text-line>
|
|
839
|
+
if integer(options.maxCount)
|
|
840
|
+
maxcount = options.maxCount
|
|
841
|
+
else
|
|
842
|
+
maxcount = 5000 // default is 5K lines maximum return
|
|
843
|
+
execOptions.outproc = function(output, liner, kill, local line, match) {
|
|
844
|
+
while (line = liner.read()) {
|
|
845
|
+
if result.length >= maxcount
|
|
846
|
+
return (kill())
|
|
847
|
+
match = line.match(lineRE)
|
|
848
|
+
if match {
|
|
849
|
+
item = [JSpath.relative(localpath, match.1), match.2]
|
|
850
|
+
if options.returnText
|
|
851
|
+
item.push(match.3)
|
|
852
|
+
result.push(item) // relative file path, line number, and (optional) text
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
execLines("grep", execOptions, function(error, output) {
|
|
857
|
+
if output.stderr
|
|
858
|
+
debuglog("grep stderr:", output.stderr.join("\n"))
|
|
859
|
+
if !error
|
|
860
|
+
output = result
|
|
861
|
+
callback(error, output)
|
|
862
|
+
})
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// execLines
|
|
866
|
+
//
|
|
867
|
+
// Execute a command and parse the results into lines, executing the callback when everything
|
|
868
|
+
// has completed. Options are:
|
|
869
|
+
// {
|
|
870
|
+
// cmdargs: -- array of string arguments to the command (see below)
|
|
871
|
+
// execops: -- optional exec options like cwd
|
|
872
|
+
// errproc: -- callback for stderr output lines, or false
|
|
873
|
+
// errLE: -- string or RegExp for stderr line ending
|
|
874
|
+
// outproc: -- optional callback for stdout output lines
|
|
875
|
+
// outLE: -- string or RegExp for stdout line ending
|
|
876
|
+
// output: -- optional output structure
|
|
877
|
+
// }
|
|
878
|
+
// The result is stored in an output structure, which can have:
|
|
879
|
+
// {
|
|
880
|
+
// exitCode: -- status code from execution, or null if killed
|
|
881
|
+
// stderr: -- array of stderr lines if errproc not specified
|
|
882
|
+
// stdout: -- array of stdout lines if outproc not specified
|
|
883
|
+
// }
|
|
884
|
+
// Errorproc and outproc have the same protocol, which is:
|
|
885
|
+
// proc(output, liner, kill)
|
|
886
|
+
// These functions are free to modify output, which is the eventual result of execLines if no
|
|
887
|
+
// overall error occurs. Each time liner is called, the result is either a line of text or false
|
|
888
|
+
// to indicate that there are no more lines available at this time. Calling kill() will terminate
|
|
889
|
+
// execution of the command immediately, setting the output.exitCode to null. When all data is
|
|
890
|
+
// transferred the procs are called one more time with both liner and kill false to give the
|
|
891
|
+
// proc a chance to finalize any output.
|
|
892
|
+
//
|
|
893
|
+
//
|
|
894
|
+
|
|
895
|
+
files.execLines = closure execLines(cmdname, options, callback, local output, arg, execops, spawn, linerErr, linerOut) {
|
|
896
|
+
if !callback
|
|
897
|
+
return (syncAdapter(execLines, cmdname, options))
|
|
898
|
+
if !string(cmdname) || cmdname == ""
|
|
899
|
+
return (asyncResult(callback, Error("invalid command:", typeof(cmdname))))
|
|
900
|
+
if options.output
|
|
901
|
+
output = options.output
|
|
902
|
+
else
|
|
903
|
+
output = {}
|
|
904
|
+
if options.execops
|
|
905
|
+
execops = options.execops
|
|
906
|
+
else
|
|
907
|
+
execops = { }
|
|
908
|
+
spawn = nodecp.spawn(cmdname, options.cmdargs, execops)
|
|
909
|
+
|
|
910
|
+
// completed
|
|
911
|
+
// execute callback only once
|
|
912
|
+
function completed(error, result) {
|
|
913
|
+
if callback {
|
|
914
|
+
callback(error, result)
|
|
915
|
+
callback = false } }
|
|
916
|
+
|
|
917
|
+
// kill
|
|
918
|
+
// terminate the command
|
|
919
|
+
function kill() {
|
|
920
|
+
grep.kill()
|
|
921
|
+
completed(false, output) } // the official end will never come
|
|
922
|
+
|
|
923
|
+
//
|
|
924
|
+
// process pipes and errors
|
|
925
|
+
//
|
|
926
|
+
spawn.stdin.end()
|
|
927
|
+
spawn.on("error", function(err) {
|
|
928
|
+
completed(Error("failed to spawn command ".concat(cmdname), err))
|
|
929
|
+
})
|
|
930
|
+
spawn.on("close", function(code) {
|
|
931
|
+
output.exitCode = code // zero on success; null on killed
|
|
932
|
+
})
|
|
933
|
+
spawn.stderr.on("error", function(err) {
|
|
934
|
+
completed(Error("stderr stream failed:", err))
|
|
935
|
+
})
|
|
936
|
+
spawn.stdout.on("error", function(err) {
|
|
937
|
+
completed(Error("stdout stream failed:", err))
|
|
938
|
+
})
|
|
939
|
+
|
|
940
|
+
//
|
|
941
|
+
// process stderr output
|
|
942
|
+
//
|
|
943
|
+
linerErr = NodeStreamLiner(options.errLE)
|
|
944
|
+
spawn.stderr.pipe(linerErr)
|
|
945
|
+
linerErr.on("end", function() {
|
|
946
|
+
if options.errproc
|
|
947
|
+
call(options.errproc, output)
|
|
948
|
+
if linerOut
|
|
949
|
+
linerErr = false // let end-of-out do the completion
|
|
950
|
+
else
|
|
951
|
+
completed(false, output)
|
|
952
|
+
})
|
|
953
|
+
linerErr.on("readable", function (local line) {
|
|
954
|
+
if options.errproc
|
|
955
|
+
call(options.errproc, output, linerErr, kill)
|
|
956
|
+
else
|
|
957
|
+
while ((line = linerErr.read()) && line.length > 0) {
|
|
958
|
+
if !output.stderr
|
|
959
|
+
output.stderr = [] // only create this if we have real output
|
|
960
|
+
output.stderr.push(line)
|
|
961
|
+
}
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
//
|
|
965
|
+
// process stdout output
|
|
966
|
+
//
|
|
967
|
+
linerOut = NodeStreamLiner(options.outLE)
|
|
968
|
+
spawn.stdout.pipe(linerOut)
|
|
969
|
+
linerOut.on("end", function() {
|
|
970
|
+
if options.outproc
|
|
971
|
+
call(options.outproc)
|
|
972
|
+
if linerErr
|
|
973
|
+
linerOut = false // let end-of-err do the completion
|
|
974
|
+
else
|
|
975
|
+
completed(false, output)
|
|
976
|
+
})
|
|
977
|
+
linerOut.on("readable", function (local line) {
|
|
978
|
+
if options.outproc
|
|
979
|
+
call(options.outproc, output, linerOut, kill)
|
|
980
|
+
else {
|
|
981
|
+
if !output.stdout
|
|
982
|
+
output.stdout = []
|
|
983
|
+
while (line = linerOut.read())
|
|
984
|
+
output.stdout.push(line)
|
|
985
|
+
}
|
|
986
|
+
})
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// finis
|
|
990
|
+
|
|
991
|
+
list(false, files)
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
|
|
995
|
+
/*
|
|
996
|
+
* filesystemInit
|
|
997
|
+
*
|
|
998
|
+
* Initialize the module.
|
|
999
|
+
*
|
|
1000
|
+
*/
|
|
1001
|
+
|
|
1002
|
+
function filesystemInit(local manifest) {
|
|
1003
|
+
|
|
1004
|
+
manifest = `(Filesystem, filesystemInit)
|
|
1005
|
+
|
|
1006
|
+
Naan.module.build(module.id, "filesystem", function(modobj, compobj) {
|
|
1007
|
+
Naan.module.require("./node.nlg")
|
|
1008
|
+
compobj.manifest = manifest
|
|
1009
|
+
modobj.exports.Filesystem = Filesystem
|
|
1010
|
+
})
|
|
1011
|
+
|
|
1012
|
+
}();
|