@jdeighan/coffee-utils 10.0.2 → 10.0.3

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": "10.0.2",
4
+ "version": "10.0.3",
5
5
  "description": "A set of utility functions for CoffeeScript",
6
6
  "main": "coffee_utils.js",
7
7
  "exports": {
@@ -12,6 +12,7 @@
12
12
  "./log": "./src/log_utils.js",
13
13
  "./block": "./src/block_utils.js",
14
14
  "./indent": "./src/indent_utils.js",
15
+ "./html": "./src/html_utils.js",
15
16
  "./stack": "./src/call_stack.js",
16
17
  "./debug": "./src/debug_utils.js",
17
18
  "./arrow": "./src/arrow.js",
@@ -97,33 +97,41 @@ export class SectionMap
97
97
  hReplacers = desc
98
98
  desc = @lSectionTree
99
99
  else if notdefined(desc)
100
+ debug "desc is entire tree"
100
101
  desc = @lSectionTree
101
102
 
102
103
  if isArray(desc)
104
+ debug "item is an array"
103
105
  lBlocks = []
104
106
  setName = undef
105
107
  for item in desc
106
108
  subBlock = undef
107
- if isArray(item)
108
- subBlock = @getBlock(item, hReplacers)
109
- else if isSectionName(item)
110
- subBlock = @getBlock(item, hReplacers)
111
- else if isSetName(item)
109
+ if isSetName(item)
110
+ debug "set name is #{item}"
112
111
  setName = item
113
- else if isString(item)
114
- subBlock = item # a literal string
112
+ else if isSectionName(item) || isArray(item)
113
+ subBlock = @getBlock(item, hReplacers)
114
+ if defined(subBlock)
115
+ debug "add subBlock", subBlock
116
+ lBlocks.push subBlock
117
+ else
118
+ debug "subBlock is undef"
119
+ else if isString(item) && nonEmpty(item)
120
+ debug "add string", item
121
+ lBlocks.push item
115
122
  else
116
123
  croak "Bad item: #{OL(item)}"
117
- if defined(subBlock)
118
- lBlocks.push subBlock
124
+
119
125
  block = arrayToBlock(lBlocks)
126
+ debug "block is", block
120
127
  if defined(setName)
121
128
  if defined(proc = hReplacers[setName])
122
- debug "REPLACE #{setName}"
123
129
  block = proc(block)
130
+ debug "REPLACE #{setName} with", block
124
131
  else
125
132
  debug "NO REPLACER for #{setName}"
126
133
  else if isSectionName(desc)
134
+ debug "item is a section name"
127
135
  block = @section(desc).getBlock()
128
136
  if defined(proc = hReplacers[desc])
129
137
  debug "REPLACE #{desc}"
@@ -131,7 +139,7 @@ export class SectionMap
131
139
  else
132
140
  debug "NO REPLACER for #{desc}"
133
141
  else if isSetName(desc)
134
- # --- pass array to getBlock()
142
+ debug "item is a set name"
135
143
  block = @getBlock(@hSets[desc], hReplacers)
136
144
  else
137
145
  croak "Bad 1st arg: #{OL(desc)}"
package/src/SectionMap.js CHANGED
@@ -126,39 +126,46 @@ export var SectionMap = class SectionMap {
126
126
  hReplacers = desc;
127
127
  desc = this.lSectionTree;
128
128
  } else if (notdefined(desc)) {
129
+ debug("desc is entire tree");
129
130
  desc = this.lSectionTree;
130
131
  }
131
132
  if (isArray(desc)) {
133
+ debug("item is an array");
132
134
  lBlocks = [];
133
135
  setName = undef;
134
136
  for (i = 0, len = desc.length; i < len; i++) {
135
137
  item = desc[i];
136
138
  subBlock = undef;
137
- if (isArray(item)) {
138
- subBlock = this.getBlock(item, hReplacers);
139
- } else if (isSectionName(item)) {
140
- subBlock = this.getBlock(item, hReplacers);
141
- } else if (isSetName(item)) {
139
+ if (isSetName(item)) {
140
+ debug(`set name is ${item}`);
142
141
  setName = item;
143
- } else if (isString(item)) {
144
- subBlock = item; // a literal string
142
+ } else if (isSectionName(item) || isArray(item)) {
143
+ subBlock = this.getBlock(item, hReplacers);
144
+ if (defined(subBlock)) {
145
+ debug("add subBlock", subBlock);
146
+ lBlocks.push(subBlock);
147
+ } else {
148
+ debug("subBlock is undef");
149
+ }
150
+ } else if (isString(item) && nonEmpty(item)) {
151
+ debug("add string", item);
152
+ lBlocks.push(item);
145
153
  } else {
146
154
  croak(`Bad item: ${OL(item)}`);
147
155
  }
148
- if (defined(subBlock)) {
149
- lBlocks.push(subBlock);
150
- }
151
156
  }
152
157
  block = arrayToBlock(lBlocks);
158
+ debug("block is", block);
153
159
  if (defined(setName)) {
154
160
  if (defined(proc = hReplacers[setName])) {
155
- debug(`REPLACE ${setName}`);
156
161
  block = proc(block);
162
+ debug(`REPLACE ${setName} with`, block);
157
163
  } else {
158
164
  debug(`NO REPLACER for ${setName}`);
159
165
  }
160
166
  }
161
167
  } else if (isSectionName(desc)) {
168
+ debug("item is a section name");
162
169
  block = this.section(desc).getBlock();
163
170
  if (defined(proc = hReplacers[desc])) {
164
171
  debug(`REPLACE ${desc}`);
@@ -167,7 +174,7 @@ export var SectionMap = class SectionMap {
167
174
  debug(`NO REPLACER for ${desc}`);
168
175
  }
169
176
  } else if (isSetName(desc)) {
170
- // --- pass array to getBlock()
177
+ debug("item is a set name");
171
178
  block = this.getBlock(this.hSets[desc], hReplacers);
172
179
  } else {
173
180
  croak(`Bad 1st arg: ${OL(desc)}`);
@@ -0,0 +1,217 @@
1
+ # html_utils.coffee
2
+
3
+ import {assert, croak} from '@jdeighan/unit-tester/utils'
4
+ import {
5
+ undef, pass, words, isEmpty, nonEmpty,
6
+ } from '@jdeighan/coffee-utils'
7
+ import {indented, enclose} from '@jdeighan/coffee-utils/indent'
8
+ import {arrayToBlock} from '@jdeighan/coffee-utils/block'
9
+ import {debug} from '@jdeighan/coffee-utils/debug'
10
+
11
+ hNoEnd = {}
12
+ for tagName in words('area base br col command embed hr img input' \
13
+ + ' keygen link meta param source track wbr')
14
+ hNoEnd[tagName] = true
15
+
16
+ # ---------------------------------------------------------------------------
17
+
18
+ export parsetag = (line) ->
19
+
20
+ if lMatches = line.match(///^
21
+ (?:
22
+ ([A-Za-z][A-Za-z0-9_]*) # variable name
23
+ \s*
24
+ =
25
+ \s*
26
+ )? # variable is optional
27
+ ([A-Za-z][A-Za-z0-9_]*) # tag name
28
+ (?:
29
+ \:
30
+ ( [a-z]+ )
31
+ )?
32
+ (\S*) # modifiers (class names, etc.)
33
+ \s*
34
+ (.*) # attributes & enclosed text
35
+ $///)
36
+ [_, varName, tagName, subtype, modifiers, rest] = lMatches
37
+ else
38
+ croak "parsetag(): Invalid HTML: '#{line}'"
39
+
40
+ # --- Handle classes - subtypes and added via .<class>
41
+ lClasses = []
42
+ if nonEmpty(subtype) && (tagName != 'svelte')
43
+ lClasses.push subtype
44
+
45
+ if modifiers
46
+ # --- currently, these are only class names
47
+ while lMatches = modifiers.match(///^
48
+ \. ([A-Za-z][A-Za-z0-9_]*)
49
+ ///)
50
+ [all, className] = lMatches
51
+ lClasses.push className
52
+ modifiers = modifiers.substring(all.length)
53
+ if modifiers
54
+ croak "parsetag(): Invalid modifiers in '#{line}'"
55
+
56
+ # --- Handle attributes
57
+ hAttr = {} # { name: { value: <value>, quote: <quote> }, ... }
58
+
59
+ if varName
60
+ hAttr['bind:this'] = {value: varName, quote: '{'}
61
+
62
+ if (tagName == 'script') && (subtype == 'startup')
63
+ hAttr['context'] = {value: 'module', quote: '"'}
64
+
65
+ if rest
66
+ while lMatches = rest.match(///^
67
+ (?:
68
+ (?:
69
+ (?:
70
+ ( bind | on ) # prefix
71
+ :
72
+ )?
73
+ ([A-Za-z][A-Za-z0-9_]*) # attribute name
74
+ )
75
+ =
76
+ (?:
77
+ \{ ([^}]*) \} # attribute value
78
+ | " ([^"]*) "
79
+ | ' ([^']*) '
80
+ | ([^"'\s]+)
81
+ )
82
+ |
83
+ \{
84
+ ([A-Za-z][A-Za-z0-9_]*)
85
+ \}
86
+ ) \s*
87
+ ///)
88
+ [all, prefix, attrName, br_val, dq_val, sq_val, uq_val, ident] = lMatches
89
+ if ident
90
+ hAttr[ident] = { value: ident, shorthand: true }
91
+ else
92
+ if br_val
93
+ value = br_val
94
+ quote = '{'
95
+ else
96
+ assert ! prefix?, "prefix requires use of {...}"
97
+ if dq_val
98
+ value = dq_val
99
+ quote = '"'
100
+ else if sq_val
101
+ value = sq_val
102
+ quote = "'"
103
+ else
104
+ value = uq_val
105
+ quote = ''
106
+
107
+ if prefix
108
+ attrName = "#{prefix}:#{attrName}"
109
+
110
+ if attrName == 'class'
111
+ for className in value.split(/\s+/)
112
+ lClasses.push className
113
+ else
114
+ if hAttr.attrName?
115
+ croak "parsetag(): Multiple attributes named '#{attrName}'"
116
+ hAttr[attrName] = { value, quote }
117
+
118
+ rest = rest.substring(all.length)
119
+
120
+ # --- The rest is contained text
121
+ rest = rest.trim()
122
+ if lMatches = rest.match(///^
123
+ ['"]
124
+ (.*)
125
+ ['"]
126
+ $///)
127
+ rest = lMatches[1]
128
+
129
+ # --- Add class attribute to hAttr if there are classes
130
+ if (lClasses.length > 0)
131
+ hAttr.class = {
132
+ value: lClasses.join(' '),
133
+ quote: '"',
134
+ }
135
+
136
+ # --- Build the return value
137
+ hToken = {
138
+ type: 'tag'
139
+ tagName
140
+ }
141
+
142
+ if subtype
143
+ hToken.subtype = subtype
144
+ hToken.orgtag = "#{tagName}:#{subtype}"
145
+ else
146
+ hToken.orgtag = tagName
147
+
148
+ # --- if tagName == 'svelte', set hToken.tagName to hToken.orgtag
149
+ if (tagName == 'svelte')
150
+ hToken.tagName = hToken.orgtag
151
+
152
+ if nonEmpty(hAttr)
153
+ hToken.hAttr = hAttr
154
+
155
+ # --- Is there contained text?
156
+ if rest
157
+ hToken.text = rest
158
+
159
+ return hToken
160
+
161
+ # ---------------------------------------------------------------------------
162
+ # --- export only for unit testing
163
+
164
+ export attrStr = (hAttr) ->
165
+
166
+ if ! hAttr
167
+ return ''
168
+ str = ''
169
+ for attrName in Object.getOwnPropertyNames(hAttr)
170
+ {value, quote, shorthand} = hAttr[attrName]
171
+ if shorthand
172
+ str += " {#{value}}"
173
+ else
174
+ if quote == '{'
175
+ bquote = '{'
176
+ equote = '}'
177
+ else
178
+ bquote = equote = quote
179
+ str += " #{attrName}=#{bquote}#{value}#{equote}"
180
+ return str
181
+
182
+ # ---------------------------------------------------------------------------
183
+
184
+ export tag2str = (hToken, type='begin') ->
185
+
186
+ {tagName, hAttr} = hToken
187
+ if (type == 'begin')
188
+ str = "<#{tagName}" # build the string bit by bit
189
+ if nonEmpty(hAttr)
190
+ str += attrStr(hAttr)
191
+ str += '>'
192
+ return str
193
+ else if (type == 'end')
194
+ if hNoEnd[tagName]
195
+ return undef
196
+ else
197
+ return "</#{tagName}>"
198
+ else
199
+ croak "type must be 'begin' or 'end'"
200
+
201
+ # ---------------------------------------------------------------------------
202
+ # elem - indent text, surround with HTML tags
203
+
204
+ export elem = (tagName, hAttr=undef, text=undef, oneIndent="\t") ->
205
+
206
+ if isEmpty(text)
207
+ return undef
208
+ hToken = {
209
+ tagName
210
+ hAttr
211
+ text
212
+ }
213
+ return arrayToBlock([
214
+ tag2str(hToken, 'begin')
215
+ indented(text, 1, oneIndent)
216
+ tag2str(hToken, 'end')
217
+ ])
@@ -0,0 +1,222 @@
1
+ // Generated by CoffeeScript 2.7.0
2
+ // html_utils.coffee
3
+ var hNoEnd, i, len, ref, tagName;
4
+
5
+ import {
6
+ assert,
7
+ croak
8
+ } from '@jdeighan/unit-tester/utils';
9
+
10
+ import {
11
+ undef,
12
+ pass,
13
+ words,
14
+ isEmpty,
15
+ nonEmpty
16
+ } from '@jdeighan/coffee-utils';
17
+
18
+ import {
19
+ indented,
20
+ enclose
21
+ } from '@jdeighan/coffee-utils/indent';
22
+
23
+ import {
24
+ arrayToBlock
25
+ } from '@jdeighan/coffee-utils/block';
26
+
27
+ import {
28
+ debug
29
+ } from '@jdeighan/coffee-utils/debug';
30
+
31
+ hNoEnd = {};
32
+
33
+ ref = words('area base br col command embed hr img input' + ' keygen link meta param source track wbr');
34
+ for (i = 0, len = ref.length; i < len; i++) {
35
+ tagName = ref[i];
36
+ hNoEnd[tagName] = true;
37
+ }
38
+
39
+ // ---------------------------------------------------------------------------
40
+ export var parsetag = function(line) {
41
+ var _, all, attrName, br_val, className, dq_val, hAttr, hToken, ident, j, lClasses, lMatches, len1, modifiers, prefix, quote, ref1, rest, sq_val, subtype, uq_val, value, varName;
42
+ if (lMatches = line.match(/^(?:([A-Za-z][A-Za-z0-9_]*)\s*=\s*)?([A-Za-z][A-Za-z0-9_]*)(?:\:([a-z]+))?(\S*)\s*(.*)$/)) { // variable name
43
+ // variable is optional
44
+ // tag name
45
+ // modifiers (class names, etc.)
46
+ // attributes & enclosed text
47
+ [_, varName, tagName, subtype, modifiers, rest] = lMatches;
48
+ } else {
49
+ croak(`parsetag(): Invalid HTML: '${line}'`);
50
+ }
51
+ // --- Handle classes - subtypes and added via .<class>
52
+ lClasses = [];
53
+ if (nonEmpty(subtype) && (tagName !== 'svelte')) {
54
+ lClasses.push(subtype);
55
+ }
56
+ if (modifiers) {
57
+ // --- currently, these are only class names
58
+ while (lMatches = modifiers.match(/^\.([A-Za-z][A-Za-z0-9_]*)/)) {
59
+ [all, className] = lMatches;
60
+ lClasses.push(className);
61
+ modifiers = modifiers.substring(all.length);
62
+ }
63
+ if (modifiers) {
64
+ croak(`parsetag(): Invalid modifiers in '${line}'`);
65
+ }
66
+ }
67
+ // --- Handle attributes
68
+ hAttr = {}; // { name: { value: <value>, quote: <quote> }, ... }
69
+ if (varName) {
70
+ hAttr['bind:this'] = {
71
+ value: varName,
72
+ quote: '{'
73
+ };
74
+ }
75
+ if ((tagName === 'script') && (subtype === 'startup')) {
76
+ hAttr['context'] = {
77
+ value: 'module',
78
+ quote: '"'
79
+ };
80
+ }
81
+ if (rest) {
82
+ while (lMatches = rest.match(/^(?:(?:(?:(bind|on):)?([A-Za-z][A-Za-z0-9_]*))=(?:\{([^}]*)\}|"([^"]*)"|'([^']*)'|([^"'\s]+))|\{([A-Za-z][A-Za-z0-9_]*)\})\s*/)) { // prefix
83
+ // attribute name
84
+ // attribute value
85
+ [all, prefix, attrName, br_val, dq_val, sq_val, uq_val, ident] = lMatches;
86
+ if (ident) {
87
+ hAttr[ident] = {
88
+ value: ident,
89
+ shorthand: true
90
+ };
91
+ } else {
92
+ if (br_val) {
93
+ value = br_val;
94
+ quote = '{';
95
+ } else {
96
+ assert(prefix == null, "prefix requires use of {...}");
97
+ if (dq_val) {
98
+ value = dq_val;
99
+ quote = '"';
100
+ } else if (sq_val) {
101
+ value = sq_val;
102
+ quote = "'";
103
+ } else {
104
+ value = uq_val;
105
+ quote = '';
106
+ }
107
+ }
108
+ if (prefix) {
109
+ attrName = `${prefix}:${attrName}`;
110
+ }
111
+ if (attrName === 'class') {
112
+ ref1 = value.split(/\s+/);
113
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
114
+ className = ref1[j];
115
+ lClasses.push(className);
116
+ }
117
+ } else {
118
+ if (hAttr.attrName != null) {
119
+ croak(`parsetag(): Multiple attributes named '${attrName}'`);
120
+ }
121
+ hAttr[attrName] = {value, quote};
122
+ }
123
+ }
124
+ rest = rest.substring(all.length);
125
+ }
126
+ }
127
+ // --- The rest is contained text
128
+ rest = rest.trim();
129
+ if (lMatches = rest.match(/^['"](.*)['"]$/)) {
130
+ rest = lMatches[1];
131
+ }
132
+ // --- Add class attribute to hAttr if there are classes
133
+ if (lClasses.length > 0) {
134
+ hAttr.class = {
135
+ value: lClasses.join(' '),
136
+ quote: '"'
137
+ };
138
+ }
139
+ // --- Build the return value
140
+ hToken = {
141
+ type: 'tag',
142
+ tagName
143
+ };
144
+ if (subtype) {
145
+ hToken.subtype = subtype;
146
+ hToken.orgtag = `${tagName}:${subtype}`;
147
+ } else {
148
+ hToken.orgtag = tagName;
149
+ }
150
+ // --- if tagName == 'svelte', set hToken.tagName to hToken.orgtag
151
+ if (tagName === 'svelte') {
152
+ hToken.tagName = hToken.orgtag;
153
+ }
154
+ if (nonEmpty(hAttr)) {
155
+ hToken.hAttr = hAttr;
156
+ }
157
+ // --- Is there contained text?
158
+ if (rest) {
159
+ hToken.text = rest;
160
+ }
161
+ return hToken;
162
+ };
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // --- export only for unit testing
166
+ export var attrStr = function(hAttr) {
167
+ var attrName, bquote, equote, j, len1, quote, ref1, shorthand, str, value;
168
+ if (!hAttr) {
169
+ return '';
170
+ }
171
+ str = '';
172
+ ref1 = Object.getOwnPropertyNames(hAttr);
173
+ for (j = 0, len1 = ref1.length; j < len1; j++) {
174
+ attrName = ref1[j];
175
+ ({value, quote, shorthand} = hAttr[attrName]);
176
+ if (shorthand) {
177
+ str += ` {${value}}`;
178
+ } else {
179
+ if (quote === '{') {
180
+ bquote = '{';
181
+ equote = '}';
182
+ } else {
183
+ bquote = equote = quote;
184
+ }
185
+ str += ` ${attrName}=${bquote}${value}${equote}`;
186
+ }
187
+ }
188
+ return str;
189
+ };
190
+
191
+ // ---------------------------------------------------------------------------
192
+ export var tag2str = function(hToken, type = 'begin') {
193
+ var hAttr, str;
194
+ ({tagName, hAttr} = hToken);
195
+ if (type === 'begin') {
196
+ str = `<${tagName}`;
197
+ if (nonEmpty(hAttr)) {
198
+ str += attrStr(hAttr);
199
+ }
200
+ str += '>';
201
+ return str;
202
+ } else if (type === 'end') {
203
+ if (hNoEnd[tagName]) {
204
+ return undef;
205
+ } else {
206
+ return `</${tagName}>`;
207
+ }
208
+ } else {
209
+ return croak("type must be 'begin' or 'end'");
210
+ }
211
+ };
212
+
213
+ // ---------------------------------------------------------------------------
214
+ // elem - indent text, surround with HTML tags
215
+ export var elem = function(tagName, hAttr = undef, text = undef, oneIndent = "\t") {
216
+ var hToken;
217
+ if (isEmpty(text)) {
218
+ return undef;
219
+ }
220
+ hToken = {tagName, hAttr, text};
221
+ return arrayToBlock([tag2str(hToken, 'begin'), indented(text, 1, oneIndent), tag2str(hToken, 'end')]);
222
+ };
@@ -164,14 +164,3 @@ export enclose = (text, pre, post, oneIndent="\t") ->
164
164
  indented(text, 1, oneIndent)
165
165
  post
166
166
  ])
167
-
168
- # ---------------------------------------------------------------------------
169
- # elem - indent text, surround with HTML tags
170
-
171
- export elem = (text, tag, oneIndent="\t") ->
172
-
173
- return arrayToBlock([
174
- "<#{tag}>"
175
- indented(text, 1, oneIndent)
176
- "</#{tag}>"
177
- ])
@@ -187,9 +187,3 @@ export var untabify = function(str, numSpaces = 3) {
187
187
  export var enclose = function(text, pre, post, oneIndent = "\t") {
188
188
  return arrayToBlock([pre, indented(text, 1, oneIndent), post]);
189
189
  };
190
-
191
- // ---------------------------------------------------------------------------
192
- // elem - indent text, surround with HTML tags
193
- export var elem = function(text, tag, oneIndent = "\t") {
194
- return arrayToBlock([`<${tag}>`, indented(text, 1, oneIndent), `</${tag}>`]);
195
- };