@jdeighan/coffee-utils 8.0.2 → 8.0.5

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