@jdeighan/coffee-utils 8.0.3 → 8.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jdeighan/coffee-utils",
3
3
  "type": "module",
4
- "version": "8.0.3",
4
+ "version": "8.0.6",
5
5
  "description": "A set of utility functions for CoffeeScript",
6
6
  "main": "coffee_utils.js",
7
7
  "exports": {
@@ -19,6 +19,8 @@
19
19
  "./store": "./src/DataStores.js",
20
20
  "./taml": "./src/taml.js",
21
21
  "./placeholders": "./src/placeholders.js",
22
+ "./section": "./src/Section.js",
23
+ "./sectionmap": "./src/SectionMap.js",
22
24
  "./package.json": "./package.json"
23
25
  },
24
26
  "engines": {
@@ -0,0 +1,72 @@
1
+ # Section.coffee
2
+
3
+ import {
4
+ assert, pass, undef, defined, croak, isArray,
5
+ } from '@jdeighan/coffee-utils'
6
+ import {arrayToBlock} from '@jdeighan/coffee-utils/block'
7
+ import {indented} from '@jdeighan/coffee-utils/indent'
8
+
9
+ # ---------------------------------------------------------------------------
10
+
11
+ export class Section
12
+
13
+ constructor: (@name) ->
14
+
15
+ @lLines = []
16
+
17
+ # ..........................................................
18
+
19
+ length: () ->
20
+
21
+ return @lLines.length
22
+
23
+ # ..........................................................
24
+
25
+ indent: (level=1) ->
26
+
27
+ lNewLines = for line in @lLines
28
+ indented(line, level)
29
+ @lLines = lNewLines
30
+ return
31
+
32
+ # ..........................................................
33
+
34
+ isEmpty: () ->
35
+
36
+ return (@lLines.length == 0)
37
+
38
+ # ..........................................................
39
+
40
+ nonEmpty: () ->
41
+
42
+ return (@lLines.length > 0)
43
+
44
+ # ..........................................................
45
+
46
+ add: (data) ->
47
+
48
+ if isArray(data)
49
+ for line in data
50
+ @lLines.push line
51
+ else
52
+ @lLines.push data
53
+ return
54
+
55
+ # ..........................................................
56
+
57
+ prepend: (data) ->
58
+
59
+ if isArray(data)
60
+ @lLines = [data..., @lLines...]
61
+ else
62
+ @lLines = [data, @lLines...]
63
+ return
64
+
65
+ # ..........................................................
66
+
67
+ getBlock: () ->
68
+
69
+ if (@lLines.length == 0)
70
+ return undef
71
+ else
72
+ return arrayToBlock(@lLines)
package/src/Section.js ADDED
@@ -0,0 +1,89 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ // Section.coffee
3
+ import {
4
+ assert,
5
+ pass,
6
+ undef,
7
+ defined,
8
+ croak,
9
+ isArray
10
+ } from '@jdeighan/coffee-utils';
11
+
12
+ import {
13
+ arrayToBlock
14
+ } from '@jdeighan/coffee-utils/block';
15
+
16
+ import {
17
+ indented
18
+ } from '@jdeighan/coffee-utils/indent';
19
+
20
+ // ---------------------------------------------------------------------------
21
+ export var Section = class Section {
22
+ constructor(name) {
23
+ this.name = name;
24
+ this.lLines = [];
25
+ }
26
+
27
+ // ..........................................................
28
+ length() {
29
+ return this.lLines.length;
30
+ }
31
+
32
+ // ..........................................................
33
+ indent(level = 1) {
34
+ var lNewLines, line;
35
+ lNewLines = (function() {
36
+ var i, len, ref, results;
37
+ ref = this.lLines;
38
+ results = [];
39
+ for (i = 0, len = ref.length; i < len; i++) {
40
+ line = ref[i];
41
+ results.push(indented(line, level));
42
+ }
43
+ return results;
44
+ }).call(this);
45
+ this.lLines = lNewLines;
46
+ }
47
+
48
+ // ..........................................................
49
+ isEmpty() {
50
+ return this.lLines.length === 0;
51
+ }
52
+
53
+ // ..........................................................
54
+ nonEmpty() {
55
+ return this.lLines.length > 0;
56
+ }
57
+
58
+ // ..........................................................
59
+ add(data) {
60
+ var i, len, line;
61
+ if (isArray(data)) {
62
+ for (i = 0, len = data.length; i < len; i++) {
63
+ line = data[i];
64
+ this.lLines.push(line);
65
+ }
66
+ } else {
67
+ this.lLines.push(data);
68
+ }
69
+ }
70
+
71
+ // ..........................................................
72
+ prepend(data) {
73
+ if (isArray(data)) {
74
+ this.lLines = [...data, ...this.lLines];
75
+ } else {
76
+ this.lLines = [data, ...this.lLines];
77
+ }
78
+ }
79
+
80
+ // ..........................................................
81
+ getBlock() {
82
+ if (this.lLines.length === 0) {
83
+ return undef;
84
+ } else {
85
+ return arrayToBlock(this.lLines);
86
+ }
87
+ }
88
+
89
+ };
@@ -0,0 +1,217 @@
1
+ # SectionMap.coffee
2
+
3
+ import {
4
+ assert, pass, undef, defined, croak, OL, isEmpty, nonEmpty,
5
+ isString, isHash, isArray, isUniqueTree, isNonEmptyString,
6
+ isNonEmptyArray,
7
+ } from '@jdeighan/coffee-utils'
8
+ import {arrayToBlock} from '@jdeighan/coffee-utils/block'
9
+ import {indented} from '@jdeighan/coffee-utils/indent'
10
+ import {debug} from '@jdeighan/coffee-utils/debug'
11
+ import {Section} from '@jdeighan/coffee-utils/section'
12
+
13
+ # ---------------------------------------------------------------------------
14
+
15
+ isSectionName = (name) ->
16
+
17
+ return isString(name) && name.match(/^[a-z][a-z0-9]*/)
18
+
19
+ # ---------------------------------------------------------------------------
20
+
21
+ isSetName = (name) ->
22
+
23
+ return isString(name) && name.match(/^[A-Z][a-z0-9]*/)
24
+
25
+ # ---------------------------------------------------------------------------
26
+
27
+ export class SectionMap
28
+
29
+ constructor: (lSectionTree) ->
30
+ # --- lSectionTree is a tree of section names
31
+
32
+ debug "enter SectionMap()", lSectionTree
33
+ @lSectionTree = lSectionTree # a tree of section names
34
+ @hSets = {}
35
+ @hSections = {}
36
+ @addSections lSectionTree
37
+ debug "return from SectionMap()", @hSections
38
+
39
+ # ..........................................................
40
+
41
+ addSections: (desc) ->
42
+ # --- returns a flat array of sections that were added
43
+
44
+ if isString(desc)
45
+ assert nonEmpty(desc), "empty section name"
46
+ assert isSectionName(desc), "bad section name #{OL(desc)}"
47
+ assert (@hSections[desc] == undef), "duplicate section #{OL(desc)}"
48
+ @hSections[desc] = new Section(desc)
49
+ return [desc]
50
+ else
51
+ assert isArray(desc), "not an array or string #{OL(desc)}"
52
+ name = undef
53
+ lParts = []
54
+ for item,i in desc
55
+ if (i==0) && isSetName(item)
56
+ name = item
57
+ else
58
+ lAdded = @addSections item
59
+ for item in lAdded
60
+ lParts.push item
61
+ if defined(name)
62
+ @addSet name, lParts
63
+ return lParts
64
+ return
65
+
66
+ # ..........................................................
67
+
68
+ addSet: (name, lSectionTree) ->
69
+
70
+ debug "enter addSet()", name, lSectionTree
71
+
72
+ # --- check the name
73
+ assert isSetName(name), "not a valid set name #{OL(name)}"
74
+
75
+ # --- check lSectionTree
76
+ assert isArray(lSectionTree), "arg 2 not an array"
77
+ for secName in lSectionTree
78
+ assert isNonEmptyString(secName),
79
+ "not a non-empty string #{OL(secName)}"
80
+ assert defined(@hSections[secName]),
81
+ "not a section name #{OL(secName)}"
82
+
83
+ @hSets[name] = lSectionTree
84
+ debug 'hSets', @hSets
85
+ debug "return from addSet()"
86
+ return
87
+
88
+ # ..........................................................
89
+ # --- sections returned in depth-first order from section tree
90
+ # Set names are simply skipped
91
+ # yields: [<level>, <section>]
92
+
93
+ allSections: (desc=undef, level=0) ->
94
+
95
+ debug "enter allSections()", desc, level
96
+ if (desc == undef)
97
+ desc = @lSectionTree
98
+ if isArray(desc)
99
+ for item in desc
100
+ if isSectionName(item)
101
+ result = [level, @section(item)]
102
+ debug 'yield', result
103
+ yield result
104
+ else if isSetName(item)
105
+ pass
106
+ else
107
+ assert isArray(item), "not an array #{OL(item)}"
108
+ yield from @allSections(item, level+1)
109
+ else if isSectionName(desc)
110
+ result = [level, @section(desc)]
111
+ debug 'yield', result
112
+ yield result
113
+ else if isSetName(desc)
114
+ lTree = @hSets[desc]
115
+ assert defined(lTree), "Not a Set: #{OL(desc)}"
116
+ yield from @allSections(lTree, level)
117
+ else
118
+ croak "Bad item: #{OL(desc)}"
119
+ debug "return from allSections()"
120
+ return
121
+
122
+ # ..........................................................
123
+ # --- procFunc should be (name, text) -> return processedText
124
+
125
+ getBlock: (procFunc=undef, lTree=undef) ->
126
+
127
+ debug "enter getBlock()"
128
+ if (lTree == undef)
129
+ lTree = @lSectionTree
130
+ else
131
+ assert isArray(lTree), "not an array #{OL(lTree)}"
132
+
133
+ lParts = []
134
+ for part in lTree
135
+ if isString(part)
136
+ text = @section(part).getBlock()
137
+ if nonEmpty(text) && defined(procFunc)
138
+ text = procFunc(part, text)
139
+ else if isNonEmptyArray(part)
140
+ if isSectionName(part[0])
141
+ text = @getBlock(procFunc, part)
142
+ else if isSetName(part[0])
143
+ text = @getBlock(procFunc, part.slice(1))
144
+ if nonEmpty(text) && defined(procFunc)
145
+ text = procFunc(part[0], text)
146
+ else
147
+ croak "Bad part: #{OL(part)}"
148
+ else
149
+ croak "Bad part: #{OL(part)}"
150
+ if defined(text)
151
+ lParts.push text
152
+
153
+ debug 'lParts', lParts
154
+ result = arrayToBlock(lParts)
155
+ debug "return from getBlock()", result
156
+ return result
157
+
158
+ # ..........................................................
159
+
160
+ section: (name) ->
161
+
162
+ sect = @hSections[name]
163
+ assert defined(sect), "No section named #{OL(name)}"
164
+ return sect
165
+
166
+ # ..........................................................
167
+
168
+ firstSection: (name) ->
169
+
170
+ assert isSetName(name), "bad set name #{OL(name)}"
171
+ lSectionTree = @hSets[name]
172
+ assert defined(lSectionTree), "no such set #{OL(name)}"
173
+ assert nonEmpty(lSectionTree), "empty section #{OL(name)}"
174
+ return @section(lSectionTree[0])
175
+
176
+ # ..........................................................
177
+
178
+ lastSection: (name) ->
179
+
180
+ assert isSetName(name), "bad set name #{OL(name)}"
181
+ lSectionTree = @hSets[name]
182
+ assert defined(lSectionTree), "no such set #{OL(name)}"
183
+ assert nonEmpty(lSectionTree), "empty section #{OL(name)}"
184
+ return @section(lSectionTree[lSectionTree.length - 1])
185
+
186
+ # ..........................................................
187
+
188
+ length: (desc=undef) ->
189
+
190
+ result = 0
191
+ for [_, sect] from @allSections(desc)
192
+ result += sect.length()
193
+ return result
194
+
195
+ # ..........................................................
196
+
197
+ isEmpty: (desc=undef) ->
198
+
199
+ return (@length(desc) == 0)
200
+
201
+ # ..........................................................
202
+
203
+ nonEmpty: (desc=undef) ->
204
+
205
+ return (@length(desc) > 0)
206
+
207
+ # ..........................................................
208
+
209
+ getShape: () ->
210
+
211
+ debug "enter getShape()"
212
+ lParts = []
213
+ for [level, sect] from @allSections()
214
+ lParts.push indented(sect.name, level)
215
+ result = arrayToBlock(lParts)
216
+ debug "return from getShape()", result
217
+ return result
@@ -0,0 +1,256 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ // SectionMap.coffee
3
+ var isSectionName, isSetName;
4
+
5
+ import {
6
+ assert,
7
+ pass,
8
+ undef,
9
+ defined,
10
+ croak,
11
+ OL,
12
+ isEmpty,
13
+ nonEmpty,
14
+ isString,
15
+ isHash,
16
+ isArray,
17
+ isUniqueTree,
18
+ isNonEmptyString,
19
+ isNonEmptyArray
20
+ } from '@jdeighan/coffee-utils';
21
+
22
+ import {
23
+ arrayToBlock
24
+ } from '@jdeighan/coffee-utils/block';
25
+
26
+ import {
27
+ indented
28
+ } from '@jdeighan/coffee-utils/indent';
29
+
30
+ import {
31
+ debug
32
+ } from '@jdeighan/coffee-utils/debug';
33
+
34
+ import {
35
+ Section
36
+ } from '@jdeighan/coffee-utils/section';
37
+
38
+ // ---------------------------------------------------------------------------
39
+ isSectionName = function(name) {
40
+ return isString(name) && name.match(/^[a-z][a-z0-9]*/);
41
+ };
42
+
43
+ // ---------------------------------------------------------------------------
44
+ isSetName = function(name) {
45
+ return isString(name) && name.match(/^[A-Z][a-z0-9]*/);
46
+ };
47
+
48
+ // ---------------------------------------------------------------------------
49
+ export var SectionMap = class SectionMap {
50
+ constructor(lSectionTree) {
51
+ // --- lSectionTree is a tree of section names
52
+ debug("enter SectionMap()", lSectionTree);
53
+ this.lSectionTree = lSectionTree; // a tree of section names
54
+ this.hSets = {};
55
+ this.hSections = {};
56
+ this.addSections(lSectionTree);
57
+ debug("return from SectionMap()", this.hSections);
58
+ }
59
+
60
+ // ..........................................................
61
+ addSections(desc) {
62
+ var i, item, j, k, lAdded, lParts, len, len1, name;
63
+ // --- returns a flat array of sections that were added
64
+ if (isString(desc)) {
65
+ assert(nonEmpty(desc), "empty section name");
66
+ assert(isSectionName(desc), `bad section name ${OL(desc)}`);
67
+ assert(this.hSections[desc] === undef, `duplicate section ${OL(desc)}`);
68
+ this.hSections[desc] = new Section(desc);
69
+ return [desc];
70
+ } else {
71
+ assert(isArray(desc), `not an array or string ${OL(desc)}`);
72
+ name = undef;
73
+ lParts = [];
74
+ for (i = j = 0, len = desc.length; j < len; i = ++j) {
75
+ item = desc[i];
76
+ if ((i === 0) && isSetName(item)) {
77
+ name = item;
78
+ } else {
79
+ lAdded = this.addSections(item);
80
+ for (k = 0, len1 = lAdded.length; k < len1; k++) {
81
+ item = lAdded[k];
82
+ lParts.push(item);
83
+ }
84
+ }
85
+ }
86
+ if (defined(name)) {
87
+ this.addSet(name, lParts);
88
+ }
89
+ return lParts;
90
+ }
91
+ }
92
+
93
+ // ..........................................................
94
+ addSet(name, lSectionTree) {
95
+ var j, len, secName;
96
+ debug("enter addSet()", name, lSectionTree);
97
+ // --- check the name
98
+ assert(isSetName(name), `not a valid set name ${OL(name)}`);
99
+ // --- check lSectionTree
100
+ assert(isArray(lSectionTree), "arg 2 not an array");
101
+ for (j = 0, len = lSectionTree.length; j < len; j++) {
102
+ secName = lSectionTree[j];
103
+ assert(isNonEmptyString(secName), `not a non-empty string ${OL(secName)}`);
104
+ assert(defined(this.hSections[secName]), `not a section name ${OL(secName)}`);
105
+ }
106
+ this.hSets[name] = lSectionTree;
107
+ debug('hSets', this.hSets);
108
+ debug("return from addSet()");
109
+ }
110
+
111
+ // ..........................................................
112
+ // --- sections returned in depth-first order from section tree
113
+ // Set names are simply skipped
114
+ // yields: [<level>, <section>]
115
+ * allSections(desc = undef, level = 0) {
116
+ var item, j, lTree, len, result;
117
+ debug("enter allSections()", desc, level);
118
+ if (desc === undef) {
119
+ desc = this.lSectionTree;
120
+ }
121
+ if (isArray(desc)) {
122
+ for (j = 0, len = desc.length; j < len; j++) {
123
+ item = desc[j];
124
+ if (isSectionName(item)) {
125
+ result = [level, this.section(item)];
126
+ debug('yield', result);
127
+ yield result;
128
+ } else if (isSetName(item)) {
129
+ pass;
130
+ } else {
131
+ assert(isArray(item), `not an array ${OL(item)}`);
132
+ yield* this.allSections(item, level + 1);
133
+ }
134
+ }
135
+ } else if (isSectionName(desc)) {
136
+ result = [level, this.section(desc)];
137
+ debug('yield', result);
138
+ yield result;
139
+ } else if (isSetName(desc)) {
140
+ lTree = this.hSets[desc];
141
+ assert(defined(lTree), `Not a Set: ${OL(desc)}`);
142
+ yield* this.allSections(lTree, level);
143
+ } else {
144
+ croak(`Bad item: ${OL(desc)}`);
145
+ }
146
+ debug("return from allSections()");
147
+ }
148
+
149
+ // ..........................................................
150
+ // --- procFunc should be (name, text) -> return processedText
151
+ getBlock(procFunc = undef, lTree = undef) {
152
+ var j, lParts, len, part, result, text;
153
+ debug("enter getBlock()");
154
+ if (lTree === undef) {
155
+ lTree = this.lSectionTree;
156
+ } else {
157
+ assert(isArray(lTree), `not an array ${OL(lTree)}`);
158
+ }
159
+ lParts = [];
160
+ for (j = 0, len = lTree.length; j < len; j++) {
161
+ part = lTree[j];
162
+ if (isString(part)) {
163
+ text = this.section(part).getBlock();
164
+ if (nonEmpty(text) && defined(procFunc)) {
165
+ text = procFunc(part, text);
166
+ }
167
+ } else if (isNonEmptyArray(part)) {
168
+ if (isSectionName(part[0])) {
169
+ text = this.getBlock(procFunc, part);
170
+ } else if (isSetName(part[0])) {
171
+ text = this.getBlock(procFunc, part.slice(1));
172
+ if (nonEmpty(text) && defined(procFunc)) {
173
+ text = procFunc(part[0], text);
174
+ }
175
+ } else {
176
+ croak(`Bad part: ${OL(part)}`);
177
+ }
178
+ } else {
179
+ croak(`Bad part: ${OL(part)}`);
180
+ }
181
+ if (defined(text)) {
182
+ lParts.push(text);
183
+ }
184
+ }
185
+ debug('lParts', lParts);
186
+ result = arrayToBlock(lParts);
187
+ debug("return from getBlock()", result);
188
+ return result;
189
+ }
190
+
191
+ // ..........................................................
192
+ section(name) {
193
+ var sect;
194
+ sect = this.hSections[name];
195
+ assert(defined(sect), `No section named ${OL(name)}`);
196
+ return sect;
197
+ }
198
+
199
+ // ..........................................................
200
+ firstSection(name) {
201
+ var lSectionTree;
202
+ assert(isSetName(name), `bad set name ${OL(name)}`);
203
+ lSectionTree = this.hSets[name];
204
+ assert(defined(lSectionTree), `no such set ${OL(name)}`);
205
+ assert(nonEmpty(lSectionTree), `empty section ${OL(name)}`);
206
+ return this.section(lSectionTree[0]);
207
+ }
208
+
209
+ // ..........................................................
210
+ lastSection(name) {
211
+ var lSectionTree;
212
+ assert(isSetName(name), `bad set name ${OL(name)}`);
213
+ lSectionTree = this.hSets[name];
214
+ assert(defined(lSectionTree), `no such set ${OL(name)}`);
215
+ assert(nonEmpty(lSectionTree), `empty section ${OL(name)}`);
216
+ return this.section(lSectionTree[lSectionTree.length - 1]);
217
+ }
218
+
219
+ // ..........................................................
220
+ length(desc = undef) {
221
+ var _, ref, result, sect, x;
222
+ result = 0;
223
+ ref = this.allSections(desc);
224
+ for (x of ref) {
225
+ [_, sect] = x;
226
+ result += sect.length();
227
+ }
228
+ return result;
229
+ }
230
+
231
+ // ..........................................................
232
+ isEmpty(desc = undef) {
233
+ return this.length(desc) === 0;
234
+ }
235
+
236
+ // ..........................................................
237
+ nonEmpty(desc = undef) {
238
+ return this.length(desc) > 0;
239
+ }
240
+
241
+ // ..........................................................
242
+ getShape() {
243
+ var lParts, level, ref, result, sect, x;
244
+ debug("enter getShape()");
245
+ lParts = [];
246
+ ref = this.allSections();
247
+ for (x of ref) {
248
+ [level, sect] = x;
249
+ lParts.push(indented(sect.name, level));
250
+ }
251
+ result = arrayToBlock(lParts);
252
+ debug("return from getShape()", result);
253
+ return result;
254
+ }
255
+
256
+ };
@@ -30,19 +30,31 @@ export blockToArray = (block) ->
30
30
  export arrayToBlock = (lLines) ->
31
31
 
32
32
  if (lLines == undef)
33
- return ''
33
+ return undef
34
34
  assert isArray(lLines), "lLines is not an array"
35
35
  lLines = lLines.filter((line) => defined(line));
36
36
  if lLines.length == 0
37
- return ''
37
+ return undef
38
38
  else
39
39
  return rtrim(lLines.join('\n'))
40
40
 
41
41
  # ---------------------------------------------------------------------------
42
42
 
43
+ export splitBlock = (block) ->
44
+
45
+ assert isString(block), "not a string"
46
+ pos = block.indexOf('\n')
47
+ if (pos == -1)
48
+ return [block, undef]
49
+ else
50
+ return [block.substring(0, pos), block.substring(pos+1)]
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+
43
55
  export firstLine = (block) ->
44
56
 
45
- assert isString(block), "firstLine(): string expected"
57
+ assert isString(block), "not a string"
46
58
  pos = block.indexOf('\n')
47
59
  if (pos == -1)
48
60
  return block
@@ -53,10 +65,10 @@ export firstLine = (block) ->
53
65
 
54
66
  export remainingLines = (block) ->
55
67
 
56
- assert isString(block), "remainingLines(): string expected"
68
+ assert isString(block), "not a string"
57
69
  pos = block.indexOf('\n')
58
70
  if (pos == -1)
59
- return ''
71
+ return undef
60
72
  else
61
73
  return block.substring(pos+1)
62
74
 
@@ -38,23 +38,35 @@ export var blockToArray = function(block) {
38
38
  // arrayToBlock - block will have no trailing whitespace
39
39
  export var arrayToBlock = function(lLines) {
40
40
  if (lLines === undef) {
41
- return '';
41
+ return undef;
42
42
  }
43
43
  assert(isArray(lLines), "lLines is not an array");
44
44
  lLines = lLines.filter((line) => {
45
45
  return defined(line);
46
46
  });
47
47
  if (lLines.length === 0) {
48
- return '';
48
+ return undef;
49
49
  } else {
50
50
  return rtrim(lLines.join('\n'));
51
51
  }
52
52
  };
53
53
 
54
+ // ---------------------------------------------------------------------------
55
+ export var splitBlock = function(block) {
56
+ var pos;
57
+ assert(isString(block), "not a string");
58
+ pos = block.indexOf('\n');
59
+ if (pos === -1) {
60
+ return [block, undef];
61
+ } else {
62
+ return [block.substring(0, pos), block.substring(pos + 1)];
63
+ }
64
+ };
65
+
54
66
  // ---------------------------------------------------------------------------
55
67
  export var firstLine = function(block) {
56
68
  var pos;
57
- assert(isString(block), "firstLine(): string expected");
69
+ assert(isString(block), "not a string");
58
70
  pos = block.indexOf('\n');
59
71
  if (pos === -1) {
60
72
  return block;
@@ -66,10 +78,10 @@ export var firstLine = function(block) {
66
78
  // ---------------------------------------------------------------------------
67
79
  export var remainingLines = function(block) {
68
80
  var pos;
69
- assert(isString(block), "remainingLines(): string expected");
81
+ assert(isString(block), "not a string");
70
82
  pos = block.indexOf('\n');
71
83
  if (pos === -1) {
72
- return '';
84
+ return undef;
73
85
  } else {
74
86
  return block.substring(pos + 1);
75
87
  }
@@ -173,6 +173,12 @@ export isArray = (x) ->
173
173
 
174
174
  # ---------------------------------------------------------------------------
175
175
 
176
+ export isNonEmptyArray = (x) ->
177
+
178
+ return isArray(x) && (x.length > 0)
179
+
180
+ # ---------------------------------------------------------------------------
181
+
176
182
  export isHash = (x, lKeys) ->
177
183
 
178
184
  if ! x || (getClassName(x) != 'Object')
@@ -186,6 +192,12 @@ export isHash = (x, lKeys) ->
186
192
 
187
193
  # ---------------------------------------------------------------------------
188
194
 
195
+ export isNonEmptyHash = (x) ->
196
+
197
+ return isHash(x) && (Object.keys(x).length > 0)
198
+
199
+ # ---------------------------------------------------------------------------
200
+
189
201
  export hashHasKey = (x, key) ->
190
202
 
191
203
  assert isHash(x), "hashHasKey(): not a hash"
@@ -159,6 +159,11 @@ export var isArray = function(x) {
159
159
  return Array.isArray(x);
160
160
  };
161
161
 
162
+ // ---------------------------------------------------------------------------
163
+ export var isNonEmptyArray = function(x) {
164
+ return isArray(x) && (x.length > 0);
165
+ };
166
+
162
167
  // ---------------------------------------------------------------------------
163
168
  export var isHash = function(x, lKeys) {
164
169
  var i, key, len;
@@ -177,6 +182,11 @@ export var isHash = function(x, lKeys) {
177
182
  return true;
178
183
  };
179
184
 
185
+ // ---------------------------------------------------------------------------
186
+ export var isNonEmptyHash = function(x) {
187
+ return isHash(x) && (Object.keys(x).length > 0);
188
+ };
189
+
180
190
  // ---------------------------------------------------------------------------
181
191
  export var hashHasKey = function(x, key) {
182
192
  assert(isHash(x), "hashHasKey(): not a hash");
@@ -146,3 +146,10 @@ export tabify = (str, numSpaces=undef) ->
146
146
  export untabify = (str, numSpaces=3) ->
147
147
 
148
148
  return str.replace(/\t/g, ' '.repeat(numSpaces))
149
+
150
+ # ---------------------------------------------------------------------------
151
+ # enclose - indent text, surround with pre and post
152
+
153
+ export enclose = (text, pre, post) ->
154
+
155
+ return pre + "\n" + indented(text) + "\n" + post
@@ -175,3 +175,9 @@ export var tabify = function(str, numSpaces = undef) {
175
175
  export var untabify = function(str, numSpaces = 3) {
176
176
  return str.replace(/\t/g, ' '.repeat(numSpaces));
177
177
  };
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // enclose - indent text, surround with pre and post
181
+ export var enclose = function(text, pre, post) {
182
+ return pre + "\n" + indented(text) + "\n" + post;
183
+ };