@naanlang/naan 1.0.7 → 1.0.8

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 (36) hide show
  1. package/LICENSE.md +3 -4
  2. package/README.md +20 -2
  3. package/bin/index.js +2 -2
  4. package/dist/env_web.js +86 -17
  5. package/dist/naan.min.js +7 -7
  6. package/frameworks/browser/browser.nlg +26 -2
  7. package/frameworks/browser/sworker.js +17 -17
  8. package/frameworks/browser/workers.nlg +7 -0
  9. package/frameworks/client/psm_client.nlg +1 -0
  10. package/frameworks/common/common.nlg +33 -5
  11. package/frameworks/node/filesystem.nlg +32 -26
  12. package/frameworks/node/gitter.nlg +9 -4
  13. package/frameworks/node/https_request.nlg +19 -14
  14. package/frameworks/project/build.nlg +177 -68
  15. package/frameworks/project/projects.nlg +1 -1
  16. package/frameworks/running/running.nlg +32 -10
  17. package/frameworks/storage/psm.nlg +20 -4
  18. package/lib/browser/env_web.js +90 -21
  19. package/lib/core/naanlib.js +7 -7
  20. package/package.json +1 -1
  21. package/plugins/serviceAws/aws/aws-sdk-node.min.js +1 -1
  22. package/plugins/serviceAws/aws/aws-sdk.min.js +1 -1
  23. package/plugins/serviceAws/aws/lambda_ws_init.nlg +2 -1
  24. package/plugins/serviceAws/aws_dynamo.nlg +27 -20
  25. package/plugins/serviceAws/aws_dynextra.nlg +110 -5
  26. package/plugins/serviceAws/aws_lambda.nlg +4 -8
  27. package/plugins/serviceAws/aws_sqs.nlg +113 -0
  28. package/plugins/serviceAws/serviceAws.nlg +1 -1
  29. package/plugins/serviceYC/generic_api.yaml +36 -0
  30. package/plugins/serviceYC/naanide-relay-index.js +148 -0
  31. package/plugins/serviceYC/serviceYC.nlg +59 -0
  32. package/plugins/serviceYC/yc_function.nlg +161 -0
  33. package/test/test_01_core.nlg +2 -2
  34. package/test/test_02_context.nlg +2 -2
  35. package/test/test_05_strings.nlg +11 -1
  36. package/test/test_06_lingoparse.nlg +43 -14
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2019-2022 by Richard C. Zulch
9
+ * Copyright (c) 2019-2023 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -47,6 +47,20 @@
47
47
  * srcpath - the source path in common to all sources
48
48
  * destpath - the destination path in common to all outputs
49
49
  *
50
+ * Special paths:
51
+ * srcpath - the Build object looks for all sources within the srcpath specified when it's
52
+ * instantiated. For example, the files in a group are located in the join of the
53
+ * paths: <srcpath> / <group.input> / <srcname.xx>.
54
+ * destpath - the Build object writes all output files within the destpath specified when
55
+ * it's instantiated. For example, the files output from a group are located in
56
+ * the join of the paths: <destpath> / <group.output> / <srcname.xx>. Note that
57
+ * group.input is used to locate the files, but is not part of the output.
58
+ * @ - the @ character stands in for a persistent, temporary directory within the
59
+ * build folder that replaces srcpath or destpath. It can be used in fields that
60
+ * are otherwise absolute: group.input, group.output, and template paths. Build
61
+ * detects piplines formed from temporary files and processes them in order from
62
+ * beginning to end.
63
+ *
50
64
  * Templates:
51
65
  * A dictionary where each key is a destfile as described above, and the data for the key
52
66
  * is the path to a template file relative to srcpath. When a template file exists for multiple-
@@ -76,6 +90,7 @@
76
90
  * licfile: <license file, path relative to srcpath>
77
91
  * verfile: <version file, path relative to srcpath>
78
92
  * buildno: <build number file, path relative to srcpath>
