@jdeighan/coffee-utils 15.0.0 → 16.0.0

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": "15.0.0",
4
+ "version": "16.0.0",
5
5
  "description": "A set of utility functions for CoffeeScript",
6
6
  "main": "coffee_utils.js",
7
7
  "exports": {
package/src/indent.coffee CHANGED
@@ -34,15 +34,6 @@ export splitLine = (line, oneIndent=undef) =>
34
34
  [prefix, str] = splitPrefix(line)
35
35
  return [indentLevel(prefix, oneIndent), str]
36
36
 
37
- # ---------------------------------------------------------------------------
38
- # indentation - return appropriate indentation string for given level
39
- # export only to allow unit testing
40
-
41
- export indentation = (level, oneIndent="\t") =>
42
-
43
- assert (level >= 0), "indentation(): negative level"
44
- return oneIndent.repeat(level)
45
-
46
37
  # ---------------------------------------------------------------------------
47
38
  # indentLevel - determine indent level of a string
48
39
  # it's OK if the string is ONLY indentation
@@ -59,28 +50,56 @@ export indentLevel = (line, oneIndent=undef) =>
59
50
  if (prefixLen == 0)
60
51
  return 0
61
52
 
62
- if defined(oneIndent)
63
- # --- prefix must be some multiple of oneIndent
64
- len = oneIndent.length
65
- assert (prefixLen % len == 0),
66
- "prefix #{OL(prefix)} not a mult of #{OL(oneIndent)}"
67
-
68
- level = prefixLen / len
53
+ # --- Match \t* followed by \x20* (error if no match)
54
+ if lMatches = prefix.match(/(\t*)(\x20*)/)
55
+ nTabs = lMatches[1].length
56
+ nSpaces = lMatches[2].length
69
57
  else
70
- ch = prefix.substring(0, 1)
71
- if (ch == "\t")
72
- oneIndent = "\t"
73
- level = prefixLen
74
- else if (ch == ' ')
75
- oneIndent = ' '.repeat(prefixLen)
76
- level = 1
58
+ croak "Invalid mix of TABs and spaces"
59
+
60
+ # --- oneIndent must be one of:
61
+ # undef
62
+ # a single TAB character
63
+ # some number of space characters
64
+
65
+ switch oneIndent
66
+ when undef
67
+ if (nTabs > 0)
68
+ level = nTabs # there may also be spaces, but we ignore them
69
+ oneIndent = "\t" # may be used at end
70
+ else
71
+ assert (nSpaces > 0), "There must be TABS or spaces"
72
+ level = 1
73
+ oneIndent = ' '.repeat(nSpaces) # may be used at end
74
+ when "\t"
75
+ assert (nTabs > 0), "Expecting TAB indentation, found spaces"
76
+ # --- NOTE: there may be spaces, but they're not indentation
77
+ level = nTabs
77
78
  else
78
- croak "Bad Indentation in #{OL(line)}"
79
-
80
- assert (prefix == oneIndent.repeat(level)),
81
- "prefix #{OL(prefix)} not a mult of #{OL(oneIndent)}"
79
+ # --- oneIndent must be all space chars
80
+ assert (nTabs == 0),
81
+ "Indentation has TABs but oneIndent = #{OL(oneIndent)}"
82
+ assert (nSpaces % oneIndent.length == 0),
83
+ "prefix #{OL(prefix)} not a mult of #{OL(oneIndent)}"
84
+ level = nSpaces / oneIndent.length
85
+
86
+ # --- If a block, i.e. multi-line string, then all lines must be
87
+ # at least at this level
88
+ if (line.indexOf("\n") >= 0)
89
+ for str in toArray(line)
90
+ assert (indentLevel(str, oneIndent) >= level),
91
+ "indentLevel of #{OL(line)} can't be found"
82
92
  return level
83
93
 
94
+ # ---------------------------------------------------------------------------
95
+ # indentation - return appropriate indentation string for given level
96
+ # export only to allow unit testing
97
+
98
+ export indentation = (level, oneIndent="\t") =>
99
+
100
+ assert (level >= 0), "indentation(): negative level"
101
+ return oneIndent.repeat(level)
102
+
84
103
  # ---------------------------------------------------------------------------
85
104
  # isUndented - true iff indentLevel(line) == 0
86
105
 
@@ -127,27 +146,22 @@ export indented = (input, level=1, oneIndent="\t") =>
127
146
  # ---------------------------------------------------------------------------
128
147
  # undented - string with 1st line indentation removed for each line
129
148
  # - ignore leading empty lines
130
- # - unless level is set, in which case exactly that
131
- # indentation is removed
132
149
  # - returns same type as text, i.e. either string or array
133
150
 
134
- export undented = (input, level=undef, oneIndent="\t") =>
135
-
136
- if defined(level) && (level==0)
137
- return input
151
+ export undented = (input) =>
138
152
 
139
- # --- Remove any leading blank lines, set lLines
153
+ # --- If a string, convert to an array
140
154
  if isString(input)
141
- if lMatches = input.match(///^ [\r\n]+ (.*) $///s)
142
- input = lMatches[1]
143
155
  lLines = toArray(input)
144
156
  else if isArray(input)
145
157
  lLines = input
146
- while (lLines.length > 0) && isEmpty(lLines[0])
147
- lLines.shift()
148
158
  else
149
159
  croak "input not a string or array"
150
160
 
161
+ # --- Remove leading blank lines
162
+ while (lLines.length > 0) && isEmpty(lLines[0])
163
+ lLines.shift() # remove
164
+
151
165
  if (lLines.length == 0)
152
166
  if isString(input)
153
167
  return ''
@@ -155,27 +169,23 @@ export undented = (input, level=undef, oneIndent="\t") =>
155
169
  return []
156
170
 
157
171
  # --- determine what to remove from beginning of each line
158
- if defined(level)
159
- assert isInteger(level), "level must be an integer"
160
- toRemove = indentation(level, oneIndent)
161
- else
162
- lMatches = lLines[0].match(/^\s*/)
163
- toRemove = lMatches[0]
164
- nToRemove = indentLevel(toRemove)
165
-
166
- lNewLines = []
167
- for line in lLines
168
- if isEmpty(line)
169
- lNewLines.push('')
170
- else
171
- if (line.indexOf(toRemove) != 0)
172
- throw new Error("remove #{OL(toRemove)} from #{OL(line)}")
173
- lNewLines.push(line.substr(nToRemove))
172
+ lMatches = lLines[0].match(/^\s*/)
173
+ toRemove = lMatches[0]
174
+ nToRemove = toRemove.length
175
+ if (nToRemove > 0)
176
+ lLines = lLines.map( (line) =>
177
+ if isEmpty(line)
178
+ return ''
179
+ else
180
+ assert (line.indexOf(toRemove)==0),
181
+ "can't remove #{OL(toRemove)} from #{OL(line)}"
182
+ return line.substr(nToRemove)
183
+ )
174
184
 
175
185
  if isString(input)
176
- return toBlock(lNewLines)
186
+ return toBlock(lLines)
177
187
  else
178
- return lNewLines
188
+ return lLines
179
189
 
180
190
  # ---------------------------------------------------------------------------
181
191
  # enclose - indent text, surround with pre and post
package/src/indent.js CHANGED
@@ -48,19 +48,11 @@ export var splitLine = (line, oneIndent = undef) => {
48
48
  return [indentLevel(prefix, oneIndent), str];
49
49
  };
50
50
 
51
- // ---------------------------------------------------------------------------
52
- // indentation - return appropriate indentation string for given level
53
- // export only to allow unit testing
54
- export var indentation = (level, oneIndent = "\t") => {
55
- assert(level >= 0, "indentation(): negative level");
56
- return oneIndent.repeat(level);
57
- };
58
-
59
51
  // ---------------------------------------------------------------------------
60
52
  // indentLevel - determine indent level of a string
61
53
  // it's OK if the string is ONLY indentation
62
54
  export var indentLevel = (line, oneIndent = undef) => {
63
- var ch, lMatches, len, level, prefix, prefixLen;
55
+ var i, lMatches, len, level, nSpaces, nTabs, prefix, prefixLen, ref, str;
64
56
  assert(isString(line), "not a string");
65
57
  // --- This will always match, and it's greedy
66
58
  if (lMatches = line.match(/^\s*/)) {
@@ -70,27 +62,59 @@ export var indentLevel = (line, oneIndent = undef) => {
70
62
  if (prefixLen === 0) {
71
63
  return 0;
72
64
  }
73
- if (defined(oneIndent)) {
74
- // --- prefix must be some multiple of oneIndent
75
- len = oneIndent.length;
76
- assert(prefixLen % len === 0, `prefix ${OL(prefix)} not a mult of ${OL(oneIndent)}`);
77
- level = prefixLen / len;
65
+ // --- Match \t* followed by \x20* (error if no match)
66
+ if (lMatches = prefix.match(/(\t*)(\x20*)/)) {
67
+ nTabs = lMatches[1].length;
68
+ nSpaces = lMatches[2].length;
78
69
  } else {
79
- ch = prefix.substring(0, 1);
80
- if (ch === "\t") {
81
- oneIndent = "\t";
82
- level = prefixLen;
83
- } else if (ch === ' ') {
84
- oneIndent = ' '.repeat(prefixLen);
85
- level = 1;
86
- } else {
87
- croak(`Bad Indentation in ${OL(line)}`);
70
+ croak("Invalid mix of TABs and spaces");
71
+ }
72
+ // --- oneIndent must be one of:
73
+ // undef
74
+ // a single TAB character
75
+ // some number of space characters
76
+ switch (oneIndent) {
77
+ case undef:
78
+ if (nTabs > 0) {
79
+ level = nTabs; // there may also be spaces, but we ignore them
80
+ oneIndent = "\t"; // may be used at end
81
+ } else {
82
+ assert(nSpaces > 0, "There must be TABS or spaces");
83
+ level = 1;
84
+ oneIndent = ' '.repeat(nSpaces); // may be used at end
85
+ }
86
+ break;
87
+ case "\t":
88
+ assert(nTabs > 0, "Expecting TAB indentation, found spaces");
89
+ // --- NOTE: there may be spaces, but they're not indentation
90
+ level = nTabs;
91
+ break;
92
+ default:
93
+ // --- oneIndent must be all space chars
94
+ assert(nTabs === 0, `Indentation has TABs but oneIndent = ${OL(oneIndent)}`);
95
+ assert(nSpaces % oneIndent.length === 0, `prefix ${OL(prefix)} not a mult of ${OL(oneIndent)}`);
96
+ level = nSpaces / oneIndent.length;
97
+ }
98
+ // --- If a block, i.e. multi-line string, then all lines must be
99
+ // at least at this level
100
+ if (line.indexOf("\n") >= 0) {
101
+ ref = toArray(line);
102
+ for (i = 0, len = ref.length; i < len; i++) {
103
+ str = ref[i];
104
+ assert(indentLevel(str, oneIndent) >= level, `indentLevel of ${OL(line)} can't be found`);
88
105
  }
89
106
  }
90
- assert(prefix === oneIndent.repeat(level), `prefix ${OL(prefix)} not a mult of ${OL(oneIndent)}`);
91
107
  return level;
92
108
  };
93
109
 
110
+ // ---------------------------------------------------------------------------
111
+ // indentation - return appropriate indentation string for given level
112
+ // export only to allow unit testing
113
+ export var indentation = (level, oneIndent = "\t") => {
114
+ assert(level >= 0, "indentation(): negative level");
115
+ return oneIndent.repeat(level);
116
+ };
117
+
94
118
  // ---------------------------------------------------------------------------
95
119
  // isUndented - true iff indentLevel(line) == 0
96
120
  export var isUndented = (line) => {
@@ -102,7 +126,7 @@ export var isUndented = (line) => {
102
126
  // indented - add indentation to each string in a block or array
103
127
  // - returns the same type as input, i.e. array or string
104
128
  export var indented = (input, level = 1, oneIndent = "\t") => {
105
- var i, lLines, len1, line, ref, toAdd;
129
+ var i, lLines, len, line, ref, toAdd;
106
130
  // --- level can be a string, in which case it is
107
131
  // pre-pended to each line of input
108
132
  if (isString(level)) {
@@ -123,7 +147,7 @@ export var indented = (input, level = 1, oneIndent = "\t") => {
123
147
  // else it splits the string into an array of lines
124
148
  lLines = [];
125
149
  ref = toArray(input);
126
- for (i = 0, len1 = ref.length; i < len1; i++) {
150
+ for (i = 0, len = ref.length; i < len; i++) {
127
151
  line = ref[i];
128
152
  if (isEmpty(line)) {
129
153
  lLines.push('');
@@ -142,28 +166,21 @@ export var indented = (input, level = 1, oneIndent = "\t") => {
142
166
  // ---------------------------------------------------------------------------
143
167
  // undented - string with 1st line indentation removed for each line
144
168
  // - ignore leading empty lines
145
- // - unless level is set, in which case exactly that
146
- // indentation is removed
147
169
  // - returns same type as text, i.e. either string or array
148
- export var undented = (input, level = undef, oneIndent = "\t") => {
149
- var i, lLines, lMatches, lNewLines, len1, line, nToRemove, toRemove;
150
- if (defined(level) && (level === 0)) {
151
- return input;
152
- }
153
- // --- Remove any leading blank lines, set lLines
170
+ export var undented = (input) => {
171
+ var lLines, lMatches, nToRemove, toRemove;
172
+ // --- If a string, convert to an array
154
173
  if (isString(input)) {
155
- if (lMatches = input.match(/^[\r\n]+(.*)$/s)) {
156
- input = lMatches[1];
157
- }
158
174
  lLines = toArray(input);
159
175
  } else if (isArray(input)) {
160
176
  lLines = input;
161
- while ((lLines.length > 0) && isEmpty(lLines[0])) {
162
- lLines.shift();
163
- }
164
177
  } else {
165
178
  croak("input not a string or array");
166
179
  }
180
+ // --- Remove leading blank lines
181
+ while ((lLines.length > 0) && isEmpty(lLines[0])) {
182
+ lLines.shift(); // remove
183
+ }
167
184
  if (lLines.length === 0) {
168
185
  if (isString(input)) {
169
186
  return '';
@@ -172,30 +189,23 @@ export var undented = (input, level = undef, oneIndent = "\t") => {
172
189
  }
173
190
  }
174
191
  // --- determine what to remove from beginning of each line
175
- if (defined(level)) {
176
- assert(isInteger(level), "level must be an integer");
177
- toRemove = indentation(level, oneIndent);
178
- } else {
179
- lMatches = lLines[0].match(/^\s*/);
180
- toRemove = lMatches[0];
181
- }
182
- nToRemove = indentLevel(toRemove);
183
- lNewLines = [];
184
- for (i = 0, len1 = lLines.length; i < len1; i++) {
185
- line = lLines[i];
186
- if (isEmpty(line)) {
187
- lNewLines.push('');
188
- } else {
189
- if (line.indexOf(toRemove) !== 0) {
190
- throw new Error(`remove ${OL(toRemove)} from ${OL(line)}`);
192
+ lMatches = lLines[0].match(/^\s*/);
193
+ toRemove = lMatches[0];
194
+ nToRemove = toRemove.length;
195
+ if (nToRemove > 0) {
196
+ lLines = lLines.map((line) => {
197
+ if (isEmpty(line)) {
198
+ return '';
199
+ } else {
200
+ assert(line.indexOf(toRemove) === 0, `can't remove ${OL(toRemove)} from ${OL(line)}`);
201
+ return line.substr(nToRemove);
191
202
  }
192
- lNewLines.push(line.substr(nToRemove));
193
- }
203
+ });
194
204
  }
195
205
  if (isString(input)) {
196
- return toBlock(lNewLines);
206
+ return toBlock(lLines);
197
207
  } else {
198
- return lNewLines;
208
+ return lLines;
199
209
  }
200
210
  };
201
211