@naanlang/naan 1.0.3 → 1.0.6

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.
Files changed (44) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +8 -6
  3. package/bin/index.js +2 -2
  4. package/dist/env_web.js +30 -6
  5. package/dist/naan.min.js +4 -4
  6. package/frameworks/browser/https_request.nlg +120 -0
  7. package/frameworks/browser/sworker.js +29 -39
  8. package/frameworks/browser/terminals.nlg +6 -4
  9. package/frameworks/browser/workers.nlg +13 -6
  10. package/frameworks/client/apiclient.nlg +1 -1
  11. package/frameworks/client/psm_client.nlg +80 -32
  12. package/frameworks/common/common.nlg +104 -26
  13. package/frameworks/common/preferences.nlg +1 -0
  14. package/frameworks/common/watching.nlg +1 -0
  15. package/frameworks/node/apiserver.nlg +9 -5
  16. package/frameworks/node/filesystem.nlg +89 -31
  17. package/frameworks/node/gitter.nlg +27 -6
  18. package/frameworks/node/https_request.nlg +119 -0
  19. package/frameworks/node/node.nlg +1 -0
  20. package/frameworks/node/psm_server.nlg +29 -7
  21. package/frameworks/project/build.nlg +13 -7
  22. package/frameworks/project/proj_cliser.nlg +11 -3
  23. package/frameworks/project/proj_console.nlg +13 -4
  24. package/frameworks/project/proj_folder.nlg +10 -1
  25. package/frameworks/project/proj_lambda.nlg +13 -4
  26. package/frameworks/project/proj_static.nlg +11 -3
  27. package/frameworks/project/projects.nlg +102 -20
  28. package/lib/browser/env_web.js +32 -8
  29. package/lib/core/naanlib.js +4 -4
  30. package/package.json +2 -1
  31. package/plugins/serviceAws/aws/aws-sdk-node.min.js +2 -0
  32. package/plugins/serviceAws/aws/aws-sdk.min.js +2 -88
  33. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +7 -6
  34. package/plugins/serviceAws/aws_dynamo.nlg +128 -45
  35. package/plugins/serviceAws/aws_lambda.nlg +11 -4
  36. package/plugins/serviceAws/aws_s3.nlg +44 -14
  37. package/plugins/serviceAws/dbt_aws.nlg +29 -36
  38. package/plugins/serviceAws/psm_aws.nlg +21 -16
  39. package/plugins/serviceAws/serviceAws.nlg +6 -4
  40. package/test/harness.nlg +3 -1
  41. package/test/test_01_core.nlg +5 -5
  42. package/test/test_02_context.nlg +4 -4
  43. package/test/test_03_jsinterop.nlg +9 -3
  44. package/plugins/serviceAws/aws_cognito.nlg +0 -76
@@ -30,6 +30,49 @@ function EncodeQuery(prefix, items, local result, item) {
30
30
  };
31
31
 
32
32
 