93
+ * xvrfile: <external version file, path relative to srcpath>
79
94
  * run: {
80
95
  * "open": "Nide_LaunchMac.tool",
81
96
  * "args": []
@@ -129,6 +144,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
129
144
  build = new(object, this)
130
145
  build.srcpath = srcpath
131
146
  build.destpath = destpath
147
+ build.intpath = JSpath.join(destpath, "tmp")
132
148
  build.tasks = { // all tasks counter
133
149
  copy: 0,
134
150
  version: 0,
@@ -152,11 +168,16 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
152
168
  build.verfilepath = JSpath.join(build.srcpath, rules.verfile)
153
169
  if rules.buildno
154
170
  build.buildnopath = JSpath.join(build.srcpath, rules.buildno)
171
+ if rules.xvrfile
172
+ build.xvrfilepath = JSpath.join(build.srcpath, rules.xvrfile)
155
173
  if rules.templates { // templates for catenates
156
174
  let (tpath, writepath) {
157
175
  build.tepaths = { } // template for each catenate output
158
176
  for tpath in rules.templates {
159
- writepath = JSpath.join(build.destpath, tpath)
177
+ if tpath.startsWith("@")
178
+ writepath = JSpath.join(build.intpath, tpath.substring(1))
179
+ else
180
+ writepath = JSpath.join(build.destpath, tpath)
160
181
  build.tepaths[writepath] = JSpath.join(build.srcpath, rules.templates[tpath])
161
182
  }
162
183
  build.testats = { }
@@ -616,7 +637,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
616
637
  }
617
638
 
618
639
  groups = new(build.groups) // copy so we can append more groups as needed
619
- sourcemax = groups.reduce(function(acc, group) { Math.max(acc, group.sources.length) }, 0)
640
+ sourcemax = 10000
620
641
  catenates = { } // catenate groups
621
642
  pending = new(nonce)
622
643
  pending.active = 0
@@ -635,11 +656,18 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
635
656
  groupVerStat, source, sdex) {
636
657
  ++pending.active
637
658
  future(function test1() {
638
- if group.input
659
+ if group.input.startsWith("@") { // intermediate directory
660
+ group.intSource = true
661
+ indirpath = JSpath.join(build.intpath, group.input.substring(1))
662
+ }
663
+ else if group.input
639
664
  indirpath = JSpath.join(build.srcpath, group.input)
640
665
  else
641
666
  indirpath = build.srcpath
642
- outdirpath = JSpath.join(build.destpath, group.output)
667
+ if group.output.startsWith("@")
668
+ outdirpath = JSpath.join(build.intpath, group.output.substring(1))
669
+ else
670
+ outdirpath = JSpath.join(build.destpath, group.output)
643
671
  destcheck = new(group.destcheck).reverse() // get next destination to check
644
672
  groupVerStat = verfileStat
645
673
  if group.depend_buildno {
@@ -655,11 +683,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
655
683
  else
656
684
  outfile = JSpath.join(outdirpath, source)
657
685
  fs.info(infile, false, function(error, instat, local listing, addgroup, file) {
658
- if error
659
- build.errorlist.push(error)
660
- else if instat.type != "directory"
661
- addEntry(infile, outfile, instat, sdex) // add execution entry if needed
662
- else { // add a new group for directory source
686
+ if !error && instat.type == "directory" {
663
687
  `(error, listing) = fs.dirList(infile, { stat: true })
664
688
  if error
665
689
  build.errorlist.push(error)
@@ -681,6 +705,10 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
681
705
  groups.push(addgroup)
682
706
  }
683
707
  }
708
+ else if !error || group.intSource
709
+ addEntry(infile, outfile, instat, sdex) // add execution entry
710
+ else
711
+ build.errorlist.push(error)
684
712
  --pending.active
685
713
  doMore()
686
714
  })
@@ -692,11 +720,17 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
692
720
  // Add an entry to execution list if it's needed.
693
721
  //
694
722
  closure addEntry(infile, outfile, instat, sdex, local error, outstat, entry) {
723
+ if sdex >= sourcemax {
724
+ build.errorlist.push(Error("Too many sources in one group:", groupno.group))
725
+ return
726
+ }
695
727
  entry = {
696
728
  readpath: infile,
697
729
  writepath: outfile
698
730
  }
699
- if group.input // insource is the name within zip/cat
731
+ if group.input.startsWith("@")
732
+ entry.insource = JSpath.join(group.input.substring(1), group.sources[sdex])
733
+ else if group.input // insource is the name within zip/cat
700
734
  entry.insource = JSpath.join(group.input, group.sources[sdex])
701
735
  else
702
736
  entry.insource = group.sources[sdex]
@@ -771,23 +805,24 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
771
805
  ++pending.active
772
806
  if destcheck { // destcheck is special target for determining if task needed
773
807
  entry.destcheck = JSpath.join(outdirpath, destcheck.pop())
774
- fs.info(entry.destcheck, false, addIfOutOfDate)
808
+ fs.info(entry.destcheck, false, addOne)
775
809
  } else
776
- fs.info(outfile, false, addIfOutOfDate)
810
+ fs.info(outfile, false, addOne)
777
811
 
778
- function addIfOutOfDate(error, outstat, local entryop) {
812
+ function addOne(error, outstat, local entryop) {
779
813
  if error || toint(instat.mtimeMs) > toint(outstat.mtimeMs) {
814
+ entry.outOfDate = true
780
815
  catenates[entry.writepath] = true // this entire group needed
781
- build.filelist.push(entry) // destination is out of date
816
+ build.filelist.push(entry)
782
817
  entryop = "make:"
783
818
  } else if entry.catorder {
784
819
  build.filelist.push(entry) // needed if any in group out of date
785
- entryop = "maybe-needed:"
820
+ entryop = "cat-check:"
786
821
  }
787
822
  else
788
823
  entryop = "up-to-date:"
789
824
  if debugMake
790
- debuglog(entryop, entry.*, instat.mtimeMs, outstat.mtimeMs, entry.readpath, entry.writepath)
825
+ debuglog(entryop, entry.readpath, entry.*, instat.mtimeMs, outstat.mtimeMs, entry.writepath)
791
826
  --pending.active
792
827
  doMore()
793
828
  }
@@ -808,6 +843,32 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
808
843
  doMore()
809
844
  pending.wait()
810
845
 
846
+ //
847
+ // Cross-check inputs from intermediate files that will be built
848
+ //
849
+
850
+ let (outint, entry) {
851
+ outint = {}
852
+ for entry in build.filelist
853
+ if entry.outOfDate && entry.writepath.startsWith(build.intpath) {
854
+ outint[entry.writepath] = entry // at least one component out of date
855
+ break
856
+ }
857
+ for entry in build.filelist
858
+ if outint[entry.writepath].catorder > entry.catorder
859
+ outint[entry.writepath] = entry // find first component
860
+ for entry in build.filelist
861
+ if outint[entry.readpath] {
862
+ entry.outOfDate = true
863
+ catenates[entry.writepath] = true // this entire group needed
864
+ entry.depends = outint[entry.readpath]
865
+ if !entry.depends.done
866
+ entry.depends.done = new(nonce)
867
+ if debugMake
868
+ debuglog("add-make:", entry.readpath, entry.*, entry.writepath)
869
+ }
870
+ } ()
871
+
811
872
  //
812
873
  // Remove unneeded catenate entries and index the remainder
813
874
  //
@@ -820,11 +881,16 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
820
881
  else if catenates[entry.writepath] {
821
882
  if !renumber[entry.writepath]
822
883
  renumber[entry.writepath] = []
823
- renumber[entry.writepath].push(entry) // this catenate is needed
884
+ if renumber[entry.writepath].indexOf(entry) < 0
885
+ renumber[entry.writepath].push(entry) // this catenate is needed
886
+ entry.outOfDate = true // this entry is needed
824
887
  true
825
888
  } else
826
889
  false // this one is not
827
890
  })
891
+ build.filelist = build.filelist.filter(function(entry) {
892
+ entry.outOfDate
893
+ })
828
894
  for wpath in renumber { // assign entries in catorder
829
895
  renumber[wpath].sort(function(a,b) { a.catorder <=> b.catorder })
830
896
  catindex = 0
@@ -845,8 +911,8 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
845
911
  // checked and any that have all the parts are written out. Catenates that don't have all their
846
912
  // parts are reported as an error in addition to the error that caused them to be incomplete.
847
913
  //
848
- closure execute(local error, licFileDefs, buildno, verFileDefs, version, entries, pending,
849
- baddestpaths, catenates) {
914
+ closure execute(local error, licFileDefs, buildno, verFileDefs, xvrFileDefs,
915
+ entries, pending, baddestpaths, catenates) {
850
916
  if build.tasks.license > 0
851
917
  `(error, licFileDefs) = readSubstitutionFile(build.licfilepath)
852
918
  if build.tasks.version > 0 {
@@ -857,9 +923,15 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
857
923
  } else
858
924
  build.buildno = buildno = toint(buildno.trim())
859
925
  build.buildno = buildno
860
- `(error, verFileDefs) = readSubstitutionFile(build.verfilepath, {
861
- BuildNumber: buildno
862
- })
926
+ if build.xvrfilepath {
927
+ `(error, xvrFileDefs) = readSubstitutionFile(build.xvrfilepath)
928
+ if error
929
+ build.errorlist.push(error)
930
+ }
931
+ if !xvrFileDefs
932
+ xvrFileDefs = { }
933
+ xvrFileDefs.BuildNumber = buildno
934
+ `(error, verFileDefs) = readSubstitutionFile(build.verfilepath, xvrFileDefs)
863
935
  build.version = verFileDefs["Version"]
864
936
  verFileDefs["CacheBuster"] = HashMD5(build.version.concat(Math.random()))
865
937
  }
@@ -889,6 +961,8 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
889
961
  closure processOneFile(entry, local result, content, error) {
890
962
  ++pending.active
891
963
  future(function build1(local dirpath) {
964
+ if entry.depends
965
+ entry.depends.done.wait() // wait for our precursor
892
966
  dirpath = JSpath.dirname(entry.writepath)
893
967
  if baddestpaths[dirpath]
894
968
  { } // error already reported
@@ -904,7 +978,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
904
978
  erase: true // not really needed
905
979
  overwrite: true // overwrite files
906
980
  force: true // ignore modify date
907
- preserve: true // preserve attributes
981
+ idmode: true // preserve ids and mode
908
982
  })
909
983
  if error // deepcopy returns a list of errors
910
984
  error = error.0 // but we're only doing one item
@@ -936,10 +1010,20 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
936
1010
  if !catenates[entry.writepath]
937
1011
  catenates[entry.writepath] = new(Array(entry.catcount))
938
1012
  entry.content = content
939
- catenates[entry.writepath][entry.catindex] = entry }
940
- else
1013
+ catenates[entry.writepath][entry.catindex] = entry
1014
+ if catReady(entry.writepath) {
1015
+ `(error, result) = writeCatenate(entry.writepath)
1016
+ if error
1017
+ build.errorlist.push(error)
1018
+ catenates[entry.writepath].0.done.signal(error)
1019
+ }
1020
+ }
1021
+ else {
941
1022
  `(error, result) = fs.writeFile(entry.writepath, content)
942
- }
1023
+ entry.done.signal(error)
1024
+ }
1025
+ } else
1026
+ entry.done.signal(error)
943
1027
  }
944
1028
  if error
945
1029
  build.errorlist.push(error)
@@ -960,52 +1044,76 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
960
1044
  if pending.remaining > 0
961
1045
  pending.wait() // don't wait if nothing to do
962
1046
 
1047
+ // catReady
963
1048
  //
964
- // Write out any completed catenates
1049
+ // Return true iff the catenate has all its elements.
965
1050
  //
1051
+ function catReady(writepath, local entry) {
1052
+ for entry in catenates[writepath]
1053
+ if !entry.content
1054
+ return (false)
1055
+ true
1056
+ }
966
1057
 
967
- let (writepath, template, entry, content, catsub, dozip, docat, fsoptions, zipmap, error, result) {
968
- for :out1 writepath in catenates {
969
- template = build.templates[writepath] // optional in some cases ### should warn in others
970
- catsub = { }
971
- for entry in catenates[writepath] { // error check and substitution build
972
- if !entry.content {
973
- build.errorlist.push(Error("Catenate incomplete:", writepath))
974
- continue :out1 }
975
- catsub[entry.insource] = entry.content
976
- if entry.zip
977
- dozip = true
978
- if entry.catenate
979
- docat = true
1058
+ // writeCatenate
1059
+ //
1060
+ // Write out a catenate, returning a standard result tuple.
1061
+ //
1062
+ function writeCatenate(writepath,
1063
+ local template, entry, content, catsub, dozip, docat, fsoptions, zipmap, error, result) {
1064
+ template = build.templates[writepath] // optional in some cases ### should warn in others
1065
+ catsub = { }
1066
+ for entry in catenates[writepath] { // error check and substitution build
1067
+ if !entry.content
1068
+ return (list(Error("Incomplete zip/cat:", writepath)))
1069
+ catsub[entry.insource] = entry.content
1070
+ if entry.zip
1071
+ dozip = true
1072
+ if entry.catenate
1073
+ docat = true
1074
+ }
1075
+ if docat { // catenate the files
1076
+ if dozip
1077
+ return (list(Error("Cannot combine zip/cat into:", writepath)))
1078
+ if template {
1079
+ `(content, result) = applySubstitutions(template, catsub)
1080
+ if length(catsub) != result
1081
+ return (list(Error("Matched ", result, " of ", length(catsub),
1082
+ "substitutions in template:", build.tepaths[writepath])))
980
1083
  }
981
- if docat { // catenate the files
982
- if template {
983
- `(content, result) = applySubstitutions(template, catsub)
984
- if length(catsub) != result
985
- build.errorlist.push(Error("Matched ", result, " of ", length(catsub),
986
- "substitutions in template:", build.tepaths[writepath]))
987
- }
988
- else
989
- content = catenates[writepath].map(function(entry){entry.content}).join("\n")
990
- if dozip {
991
- zipmap = { }
992
- zipmap[JSpath.basename(writepath)] = content
993
- `(error, content) = zip(zipmap) // compress the catenated file
994
- }
995
- } else {
996
- if template
997
- zipmap = applyZipTemplate(template, catsub)
998
- else
999
- zipmap = trimZipPaths(catsub)
1000
- `(error, content) = zip(zipmap) // compress each of the files
1084
+ else
1085
+ content = catenates[writepath].map(function(entry){entry.content}).join("\n")
1086
+ if dozip {
1087
+ zipmap = { }
1088
+ zipmap[JSpath.basename(writepath)] = content
1089
+ `(error, content) = zip(zipmap) // compress the catenated file
1001
1090
  }
1002
- if dozip
1003
- fsoptions = { encoding: "base64" }
1004
- if !error
1005
- `(error, result) = fs.writeFile(writepath, content, fsoptions)
1006
- if error
1007
- build.errorlist.push(error)
1091
+ } else {
1092
+ if template
1093
+ zipmap = applyZipTemplate(template, catsub)
1094
+ else
1095
+ zipmap = trimZipPaths(catsub)
1096
+ `(error, content) = zip(zipmap) // compress each of the files
1008
1097
  }
1098
+ if dozip
1099
+ fsoptions = { encoding: "base64" }
1100
+ if !error
1101
+ `(error, result) = fs.writeFile(writepath, content, fsoptions)
1102
+ if error
1103
+ list(error)
1104
+ else
1105
+ list(false, { ok: true })
1106
+ }
1107
+
1108
+ //
1109
+ // Check for any incomplete catenates.
1110
+ //
1111
+
1112
+ let (writepath, entry) {
1113
+ for writepath in catenates
1114
+ for entry in catenates[writepath]
1115
+ if !entry.content
1116
+ build.errorlist.push(Error("Missing zip/cat element", writepath, "--", entry.readpath))
1009
1117
  } ()
1010
1118
  }
1011
1119
 
@@ -1086,7 +1194,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
1086
1194
  `(error, executor) = track.spawn(build.runway.spawn.0, build.runway.spawn.1, "Build-".concat(build.buildnum), {
1087
1195
  startup: {
1088
1196
  initcmds: maintext
1089
- dirpath: JSpath.join(fs.rootpath, build.destpath)
1197
+ dirpath: fs.path.resolve(fs.path.sep, fs.rootpath, build.destpath)
1090
1198
  }
1091
1199
  })
1092
1200
  if error
@@ -1136,6 +1244,7 @@ closure Builder(rules, operation, fs, srcpath, destpath, local build, group, new
1136
1244
  `(error, site) = track.spawn("V-Site", build.runway."v-site", fs, vpath)
1137
1245
  if error
1138
1246
  return (list(Error("Builder: cannot create virtual site", error)))
1247
+ sleep(1) // needed on Windows or it won't open, fiik why
1139
1248
  window = js.w.open(url, title, features)
1140
1249
  list(false, { ok: true }) // we don't know the executor
1141
1250
  }
@@ -296,7 +296,7 @@ closure projConnector(projman, projtype, local connector, watch) {
296
296
  *
297
297
  * Nide.proj/
298
298
  * nide_cliser.cfg - JSON definition of the project (see below)
299
- * NaanIDE_version.txt - [default] substitution file defining 1.0.7+1 etc.
299
+ * NaanIDE_version.txt - [default] substitution file defining 1.0.8+1 etc.
300
300
  * NaanIDE_version_builds.txt - [default] contains current build number as decimal string
301
301
  * build/ - [optional] default build output location
302
302
  * NaanIDE.lic - [optional] defines Zulch Laboratories, Inc. and other license info
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * column positioning: // // !
8
8
  *
9
- * Copyright (c) 2021 by Richard C. Zulch
9
+ * Copyright (c) 2021-2023 by Richard C. Zulch
10
10
  *
11
11
  */
12
12
 
@@ -48,17 +48,26 @@ function baseproc(procdef) {
48
48
  /*
49
49
  * pathmatch
50
50
  *
51
- * Given a parent symbol and a tuple of child procedure symbols within it, return the child
52
- * procedure named by the path in the tuple.
51
+ * Given a parent symbol and a tuple of child procedures within it, return the child procedure
52
+ * named by the path in the tuple. Each element in the child procedures is either a symbol == to the
53
+ * procedure or, in the case of lambdas, a tuple comprising the procedure symbol and an integer
54
+ * specifying the nth matching child. This is necessary because a procedure can have any number of
55
+ * children named lambda, and we need to pick the right ones even though we don't have identical
56
+ * symbols.
53
57
  *
54
58
  */
55
59
 
56
- function pathmatch(parent, path, local segment) {
60
+ function pathmatch(parent, path, local segment, nth, child) {
57
61
  if empty(path)
58
62
  return (parent)
59
- segment = tostring(pop(path))
63
+ segment = pop(path)
64
+ if tuple(segment) { // select nth instance of matching symbol
65
+ nth = segment.1
66
+ segment = segment.0
67
+ } else
68
+ nth = 0
60
69
  for child in parent@\.children
61
- if tostring(child) == segment
70
+ if child == segment && nth-- == 0
62
71
  return (pathmatch(child, path))
63
72
  false
64
73
  };
@@ -68,14 +77,27 @@ function pathmatch(parent, path, local segment) {
68
77
  * procpath
69
78
  *
70
79
  * Return a tuple of parent prcoedure symbols for the specified child procedure. Note that this
71
- * path includes the parent but pathmatch above requires the parent as a separate argument.
80
+ * path includes the parent but pathmatch above requires the parent as a separate argument. As noted
81
+ * above, the segments of the path are either a procedure symbol, or a tuple of symbol and integer.
72
82
  *
73
83
  */
74
84
 
75
- function procpath(procdef, local path) {
85
+ function procpath(procdef, local path, segment, parent, peers, nth, peer) {
76
86
  loop {
77
- push(procdef.1, path)
78
- parent = procdef.1@\.parent
87
+ segment = procdef.1
88
+ parent = segment@\.parent
89
+ if segment == lambda && parent { // lambdas all have same name
90
+ peers = parent@\.children
91
+ nth = 0
92
+ for peer in peers {
93
+ if peer === segment
94
+ break
95
+ if peer == segment
96
+ ++nth
97
+ }
98
+ push(list(segment, nth), path) // we are the nth of the matching ones
99
+ } else
100
+ push(segment, path)
79
101
  if !parent
80
102
  break
81
103
  procdef = parent.proc
@@ -248,8 +248,8 @@ closure MakePSMutil(fs, local util) {
248
248
  // erase: <boolean> -- remove extraneous files in destination folders
249
249
  // force: <boolean> -- copy files even if older (default is only copy if new/newer)
250
250
  // follow: <boolean> -- follow links instead of treating them as files
251
- // preserve: <boolean> -- preserve file info where possible
252
- // (e.g. mtime/atime/mode/uid/gid on NodeJS)
251
+ // idmode: <boolean> -- preserve id/mode where possible
252
+ // times: <boolean> -- preserve time where possible
253
253
  // }
254
254
  //
255
255
  // Limitations:
@@ -336,7 +336,8 @@ closure MakePSMutil(fs, local util) {
336
336
  //
337
337
  // Pass 3 - copy files and links
338
338
  //
339
- asyncArray(copies, 10, closure cpfiles(entry, index, elements, local error, data, instat, outstat) {
339
+ asyncArray(copies, 10, closure cpfiles(entry, index, elements,
340
+ local error, data, dest, instat, outstat) {
340
341
  `(error, instat) = fs.info(entry.source, fsopt)
341
342
  if !error {
342
343
  `(error, outstat) = outfs.info(entry.dest)
@@ -348,6 +349,13 @@ closure MakePSMutil(fs, local util) {
348
349
  error = Error("deepcopy: can't overwrite", entry.dest)
349
350
  else if instat.type == "symlink" && !options.follow {
350
351
  `(error, data) = fs.readlink(entry.source)
352
+ if !error {
353
+ `(error, dest) = outfs.readlink(entry.dest) // get destination to compare links
354
+ if dest == data
355
+ return // symlink unchanged
356
+ outfs.delete(entry.dest)
357
+ error = false // ignore any destination errors
358
+ }
351
359
  if !error
352
360
  `(error, data) = outfs.symlink(data, entry.dest)
353
361
  }
@@ -355,8 +363,16 @@ closure MakePSMutil(fs, local util) {
355
363
  `(error, data) = fs.readFile(entry.source, { encoding: "binary"})
356
364
  if !error
357
365
  `(error, data) = outfs.writeFile(entry.dest, data, { encoding: "binary"})
358
- if !error && options.preserve
366
+ if !error && (options.idmode || options.times) {
367
+ if !options.idmode
368
+ instat.mode = instat.uid = instat.gid = false
369
+ if !options.times {
370
+ instat.mtimeMs = instat.mtime = false
371
+ instat.nodejs_stat.mtimeMs = instat.nodejs_stat.mtime = false
372
+ instat.nodejs_stat.atimeMs = instat.nodejs_stat.atime = false
373
+ }
359
374
  `(error) = outfs.setInfo(entry.dest, instat)
375
+ }
360
376
  }
361
377
  }
362
378
  if error