@jdeighan/coffee-utils 8.0.3 → 8.0.6

Sign up to get free protection for your applications and to get access to all the features.
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
+ };