33
+ /*
34
+ * ContentTypeFromFileExt
35
+ *
36
+ * Given a filename with extension, return a tuple of the recommended contentType and encoding
37
+ * for that type of data: `(contentType, encoding). Currently the encoding is false for text and
38
+ * "binary" for everything else.
39
+ *
40
+ */
41
+
42
+ function ContentTypeFromFileExt(filepath, local mimeType, encoding) {
43
+ mimeType = {
44
+ "css": "text/css",
45
+ "csv": "text/csv",
46
+ "gif": "image/gif",
47
+ "htm": "text/html",
48
+ "html": "text/html",
49
+ "ico": "image/x-icon",
50
+ "ics": "application/octet-stream",
51
+ "jpeg": "image/jpeg",
52
+ "jpg": "image/jpeg",
53
+ "js": "text/javascript",
54
+ "json": "application/json",
55
+ "mov": "video/mp4",
56
+ "mp3": "audio/mpeg",
57
+ "mp4": "video/mp4",
58
+ "nlg": "text/plain",
59
+ "npk": "application/json",
60
+ "ogg": "video/ogg",
61
+ "svg": "image/svg+xml",
62
+ "text": "text/plain",
63
+ "txt": "text/plain",
64
+ "zip": "application/octet-stream"
65
+ }[JSpath.extname(filepath).substring(1)]
66
+ if !mimeType
67
+ mimeType = "application/octet-stream"
68
+ if mimeType.startsWith("text/")
69
+ mimeType = mimeType.concat("; charset=UTF-8")
70
+ else
71
+ encoding = "binary"
72
+ list(mimeType, encoding)
73
+ };
74
+
75
+
33
76
  /*
34
77
  * UUID
35
78
  *
@@ -66,7 +109,7 @@ function JsonParse(data) {
66
109
  list(false, data)
67
110
  } catch {
68
111
  if true
69
- list(exception)
112
+ list(exception)
70
113
  }
71
114
  };
72
115
 
@@ -84,7 +127,7 @@ function JsonStringify(data) {
84
127
  list(false, data)
85
128
  } catch {
86
129
  if true
87
- list(exception)
130
+ list(exception)
88
131
  }
89
132
  };
90
133
 
@@ -125,11 +168,8 @@ function JsLoadScript(libpath, globalID, local scriptTag, fpath) {
125
168
  }
126
169
  js.w.document.body.appendChild(scriptTag)
127
170
  pending.wait()
128
- } else if js.g {
129
- if !libpath.startsWith("/")
130
- fpath = "./".concat(libpath)
131
- jsScripts[libpath] = js.r(fpath) // load in NodeJS context
132
- }
171
+ } else if js.g
172
+ jsScripts[libpath] = js.r(libpath) // load in NodeJS context
133
173
  jsScripts[libpath]
134
174
  };
135
175
 
@@ -260,13 +300,15 @@ EncodeBase64 = false;
260
300
  DecodeBase64 = false;
261
301
  JSONstringify = false;
262
302
  JSONparse = false;
303
+ Uint8ArrayFromString = false;
263
304
 
264
305
  function comrInit(local manifest) {
265
- manifest = `(EncodeQuery, UUID, JsLoadScript, LoadComponentOnDemand, LiveExport, comrInit)
306
+ manifest = `(EncodeQuery, ContentTypeFromFileExt, UUID, JsLoadScript, LoadComponentOnDemand, LiveExport, comrInit)
266
307
 
267
- Naan.module.build(module.id, "nideCommon", closure(modobj, compobj) {
308
+ Naan.module.build(module.id, "common", closure(modobj, compobj) {
268
309
  compobj.manifest = manifest
269
310
  modobj.exports.EncodeQuery = EncodeQuery
311
+ modobj.exports.ContentTypeFromFileExt = ContentTypeFromFileExt
270
312
  modobj.exports.UUID = UUID
271
313
  modobj.exports.JsLoadScript = JsLoadScript
272
314
  modobj.exports.LoadComponentOnDemand = LoadComponentOnDemand
@@ -291,14 +333,18 @@ function comrInit(local manifest) {
291
333
  EncodeURIComponent = wsg.encodeURIComponent
292
334
  DecodeURIComponent = wsg.decodeURIComponent
293
335
  //
294
- // everywhere: Base65
295
- EncodeBase64 = wsg.btoa
296
- DecodeBase64 = wsg.atob
297
- //
298
336
  // everywhere: JSON
299
337
  JSONstringify = wsg.JSON.stringify
300
338
  JSONparse = wsg.JSON.parse
301
339
  //
340
+ // everywhere: Uint8ArrayFromString
341
+ //
342
+ Uint8ArrayFromString = function uint8ArrayFromString(data) {
343
+ wsg.Uint8Array.from(Array.from(data).map(function(letter) {
344
+ letter.charCodeAt(0)
345
+ }))
346
+ }
347
+ //
302
348
  // binaryFrom - convert arbitrary item to binary buffer or text.
303
349
  //
304
350
  function binaryFrom(data) {
@@ -327,22 +373,17 @@ function comrInit(local manifest) {
327
373
  result
328
374
  }
329
375
  //
330
- // base64Format - create base 64 string from binary array
376
+ // EncodeBase64url - decode base64url to original string
377
+ // See https://en.wikipedia.org/wiki/Base64 for details on variants
331
378
  //
332
- function base64Format(data, local blob, reader, pending, result) {
333
- blob = xnew(wsg.Blob, [data])
334
- reader = xnew(wsg.FileReader)
335
- pending = new(nonce)
336
- reader.onload = function(event) { pending.signal(event.target.result) }
337
- reader.readAsDataURL(blob)
338
- result = pending.wait()
339
- result.slice(37)
379
+ EncodeBase64url = function encodeBase64url(data) {
380
+ data = EncodeBase64(data)
381
+ data.replace(RegExp("[+]", "g"), '-').replace(RegExp("/", "g"), '_')
340
382
  }
341
383
  //
342
384
  // DecodeBase64url - decode base64url to original string
343
- // See https://en.wikipedia.org/wiki/Base64 for details on variants
344
385
  //
345
- DecodeBase64url = function decodeBase64url(base64url, local padding) {
386
+ DecodeBase64url = function decodeBase64url(base64url) {
346
387
  base64url = base64url.replace(RegExp("-", "g"), '+').replace(RegExp("_", "g"), '/')
347
388
  DecodeBase64(base64url)
348
389
  }
@@ -350,6 +391,31 @@ function comrInit(local manifest) {
350
391
  // For browser windows and web workers
351
392
  //
352
393
  if js.w || js.s {
394
+ //
395
+ // base64Format - create base 64 string from binary array
396
+ //
397
+ function browserBase64Format(data, local blob, reader, pending, result) {
398
+ blob = xnew(wsg.Blob, [data])
399
+ reader = xnew(wsg.FileReader)
400
+ pending = new(nonce)
401
+ reader.onload = function(event) { pending.signal(event.target.result) }
402
+ reader.readAsDataURL(blob)
403
+ result = pending.wait()
404
+ result.slice(37)
405
+ }
406
+ //
407
+ // binary to Base64
408
+ EncodeBase64 = function browserEncodeBase64(data) {
409
+ if xobject(data)
410
+ browserBase64Format(data)
411
+ else
412
+ wsg.btoa(data)
413
+ }
414
+ //
415
+ // Base64 to binary
416
+ DecodeBase64 = function browserDecodeBase64(data) {
417
+ wsg.atob(data)
418
+ }
353
419
  //
354
420
  // MD5 functions
355
421
  JsLoadScript("frameworks/browser/spark-md5/spark-md5.min.js", "SparkMD5")
@@ -373,7 +439,7 @@ function comrInit(local manifest) {
373
439
  throw(Error("crypto functions unavailable"))
374
440
  data = binaryFrom(data)
375
441
  if string(data)
376
- data = xnew(TextEncoder).encode(data)
442
+ data = Uint8ArrayFromString(data)
377
443
  `(error, result) = await(wsg.crypto.subtle.digest(algo, data))
378
444
  if error
379
445
  throw(Error("crypto digest failed", error))
@@ -392,7 +458,7 @@ function comrInit(local manifest) {
392
458
  //
393
459
  // HashSHA-256 in base64
394
460
  HashSHA256_base64 = function browserSHA256_base64(data) {
395
- base64Format(browserDigest(data, "SHA-256"))
461
+ browserBase64Format(browserDigest(data, "SHA-256"))
396
462
  }
397
463
  //
398
464
  // random bytes as UInt8Array - use toint() for integer
@@ -405,6 +471,16 @@ function comrInit(local manifest) {
405
471
  // For NodeJS
406
472
  //
407
473
  if js.g {
474
+ //
475
+ // binary to Base64
476
+ EncodeBase64 = function nodejsEncodeBase64(data) {
477
+ wsg.Buffer.from(data, "binary").toString("base64")
478
+ }
479
+ //
480
+ // Base64 to binary
481
+ DecodeBase64 = function nodejsDecodeBase64(data) {
482
+ wsg.Buffer.from(data, "base64").toString("binary")
483
+ }
408
484
  if !crypto
409
485
  crypto = js.r("crypto")
410
486
  if crypto {
@@ -456,6 +532,8 @@ function comrInit(local manifest) {
456
532
  modobj.exports.JSONparse = JSONparse
457
533
  modobj.exports.JsonStringify = JsonStringify
458
534
  modobj.exports.JsonParse = JsonParse
535
+ modobj.exports.Uint8ArrayFromString = Uint8ArrayFromString
536
+ modobj.exports.EncodeBase64url = EncodeBase64url
459
537
  modobj.exports.DecodeBase64url = DecodeBase64url
460
538
  modobj.exports.HashMD5 = HashMD5
461
539
  modobj.exports.HashMD5_base64 = HashMD5_base64
@@ -125,6 +125,7 @@ function prefsInit(local manifest) {
125
125
  Naan.module.build(module.id, "preferences", function(modobj, compobj) {
126
126
  require("../common/common.nlg")
127
127
  compobj.manifest = manifest
128
+ updateExports()
128
129
  })
129
130
 
130
131
  module.exports.Preferences = Preferences
@@ -93,5 +93,6 @@ function wachInit(local manifest) {
93
93
  require("./common.nlg")
94
94
  compobj.manifest = manifest
95
95
  module.exports.Watchable = Watchable
96
+ updateExports()
96
97
  })
97
98
  } ();
@@ -110,8 +110,12 @@ closure MakeAPI(port, options, callback, local chroot, server) {
110
110
 
111
111
  server.app.use(function(req,res,next) {
112
112
  res.header("Access-Control-Allow-Origin", "*")
113
- res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
114
- next()
113
+ res.header("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS")
114
+ res.header("Access-Control-Allow-Headers", "x-naanlang-api-guid")
115
+ if req.method == "OPTIONS"
116
+ res.sendStatus(200)
117
+ else
118
+ next()
115
119
  })
116
120
 
117
121
  server.app.use("", express.static(chroot))
@@ -167,8 +171,8 @@ closure MakeAPI(port, options, callback, local chroot, server) {
167
171
  res.status(403).send("unauthorized")
168
172
  timeoutms = toint(req.query.timeout)
169
173
  after = toint(req.query.after)
170
- if not integer(timeoutms) or timeoutms > 10000
171
- res.status(400).send("timeout is integer milliseconds no greater than 10000")
174
+ if not integer(timeoutms) or timeoutms > 7200000
175
+ res.status(400).send("timeout is integer milliseconds no greater than 7200000")
172
176
  else if not integer(after)
173
177
  res.status(400).send("after must be an integer")
174
178
  else {
@@ -197,7 +201,7 @@ closure MakeAPI(port, options, callback, local chroot, server) {
197
201
 
198
202
  server.app.all("/psm", closure(req, res, local guid) {
199
203
  guid = req.get("x-naanlang-api-guid")
200
- if server.guid && guid != server.guid
204
+ if server.guid && guid != server.guid && req.method !== "OPTIONS"
201
205
  res.status(403).send("unauthorized")
202
206
  else if !server.hostfs
203
207
  res.status(503).send("host filesystem not available")
@@ -23,7 +23,7 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
23
23
  if !rootpath
24
24
  return (list(Error("mandatory rootpath missing")))
25
25
  else if rootpath == ""
26
- rootpath = js.g.process.cwd() // NodeJS uses CWD for empty rootpath
26
+ rootpath = JSpath.sep
27
27
  files = new(object, this)
28
28
  files.rootpath = rootpath
29
29
  files.path = JSpath
@@ -54,24 +54,31 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
54
54
  // Convert a path argument to a local path, returning a standard result tuple. If the path is
55
55
  // invalid this will report why. Otherwise this returns the correct localpath, which is the path
56
56
  // we use with the underlying API.
57
+ // Win32 driver letters are annoying here. If the rootpath specifies a drive letter then the
58
+ // paths must match it. If the rootpath doesn't specify a drive letter then path can have any
59
+ // drive letter, or none.
57
60
 
58
61
  badCreatePathRegEx = RegExp("[:]|[/\\\\][/\\\\]|[/\\\\]$|(^|[/\\\\])[.][.]?$")
59
62
 
60
- closure localPath(path, create, local pattern, testpath) {
63
+ files.localPath = closure localPath(path, create, local pattern, testpath, locroot) {
61
64
  if !path
62
65
  return (list(Error("filesystem path false")))
63
66
  else if !string(path)
64
67
  return (list(Error("filesystem path invalid:", typeof(path))))
68
+ if JSpath === JSpath.win32 {
69
+ locroot = JSpath.resolve(path, rootpath) // use path's drive letter if we don't have one
70
+ pattern = "\\:\\" // unchanged and unique on win32
71
+ }
72
+ else {
73
+ locroot = rootpath
74
+ pattern = "/:/" // unchanged and unique on posix
75
+ }
65
76
  if JSpath.isAbsolute(path)
66
- path = JSpath.resolve(path) // puts C: in front of \abs\win\path
77
+ path = JSpath.resolve(locroot, path) // add drive letter to absolute path
67
78
  else
68
79
  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
80
+ if (commonParents(path, locroot) != "")
81
+ list(false, JSpath.resolve(locroot, path))
75
82
  else if ((testpath = JSpath.resolve(pattern, path)).indexOf(pattern.slice(0,-1)) < 0)
76
83
  list(Error("filesystem path reaches above root:", path))
77
84
  else if create && testpath.indexOf(pattern) < 0
@@ -79,7 +86,31 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
79
86
  else if create && badCreatePathRegEx.test(path)
80
87
  list(Error("filesystem create path invalid", path))
81
88
  else
82
- list(false, JSpath.resolve(rootpath, path))
89
+ list(false, JSpath.resolve(locroot, path))
90
+ }
91
+
92
+ // folderPath
93
+ //
94
+ // Return a folder path for the specified folderID, relative to our root. If the folder path is
95
+ // not within our root then it is returned as an absolute path.
96
+
97
+ files.folderPath = function folderPath(folderID, callback, local envID, path) {
98
+ if !callback
99
+ return (syncAdapter(folderPath, folderID))
100
+ envID = {
101
+ "HomeDir": "HOME",
102
+ "TempDir": "TMPDIR",
103
+ "PackageDir": "npm_config_prefix"
104
+ }[folderID]
105
+ if envID {
106
+ path = js.g.process.env[envID]
107
+ if path.startsWith(rootpath)
108
+ path = path.slice(rootpath.length) // remove rootpath from beginning
109
+ }
110
+ if path
111
+ callback(false, path)
112
+ else
113
+ callback(Error("unknown folderID:", folderID))
83
114
  }
84
115
 
85
116
  // winDrives
@@ -150,11 +181,11 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
150
181
  // readLines
151
182
  //
152
183
  // 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
184
+ // pattern: -- only return lines matching pattern
185
+ // fixed -- pattern is a fixed string, not a regular expression
186
+ // ignoreCase: -- ignore case during match
187
+ // invert: -- return lines that DO NOT match the regex
188
+ // returnText: -- return text of matched lines
158
189
  //
159
190
 
160
191
  files.readLines = closure readLines(path, options, callback,
@@ -639,8 +670,10 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
639
670
  // Recursively build the tree
640
671
 
641
672
  closure recurTree(localpath, depth, parent) {
642
- if depthlimit && depth >= depthlimit
673
+ if depthlimit && depth >= depthlimit {
674
+ recurNext()
643
675
  return
676
+ }
644
677
  ++pending.active
645
678
  fs.readdir(localpath, { }, function(err, nodes, local child, node, stat) {
646
679
  if err.code == "ENOTDIR"
@@ -670,13 +703,21 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
670
703
  parent: child
671
704
  }) } }
672
705
  --pending.active
673
- while queue.length > 0 && pending.active < 20 {
674
- node = queue.pop()
675
- recurTree(node.path, node.depth, node.parent) }
676
- if pending.active == 0 && queue.length == 0
677
- pending.signal(list(false, root))
706
+ recurNext()
678
707
  })
679
708
  }
709
+
710
+ // recurNext
711
+ //
712
+ // Pull more queued items and signal when done.
713
+
714
+ function recurNext(local node) {
715
+ while queue.length > 0 && pending.active < 20 {
716
+ node = queue.pop()
717
+ recurTree(node.path, node.depth, node.parent) }
718
+ if pending.active == 0 && queue.length == 0
719
+ pending.signal(list(false, root))
720
+ }
680
721
 
681
722
  `(error, localpath) = localPath(path)
682
723
  if error
@@ -707,21 +748,31 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
707
748
  // dirSearch
708
749
  //
709
750
  // Search the files in a directory for a pattern.
710
-
751
+ //
752
+ // Most options are handled by readLines, but the following is done here:
753
+ // excludeDirs: [dirnames] -- exclude the specified directory names
754
+
711
755
  files.dirSearch = closure dirSearch(path, options, callback,
712
- local error, localpaths, output) {
756
+ local error, localpaths, dexmatch, item, output) {
713
757
  if !callback
714
758
  return (syncAdapter(dirSearch, path, options))
715
759
  `(error, localpath) = localPath(path)
716
760
  if error
717
761
  return (asyncResult(callback, error))
762
+ if options.excludeDirs.0 { // match all provided paths
763
+ dexmatch = []
764
+ for item in options.excludeDirs // escape all special characters
765
+ dexmatch.push(item.replace(RegExp("[^A-Za-z0-9-_\n]","g"), "\\$&"))
766
+ dexmatch = RegExp(dexmatch.join("|"))
767
+ }
718
768
  output = []
719
769
  debuglog("dirSearch begins", localpath)
720
770
  asyncArray(localpaths, 10, closure(localpath, index, elements, local error, direnum, node, lines, line) {
721
771
  `(error, direnum) = dirList(localpath, { })
722
772
  if direnum.children
723
773
  for node in direnum.children
724
- localpaths.push(JSpath.join(localpath, node.name)) // more files or directories to examine
774
+ if !dexmatch || !node.name.match(dexmatch)
775
+ localpaths.push(JSpath.join(localpath, node.name)) // more files or directories to examine
725
776
  else {
726
777
  `(error, lines) = readLines(localpath, options)
727
778
  if error && localpaths.length == 1 {
@@ -945,22 +996,29 @@ closure Filesystem(rootpath, local files, badCreatePathRegEx) {
945
996
  //
946
997
  // Return an array of text matches in a file or recursive directory of files with the following
947
998
  // options:
948
- // pattern: -- pattern to find
949
- // ignoreCase: -- ignore case during match
950
- // invert: -- return lines that DO NOT match the regex
951
- // returnText: -- return text of matched lines
952
- // maxCount: -- integer maximum number of results (default 10,000)
999
+ // pattern: -- pattern to find
1000
+ // ignoreCase: -- ignore case during match
1001
+ // invert: -- return lines that DO NOT match the regex
1002
+ // returnText: -- return text of matched lines
1003
+ // excludeDirs: [dirnames] -- exclude the specified directory names
1004
+ // maxCount: -- integer maximum number of results (default 10,000)
953
1005
  // grep-specific options:
954
- // ignoreBinary: -- ignore binary files
1006
+ // ignoreBinary: -- ignore binary files
955
1007
  //
956
1008
  files.grepLines = closure grepLines(path, options, callback,
957
- local error, localpath, cmdargs, execOptions, grep, result, lineRE, maxcount) {
1009
+ local error, localpath, cmdargs, item, execOptions, grep, result, lineRE, maxcount) {
958
1010
  if !callback
959
1011
  return (syncAdapter(grepLines, path, options))
960
1012
  `(error, localpath) = localPath(path)
961
1013
  if error
962
1014
  return (asyncResult(callback, error))
963
1015
  cmdargs = ["-rn"] // recursively search directories for files; report line numbers
1016
+ if options.excludeDirs.0 { // exclude a directory
1017
+ for item in options.excludeDirs {
1018
+ cmdargs.push("--exclude-dir")
1019
+ cmdargs.push(item)
1020
+ }
1021
+ }
964
1022
  if options.ignoreCase
965
1023
  cmdargs.push("-i")
966
1024
  if options.invert
@@ -23,6 +23,28 @@ closure Gitter(fs, local gitt) {
23
23
  gitt = new(object, this)
24
24
  gitt.roots = {}
25
25
 
26
+ // gitPath
27
+ //
28
+ // Convert a Windows path to a posix (git) path if needed.
29
+ //
30
+ function gitPath(fspath) {
31
+ if fs.path.sep == "/"
32
+ fspath
33
+ else
34
+ fspath.split(fs.path.sep).join("/")
35
+ }
36
+
37
+ // fsPath
38
+ //
39
+ // Convert a posix (git) path to a filesystem path if needed.
40
+ //
41
+ function fsPath(gitpath) {
42
+ if fs.path.sep == "/"
43
+ gitpath
44
+ else
45
+ gitpath.split("/").join(fs.path.sep)
46
+ }
47
+
26
48
  // repoRoot
27
49
  //
28
50
  // Compute the path from the fs root to the repo root.
@@ -54,11 +76,10 @@ closure Gitter(fs, local gitt) {
54
76
  // Compute the bridge path to an absolute repopath (which can point inside a repo) from the
55
77
  // phyiscal repo root. If these are the same, or an error occurs, then the result is "".
56
78
  //
57
-
58
79
  function bridgePath(repopath, local top) {
59
80
  top = repoRoot(repopath).1
60
81
  if top && repopath.startsWith(top) && repopath.length > top.length
61
- top = repopath.slice(top.length+1).concat("/") // move leading "/" of bridge path to trailing
82
+ top = repopath.slice(top.length+1).concat(fs.path.sep) // move leading "/" of bridge path to trailing
62
83
  else
63
84
  "" // we are looking at the root, or an error
64
85
  }
@@ -151,12 +172,12 @@ closure Gitter(fs, local gitt) {
151
172
  outLE: "\x00" // lines delimited by nulls
152
173
  }
153
174
  if options.pathspec
154
- execOptions.cmdargs = execOptions.cmdargs.concat("--", options.pathspec)
175
+ execOptions.cmdargs = execOptions.cmdargs.concat("--", gitPath(options.pathspec))
155
176
  result = []
156
177
  execOptions.outproc = function(output, liner, kill, local line, path) {
157
178
  while (line = liner.read())
158
179
  if line != "" {
159
- path = line.substring(3)
180
+ path = fsPath(line.substring(3))
160
181
  if path.startsWith(bridge) { // must start with bridge to be within repopath
161
182
  path = path.slice(bridge.length)
162
183
  result.push({
@@ -183,7 +204,7 @@ closure Gitter(fs, local gitt) {
183
204
  // Retrieve the content of a specific file and revision. The file pathspec must be the path of
184
205
  // the file within the repo.
185
206
 
186
- gitt.showfile = closure showfile(repopath, options, callback, local execOptions, result) {
207
+ gitt.showfile = closure showfile(repopath, options, callback, local execOptions) {
187
208
  if !callback
188
209
  return (syncAdapter(showfile, repopath, options))
189
210
  if !string(repopath)
@@ -192,7 +213,7 @@ closure Gitter(fs, local gitt) {
192
213
  return (asyncResult(callback, Error("both pathspec and hash required:", options.pathspec, options.hash)))
193
214
  repopath = fs.path.resolve(fs.rootpath, repopath)
194
215
  execOptions = {
195
- cmdargs: ["show", options.hash.concat(":", bridgePath(repopath), options.pathspec)]
216
+ cmdargs: ["show", options.hash.concat(":", bridgePath(repopath), gitPath(options.pathspec))]
196
217
  execops: {
197
218
  cwd: repopath
198
219
  }