@jackens/nnn 2026.4.15 → 2026.8.31
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/nnn.d.ts +52 -89
- package/nnn.js +81 -83
- package/package.json +1 -2
- package/readme.md +554 -591
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nnn
|
|
2
2
|
|
|
3
|
-
A collection of Jackens’ JavaScript helper utilities (version: `2026.
|
|
3
|
+
A collection of Jackens’ JavaScript helper utilities (version: `2026.8.31`).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -19,6 +19,8 @@ npm i @jackens/nnn
|
|
|
19
19
|
```js
|
|
20
20
|
import {
|
|
21
21
|
c,
|
|
22
|
+
createEscape,
|
|
23
|
+
createTokenizer,
|
|
22
24
|
csvParse,
|
|
23
25
|
fixPlTypography,
|
|
24
26
|
h,
|
|
@@ -32,9 +34,6 @@ import {
|
|
|
32
34
|
jsOnParse,
|
|
33
35
|
monokai,
|
|
34
36
|
nanolightTs,
|
|
35
|
-
newEscape,
|
|
36
|
-
newNounForm,
|
|
37
|
-
newTokenizer,
|
|
38
37
|
omit,
|
|
39
38
|
pick,
|
|
40
39
|
rwd,
|
|
@@ -61,6 +60,12 @@ import {
|
|
|
61
60
|
A minimal CSS-in-JS helper
|
|
62
61
|
that converts a JavaScript object hierarchy
|
|
63
62
|
into a CSS string.
|
|
63
|
+
- [`createEscape`](#createEscape):
|
|
64
|
+
Creates a tag function
|
|
65
|
+
for escaping interpolated values in template literals.
|
|
66
|
+
- [`createTokenizer`](#createTokenizer):
|
|
67
|
+
A helper for building simple tokenizers
|
|
68
|
+
(see also [`nanolightTs`](#nanolightTs)).
|
|
64
69
|
- [`csvParse`](#csvParse):
|
|
65
70
|
Parses a CSV string
|
|
66
71
|
into a two-dimensional array of strings.
|
|
@@ -98,17 +103,7 @@ import {
|
|
|
98
103
|
and [`nanolightTs`](#nanolightTs) tokenizer.
|
|
99
104
|
- [`nanolightTs`](#nanolightTs):
|
|
100
105
|
A TypeScript/JavaScript syntax highlighting tokenizer
|
|
101
|
-
built using [`
|
|
102
|
-
- [`newEscape`](#newEscape):
|
|
103
|
-
Creates a tag function
|
|
104
|
-
for escaping interpolated values in template literals.
|
|
105
|
-
- [`newNounForm`](#newNounForm):
|
|
106
|
-
Creates a function that
|
|
107
|
-
returns the appropriate noun form
|
|
108
|
-
based on a numeric value using `Intl.PluralRules`.
|
|
109
|
-
- [`newTokenizer`](#newTokenizer):
|
|
110
|
-
A helper for building simple tokenizers
|
|
111
|
-
(see also [`nanolightTs`](#nanolightTs)).
|
|
106
|
+
built using [`createTokenizer`](#createTokenizer).
|
|
112
107
|
- [`omit`](#omit):
|
|
113
108
|
Creates a new object excluding the specified keys
|
|
114
109
|
from the source object.
|
|
@@ -215,16 +210,14 @@ A CSS string representing the compiled rules.
|
|
|
215
210
|
#### Usage Examples
|
|
216
211
|
|
|
217
212
|
```ts
|
|
218
|
-
const actual1 = c(
|
|
219
|
-
{
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
padding: 1,
|
|
225
|
-
},
|
|
213
|
+
const actual1 = c({
|
|
214
|
+
a: {
|
|
215
|
+
color: 'red',
|
|
216
|
+
margin: 1,
|
|
217
|
+
'.c': { margin: 2, padding: 2 },
|
|
218
|
+
padding: 1,
|
|
226
219
|
},
|
|
227
|
-
)
|
|
220
|
+
})
|
|
228
221
|
|
|
229
222
|
const expected1 = `
|
|
230
223
|
a{
|
|
@@ -241,18 +234,16 @@ a{
|
|
|
241
234
|
|
|
242
235
|
expect(actual1).to.equal(expected1)
|
|
243
236
|
|
|
244
|
-
const actual2 = c(
|
|
245
|
-
{
|
|
246
|
-
|
|
247
|
-
'
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
padding: 1,
|
|
252
|
-
},
|
|
237
|
+
const actual2 = c({
|
|
238
|
+
a: {
|
|
239
|
+
'.b': {
|
|
240
|
+
color: 'red',
|
|
241
|
+
margin: 1,
|
|
242
|
+
'.c': { margin: 2, padding: 2 },
|
|
243
|
+
padding: 1,
|
|
253
244
|
},
|
|
254
245
|
},
|
|
255
|
-
)
|
|
246
|
+
})
|
|
256
247
|
|
|
257
248
|
const expected2 = `
|
|
258
249
|
a.b{
|
|
@@ -269,36 +260,35 @@ a.b{
|
|
|
269
260
|
|
|
270
261
|
expect(actual2).to.equal(expected2)
|
|
271
262
|
|
|
272
|
-
const actual3 = c(
|
|
273
|
-
{
|
|
274
|
-
'
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
263
|
+
const actual3 = c({
|
|
264
|
+
'@font-face$$1': {
|
|
265
|
+
fontFamily: 'Jackens',
|
|
266
|
+
src$$1: 'url(otf/jackens.otf)',
|
|
267
|
+
src$$2:
|
|
268
|
+
"url(otf/jackens.otf) format('opentype')," +
|
|
278
269
|
"url(svg/jackens.svg) format('svg')",
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
},
|
|
282
|
-
'@font-face$$2': {
|
|
283
|
-
font_family: 'C64',
|
|
284
|
-
src: 'url(fonts/C64_Pro_Mono-STYLE.woff)',
|
|
285
|
-
},
|
|
286
|
-
'@keyframes spin': {
|
|
287
|
-
'0%': { transform: 'rotate(0deg)' },
|
|
288
|
-
'100%': { transform: 'rotate(360deg)' },
|
|
289
|
-
},
|
|
290
|
-
div: {
|
|
291
|
-
border: 'solid red 1px',
|
|
292
|
-
'.c1': { 'background-color': '#000' },
|
|
293
|
-
' .c1': { background_color: 'black' },
|
|
294
|
-
'.c2': { backgroundColor: 'rgb(0,0,0)' },
|
|
295
|
-
},
|
|
296
|
-
'@media(min-width:200px)': {
|
|
297
|
-
div: { margin: 0, padding: 0 },
|
|
298
|
-
span: { color: '#000' },
|
|
299
|
-
},
|
|
270
|
+
font_weight: 'normal',
|
|
271
|
+
'font-style': 'normal',
|
|
300
272
|
},
|
|
301
|
-
|
|
273
|
+
'@font-face$$2': {
|
|
274
|
+
font_family: 'C64',
|
|
275
|
+
src: 'url(fonts/C64_Pro_Mono-STYLE.woff)',
|
|
276
|
+
},
|
|
277
|
+
'@keyframes spin': {
|
|
278
|
+
'0%': { transform: 'rotate(0deg)' },
|
|
279
|
+
'100%': { transform: 'rotate(360deg)' },
|
|
280
|
+
},
|
|
281
|
+
div: {
|
|
282
|
+
border: 'solid red 1px',
|
|
283
|
+
'.c1': { 'background-color': '#000' },
|
|
284
|
+
' .c1': { background_color: 'black' },
|
|
285
|
+
'.c2': { backgroundColor: 'rgb(0,0,0)' },
|
|
286
|
+
},
|
|
287
|
+
'@media(min-width:200px)': {
|
|
288
|
+
div: { margin: 0, padding: 0 },
|
|
289
|
+
span: { color: '#000' },
|
|
290
|
+
},
|
|
291
|
+
})
|
|
302
292
|
|
|
303
293
|
const expected3 = `
|
|
304
294
|
@font-face{
|
|
@@ -344,18 +334,16 @@ div.c2{
|
|
|
344
334
|
|
|
345
335
|
expect(actual3).to.equal(expected3)
|
|
346
336
|
|
|
347
|
-
const actual4 = c(
|
|
348
|
-
{
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
margin: 2,
|
|
354
|
-
},
|
|
337
|
+
const actual4 = c({
|
|
338
|
+
a: {
|
|
339
|
+
'.b,.c': {
|
|
340
|
+
margin: 1,
|
|
341
|
+
'.d': {
|
|
342
|
+
margin: 2,
|
|
355
343
|
},
|
|
356
344
|
},
|
|
357
345
|
},
|
|
358
|
-
)
|
|
346
|
+
})
|
|
359
347
|
|
|
360
348
|
const expected4 = `
|
|
361
349
|
a.b,a.c{
|
|
@@ -367,16 +355,14 @@ a.b.d,a.c.d{
|
|
|
367
355
|
|
|
368
356
|
expect(actual4).to.equal(expected4)
|
|
369
357
|
|
|
370
|
-
const actual5 = c(
|
|
371
|
-
{
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
margin: 2,
|
|
376
|
-
},
|
|
358
|
+
const actual5 = c({
|
|
359
|
+
'.b,.c': {
|
|
360
|
+
margin: 1,
|
|
361
|
+
'.d': {
|
|
362
|
+
margin: 2,
|
|
377
363
|
},
|
|
378
364
|
},
|
|
379
|
-
)
|
|
365
|
+
})
|
|
380
366
|
|
|
381
367
|
const expected5 = `
|
|
382
368
|
.b,.c{
|
|
@@ -388,16 +374,14 @@ const expected5 = `
|
|
|
388
374
|
|
|
389
375
|
expect(actual5).to.equal(expected5)
|
|
390
376
|
|
|
391
|
-
const actual6 = c(
|
|
392
|
-
{
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
margin: 2,
|
|
397
|
-
},
|
|
377
|
+
const actual6 = c({
|
|
378
|
+
'.a,.b': {
|
|
379
|
+
margin: 1,
|
|
380
|
+
'.c,.d': {
|
|
381
|
+
margin: 2,
|
|
398
382
|
},
|
|
399
383
|
},
|
|
400
|
-
)
|
|
384
|
+
})
|
|
401
385
|
|
|
402
386
|
const expected6 = `
|
|
403
387
|
.a,.b{
|
|
@@ -410,6 +394,221 @@ const expected6 = `
|
|
|
410
394
|
expect(actual6).to.equal(expected6)
|
|
411
395
|
```
|
|
412
396
|
|
|
397
|
+
### createEscape
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
const createEscape: (escapeFn: (value: any) => string) => (template: TemplateStringsArray, ...values: unknown[]) => string;
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Creates a tag function
|
|
404
|
+
for escaping interpolated values in template literals.
|
|
405
|
+
|
|
406
|
+
#### escapeFn
|
|
407
|
+
|
|
408
|
+
A function that takes a value
|
|
409
|
+
and returns its escaped string representation.
|
|
410
|
+
|
|
411
|
+
#### Returns
|
|
412
|
+
|
|
413
|
+
A tag function that escapes interpolated values
|
|
414
|
+
using the provided escape map.
|
|
415
|
+
|
|
416
|
+
#### Usage Examples
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
const escapeFn = (value: any): string =>
|
|
420
|
+
isArray(value)
|
|
421
|
+
? value.map(escapeFn).join(', ')
|
|
422
|
+
: value === true || value === false
|
|
423
|
+
? `b'${+value}'`
|
|
424
|
+
: value instanceof Date
|
|
425
|
+
? `'${value
|
|
426
|
+
.toISOString()
|
|
427
|
+
.replace(/^(.+)T(.+)\..*$/, '$1 $2')}'`
|
|
428
|
+
: isFiniteNumber(value)
|
|
429
|
+
? `${value}`
|
|
430
|
+
: isString(value)
|
|
431
|
+
? `'${value.replace(/'/g, "''")}'`
|
|
432
|
+
: 'NULL'
|
|
433
|
+
|
|
434
|
+
const sql = createEscape(escapeFn)
|
|
435
|
+
|
|
436
|
+
const actual = sql`
|
|
437
|
+
SELECT *
|
|
438
|
+
FROM table_name
|
|
439
|
+
WHERE column_name IN (
|
|
440
|
+
${[
|
|
441
|
+
true,
|
|
442
|
+
null,
|
|
443
|
+
undefined,
|
|
444
|
+
NaN,
|
|
445
|
+
Infinity,
|
|
446
|
+
42,
|
|
447
|
+
'42',
|
|
448
|
+
"4'2",
|
|
449
|
+
/42/,
|
|
450
|
+
new Date(323325000000),
|
|
451
|
+
]}
|
|
452
|
+
)`
|
|
453
|
+
|
|
454
|
+
const expected = `
|
|
455
|
+
SELECT *
|
|
456
|
+
FROM table_name
|
|
457
|
+
WHERE column_name IN (
|
|
458
|
+
b'1', NULL, NULL, NULL, NULL, 42, '42', '4''2', NULL, '1980-03-31 04:30:00'
|
|
459
|
+
)`
|
|
460
|
+
|
|
461
|
+
expect(actual).to.equal(expected)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### createTokenizer
|
|
465
|
+
|
|
466
|
+
```ts
|
|
467
|
+
const createTokenizer: <M, T>(decorator: (chunk: string, metadata?: M) => T, ...specs: [M, string | RegExp][]) => (code: string) => T[];
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
A helper for building simple tokenizers
|
|
471
|
+
(see also [`nanolightTs`](#nanolightTs)).
|
|
472
|
+
|
|
473
|
+
#### decorator
|
|
474
|
+
|
|
475
|
+
A function that wraps each matched chunk.
|
|
476
|
+
It receives the matched text (`chunk`)
|
|
477
|
+
and optionally the `metadata`
|
|
478
|
+
associated with the pattern that produced the match.
|
|
479
|
+
For unmatched text between patterns,
|
|
480
|
+
`metadata` is `undefined`.
|
|
481
|
+
|
|
482
|
+
#### specs
|
|
483
|
+
|
|
484
|
+
An array of tuples `[metadata, pattern]` where:
|
|
485
|
+
- `metadata`: arbitrary data (e.g., a CSS class name)
|
|
486
|
+
passed to `decorator` when the pattern matches.
|
|
487
|
+
- `pattern`: a `string` or `RegExp`
|
|
488
|
+
to match against the input.
|
|
489
|
+
|
|
490
|
+
#### Returns
|
|
491
|
+
|
|
492
|
+
A tokenizer function that accepts a code string
|
|
493
|
+
and returns an array of decorated tokens.
|
|
494
|
+
|
|
495
|
+
#### Remarks
|
|
496
|
+
|
|
497
|
+
1. Matches starting at an earlier position
|
|
498
|
+
take precedence.
|
|
499
|
+
2. Among matches at the same position,
|
|
500
|
+
the longer one wins.
|
|
501
|
+
3. Among matches of the same position and length,
|
|
502
|
+
the one defined earlier wins.
|
|
503
|
+
|
|
504
|
+
#### Usage Examples
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
const tokenizer1 = createTokenizer(
|
|
508
|
+
(chunk, metadata) => ({ chunk, metadata }),
|
|
509
|
+
['keyword', /\b(if|else|for)\b/],
|
|
510
|
+
['string', /"[^"]*"/]
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
const result1 = tokenizer1('if "hello" else "world"')
|
|
514
|
+
|
|
515
|
+
expect(result1).to.deep.equal([
|
|
516
|
+
{ chunk: 'if', metadata: 'keyword' },
|
|
517
|
+
{ chunk: ' ', metadata: undefined },
|
|
518
|
+
{ chunk: '"hello"', metadata: 'string' },
|
|
519
|
+
{ chunk: ' ', metadata: undefined },
|
|
520
|
+
{ chunk: 'else', metadata: 'keyword' },
|
|
521
|
+
{ chunk: ' ', metadata: undefined },
|
|
522
|
+
{ chunk: '"world"', metadata: 'string' },
|
|
523
|
+
])
|
|
524
|
+
|
|
525
|
+
const tokenizer2 = createTokenizer(
|
|
526
|
+
(chunk, metadata) => `${metadata}:${chunk}`,
|
|
527
|
+
['tag', 'BEGIN'],
|
|
528
|
+
['end', 'END']
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
const result2 = tokenizer2('aBEGINbENDc')
|
|
532
|
+
|
|
533
|
+
expect(result2).to.deep.equal([
|
|
534
|
+
'undefined:a',
|
|
535
|
+
'tag:BEGIN',
|
|
536
|
+
'undefined:b',
|
|
537
|
+
'end:END',
|
|
538
|
+
'undefined:c',
|
|
539
|
+
])
|
|
540
|
+
|
|
541
|
+
const tokenizer3 = createTokenizer(
|
|
542
|
+
chunk => chunk,
|
|
543
|
+
['test', /test/]
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
const result3 = tokenizer3('')
|
|
547
|
+
|
|
548
|
+
expect(result3).to.deep.equal([])
|
|
549
|
+
|
|
550
|
+
const tokenizer4 = createTokenizer(
|
|
551
|
+
(chunk, metadata) => ({ chunk, metadata }),
|
|
552
|
+
['start', /^test/]
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
const result4 = tokenizer4('test here')
|
|
556
|
+
|
|
557
|
+
expect(result4).to.deep.equal([
|
|
558
|
+
{ chunk: 'test', metadata: 'start' },
|
|
559
|
+
{ chunk: ' here', metadata: undefined },
|
|
560
|
+
])
|
|
561
|
+
|
|
562
|
+
const tokenizer5 = createTokenizer(
|
|
563
|
+
(chunk, metadata) => metadata,
|
|
564
|
+
['later', /x/],
|
|
565
|
+
['earlier', /y/]
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
const result5 = tokenizer5('yx')
|
|
569
|
+
|
|
570
|
+
expect(result5).to.deep.equal(['earlier', 'later'])
|
|
571
|
+
|
|
572
|
+
const tokenizer6 = createTokenizer(
|
|
573
|
+
chunk => chunk,
|
|
574
|
+
['short', 'a'],
|
|
575
|
+
['long', 'abc']
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
const result6 = tokenizer6('abc')
|
|
579
|
+
|
|
580
|
+
expect(result6).to.deep.equal(['abc'])
|
|
581
|
+
|
|
582
|
+
const tokenizer7 = createTokenizer(
|
|
583
|
+
chunk => chunk,
|
|
584
|
+
['empty', ''],
|
|
585
|
+
['word', /\w+/]
|
|
586
|
+
)
|
|
587
|
+
|
|
588
|
+
const result7 = tokenizer7('hello')
|
|
589
|
+
|
|
590
|
+
expect(result7).to.deep.equal(['hello'])
|
|
591
|
+
|
|
592
|
+
const tokenizer8 = createTokenizer(
|
|
593
|
+
chunk => chunk,
|
|
594
|
+
['test', /xyz/]
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
const result8 = tokenizer8('abc')
|
|
598
|
+
|
|
599
|
+
expect(result8).to.deep.equal(['abc'])
|
|
600
|
+
|
|
601
|
+
const tokenizer9 = createTokenizer(
|
|
602
|
+
(_chunk, metadata) => metadata,
|
|
603
|
+
['a', 'a'],
|
|
604
|
+
['b', 'b']
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
const result9 = tokenizer9('aabb')
|
|
608
|
+
|
|
609
|
+
expect(result9).to.deep.equal(['a', 'a', 'b', 'b'])
|
|
610
|
+
```
|
|
611
|
+
|
|
413
612
|
### csvParse
|
|
414
613
|
|
|
415
614
|
```ts
|
|
@@ -447,13 +646,11 @@ yyy",zzz
|
|
|
447
646
|
|
|
448
647
|
`
|
|
449
648
|
|
|
450
|
-
expect(csvParse(text)).to.deep.equal(
|
|
451
|
-
[
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
],
|
|
456
|
-
)
|
|
649
|
+
expect(csvParse(text)).to.deep.equal([
|
|
650
|
+
['aaa\n"aaa"\naaa', 'bbb', 'ccc,ccc'],
|
|
651
|
+
['xxx,xxx', 'yyy\nyyy', 'zzz'],
|
|
652
|
+
[' 42 ', '42', ' 17'],
|
|
653
|
+
])
|
|
457
654
|
```
|
|
458
655
|
|
|
459
656
|
### fixPlTypography
|
|
@@ -483,23 +680,25 @@ except those inside `IFRAME`, `NOSCRIPT`, `PRE`,
|
|
|
483
680
|
#### Usage Examples
|
|
484
681
|
|
|
485
682
|
```ts
|
|
486
|
-
const p = h(
|
|
683
|
+
const p = h(
|
|
684
|
+
'p',
|
|
487
685
|
'Pchnąć w tę łódź jeża lub ośm skrzyń fig ' +
|
|
488
|
-
|
|
686
|
+
'(zob. https://pl.wikipedia.org/wiki/Pangram).',
|
|
489
687
|
['br'],
|
|
490
|
-
['b', 'Zażółć gęślą jaźń.']
|
|
688
|
+
['b', 'Zażółć gęślą jaźń.']
|
|
491
689
|
)
|
|
492
690
|
|
|
493
691
|
fixPlTypography(p)
|
|
494
692
|
|
|
495
693
|
expect(p.innerHTML).to.deep.equal(
|
|
496
694
|
'Pchnąć ' +
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
695
|
+
'<span style="white-space:nowrap">w </span>' +
|
|
696
|
+
'tę łódź jeża lub ośm skrzyń fig ' +
|
|
697
|
+
'(zob. https://\u200Bpl.\u200Bwikipedia.\u200Borg/' +
|
|
698
|
+
'\u200Bwiki/\u200BPangram).' +
|
|
699
|
+
'<br>' +
|
|
700
|
+
'<b>Zażółć gęślą jaźń.</b>'
|
|
701
|
+
)
|
|
503
702
|
```
|
|
504
703
|
|
|
505
704
|
### h
|
|
@@ -552,35 +751,43 @@ const i = h('i', 'text')
|
|
|
552
751
|
h(b, i)
|
|
553
752
|
|
|
554
753
|
expect(i.outerHTML).to.equal('<i>text</i>')
|
|
754
|
+
|
|
555
755
|
expect(b.outerHTML).to.equal('<b><i>text</i></b>')
|
|
556
756
|
|
|
557
757
|
h(i, { $className: 'some class' })
|
|
558
758
|
|
|
559
|
-
expect(i.outerHTML)
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
759
|
+
expect(i.outerHTML).to.equal(
|
|
760
|
+
'<i class="some class">text</i>'
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
expect(b.outerHTML).to.equal(
|
|
764
|
+
'<b><i class="some class">text</i></b>'
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
expect(h('span', 'text').outerHTML).to.equal(
|
|
768
|
+
'<span>text</span>'
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
expect(
|
|
772
|
+
h('span', { $innerText: 'text' }).outerHTML
|
|
773
|
+
).to.equal('<span>text</span>')
|
|
563
774
|
|
|
564
|
-
expect(h('span', '
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
.to.equal('<span>text</span>')
|
|
775
|
+
expect(h('span', '42').outerHTML).to.equal(
|
|
776
|
+
'<span>42</span>'
|
|
777
|
+
)
|
|
568
778
|
|
|
569
|
-
expect(h('span', '42').outerHTML)
|
|
570
|
-
.to.equal('<span>42</span>')
|
|
571
779
|
expect(h('span', 42).outerHTML).to.equal('<span>42</span>')
|
|
572
780
|
|
|
573
|
-
expect(h('div', { style: 'margin:0;padding:0' }).outerHTML)
|
|
574
|
-
.to.equal('<div style="margin:0;padding:0"></div>')
|
|
575
|
-
expect(h('div', { $style: 'margin:0;padding:0' }).outerHTML)
|
|
576
|
-
.to.equal(
|
|
577
|
-
'<div style="margin: 0px; padding: 0px;"></div>',
|
|
578
|
-
)
|
|
579
781
|
expect(
|
|
580
|
-
h(
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
782
|
+
h('div', { style: 'margin:0;padding:0' }).outerHTML
|
|
783
|
+
).to.equal('<div style="margin:0;padding:0"></div>')
|
|
784
|
+
|
|
785
|
+
expect(
|
|
786
|
+
h('div', { $style: 'margin:0;padding:0' }).outerHTML
|
|
787
|
+
).to.equal('<div style="margin: 0px; padding: 0px;"></div>')
|
|
788
|
+
|
|
789
|
+
expect(
|
|
790
|
+
h('div', { $style: { margin: 0, padding: 0 } }).outerHTML
|
|
584
791
|
).to.equal('<div style="margin: 0px; padding: 0px;"></div>')
|
|
585
792
|
|
|
586
793
|
const input1 = h('input', { value: 42 })
|
|
@@ -592,22 +799,26 @@ expect(input2.value).to.equal('42')
|
|
|
592
799
|
expect(input1.outerHTML).to.equal('<input value="42">')
|
|
593
800
|
expect(input2.outerHTML).to.equal('<input>')
|
|
594
801
|
|
|
595
|
-
const checkbox1 = h(
|
|
596
|
-
'
|
|
597
|
-
|
|
598
|
-
)
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
802
|
+
const checkbox1 = h('input', {
|
|
803
|
+
type: 'checkbox',
|
|
804
|
+
checked: true,
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
const checkbox2 = h('input', {
|
|
808
|
+
type: 'checkbox',
|
|
809
|
+
$checked: true,
|
|
810
|
+
})
|
|
603
811
|
|
|
604
812
|
expect(checkbox1.checked).to.be.true
|
|
605
813
|
expect(checkbox2.checked).to.be.true
|
|
606
814
|
|
|
607
|
-
expect(checkbox1.outerHTML)
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
815
|
+
expect(checkbox1.outerHTML).to.equal(
|
|
816
|
+
'<input type="checkbox" checked="">'
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
expect(checkbox2.outerHTML).to.equal(
|
|
820
|
+
'<input type="checkbox">'
|
|
821
|
+
)
|
|
611
822
|
|
|
612
823
|
const div = h('div')
|
|
613
824
|
|
|
@@ -628,15 +839,20 @@ expect(elemWithClass.getAttribute('class')).to.equal('test')
|
|
|
628
839
|
const elemWithText = h('div', 'initial')
|
|
629
840
|
|
|
630
841
|
h(elemWithText, ' more')
|
|
631
|
-
expect(elemWithText.outerHTML)
|
|
632
|
-
.to.equal('<div>initial more</div>')
|
|
633
842
|
|
|
634
|
-
|
|
843
|
+
expect(elemWithText.outerHTML).to.equal(
|
|
844
|
+
'<div>initial more</div>'
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
const elemWithNested = h(
|
|
848
|
+
'div',
|
|
635
849
|
['span', 'hello'],
|
|
636
|
-
['b', 'world']
|
|
850
|
+
['b', 'world']
|
|
851
|
+
)
|
|
637
852
|
|
|
638
|
-
expect(elemWithNested.outerHTML)
|
|
639
|
-
|
|
853
|
+
expect(elemWithNested.outerHTML).to.equal(
|
|
854
|
+
'<div><span>hello</span><b>world</b></div>'
|
|
855
|
+
)
|
|
640
856
|
```
|
|
641
857
|
|
|
642
858
|
### hasOwn
|
|
@@ -721,8 +937,9 @@ const fakeArray1 = Object.create({ constructor: Array })
|
|
|
721
937
|
|
|
722
938
|
expect(isArray(fakeArray1)).to.be.false
|
|
723
939
|
|
|
724
|
-
const fakeArray2 =
|
|
725
|
-
|
|
940
|
+
const fakeArray2 = Object.create({
|
|
941
|
+
[Symbol.toStringTag]: Array.name,
|
|
942
|
+
})
|
|
726
943
|
|
|
727
944
|
expect(isArray(fakeArray2)).to.be.false
|
|
728
945
|
```
|
|
@@ -748,9 +965,13 @@ The value to check.
|
|
|
748
965
|
|
|
749
966
|
```ts
|
|
750
967
|
expect(isFiniteNumber(42)).to.be.true
|
|
968
|
+
|
|
751
969
|
expect(isFiniteNumber(Number(42))).to.be.true
|
|
970
|
+
|
|
752
971
|
expect(isFiniteNumber(new Number(42))).to.be.false
|
|
972
|
+
|
|
753
973
|
expect(isFiniteNumber(NaN)).to.be.false
|
|
974
|
+
|
|
754
975
|
expect(isFiniteNumber(Infinity)).to.be.false
|
|
755
976
|
```
|
|
756
977
|
|
|
@@ -774,12 +995,18 @@ The value to check.
|
|
|
774
995
|
|
|
775
996
|
```ts
|
|
776
997
|
expect(isInteger(42)).to.be.true
|
|
998
|
+
|
|
777
999
|
expect(isInteger(42.00000000000001)).to.be.false
|
|
1000
|
+
|
|
778
1001
|
expect(isInteger(42.000000000000001)).to.be.true
|
|
779
1002
|
// ^because of loss of precision
|
|
1003
|
+
|
|
780
1004
|
expect(isInteger(Number(42))).to.be.true
|
|
1005
|
+
|
|
781
1006
|
expect(isInteger(new Number(42))).to.be.false
|
|
1007
|
+
|
|
782
1008
|
expect(isInteger(NaN)).to.be.false
|
|
1009
|
+
|
|
783
1010
|
expect(isInteger(Infinity)).to.be.false
|
|
784
1011
|
```
|
|
785
1012
|
|
|
@@ -804,9 +1031,13 @@ The value to check.
|
|
|
804
1031
|
|
|
805
1032
|
```ts
|
|
806
1033
|
expect(isNumber(42)).to.be.true
|
|
1034
|
+
|
|
807
1035
|
expect(isNumber(Number(42))).to.be.true
|
|
1036
|
+
|
|
808
1037
|
expect(isNumber(new Number(42))).to.be.false
|
|
1038
|
+
|
|
809
1039
|
expect(isNumber(NaN)).to.be.true
|
|
1040
|
+
|
|
810
1041
|
expect(isNumber(Infinity)).to.be.true
|
|
811
1042
|
```
|
|
812
1043
|
|
|
@@ -831,22 +1062,26 @@ The value to check.
|
|
|
831
1062
|
|
|
832
1063
|
```ts
|
|
833
1064
|
expect(isRecord({})).to.be.true
|
|
1065
|
+
|
|
834
1066
|
expect(isRecord([])).to.be.false
|
|
1067
|
+
|
|
835
1068
|
expect(isRecord(Object.create(null))).to.be.true
|
|
836
1069
|
|
|
837
1070
|
const fakeNumber1 = Object.create({ constructor: Number })
|
|
838
1071
|
|
|
839
1072
|
expect(isRecord(fakeNumber1)).to.be.true
|
|
840
1073
|
|
|
841
|
-
const fakeNumber2 =
|
|
842
|
-
|
|
1074
|
+
const fakeNumber2 = Object.create({
|
|
1075
|
+
[Symbol.toStringTag]: Number.name,
|
|
1076
|
+
})
|
|
843
1077
|
|
|
844
1078
|
expect(isRecord(fakeNumber2)).to.be.true
|
|
845
1079
|
|
|
846
1080
|
expect(isRecord(new Number(42))).to.be.true
|
|
1081
|
+
|
|
847
1082
|
expect(isRecord(new String('42'))).to.be.true
|
|
848
1083
|
|
|
849
|
-
class FooBar {
|
|
1084
|
+
class FooBar {}
|
|
850
1085
|
|
|
851
1086
|
expect(isRecord(new FooBar())).to.be.true
|
|
852
1087
|
```
|
|
@@ -911,7 +1146,9 @@ const handlers = {
|
|
|
911
1146
|
$foo: () => 'bar',
|
|
912
1147
|
}
|
|
913
1148
|
|
|
914
|
-
const actual1 = jsOnParse(
|
|
1149
|
+
const actual1 = jsOnParse(
|
|
1150
|
+
handlers,
|
|
1151
|
+
`[
|
|
915
1152
|
{
|
|
916
1153
|
"$add": [1, 2]
|
|
917
1154
|
},
|
|
@@ -933,51 +1170,50 @@ const actual1 = jsOnParse(handlers, `[
|
|
|
933
1170
|
"one": 1,
|
|
934
1171
|
"two": 2
|
|
935
1172
|
}
|
|
936
|
-
]`
|
|
1173
|
+
]`
|
|
1174
|
+
)
|
|
937
1175
|
|
|
938
|
-
expect(actual1).to.deep.equal(
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
'The parent object does not have ' +
|
|
1176
|
+
expect(actual1).to.deep.equal([
|
|
1177
|
+
3,
|
|
1178
|
+
'Hello World!',
|
|
1179
|
+
{
|
|
1180
|
+
nested: 'Hello nested World!',
|
|
1181
|
+
one: 1,
|
|
1182
|
+
two: 2,
|
|
1183
|
+
},
|
|
1184
|
+
'bar',
|
|
1185
|
+
{
|
|
1186
|
+
$foo: [
|
|
1187
|
+
'The parent object does not have ' +
|
|
951
1188
|
'exactly one property!',
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
)
|
|
1189
|
+
],
|
|
1190
|
+
one: 1,
|
|
1191
|
+
two: 2,
|
|
1192
|
+
},
|
|
1193
|
+
])
|
|
958
1194
|
|
|
959
1195
|
const actual2 = jsOnParse(
|
|
960
1196
|
{ $notFunc: 'handler not being a function' } as any,
|
|
961
|
-
'{"$notFunc": [1, 2]}'
|
|
1197
|
+
'{"$notFunc": [1, 2]}'
|
|
962
1198
|
)
|
|
963
1199
|
|
|
964
1200
|
expect(actual2).to.deep.equal({ $notFunc: [1, 2] })
|
|
965
1201
|
|
|
966
1202
|
const actual3 = jsOnParse(
|
|
967
1203
|
handlers,
|
|
968
|
-
'{"$unknown_handler_key": [1, 2]}'
|
|
1204
|
+
'{"$unknown_handler_key": [1, 2]}'
|
|
969
1205
|
)
|
|
970
1206
|
|
|
971
|
-
expect(actual3)
|
|
972
|
-
|
|
1207
|
+
expect(actual3).to.deep.equal({
|
|
1208
|
+
$unknown_handler_key: [1, 2],
|
|
1209
|
+
})
|
|
973
1210
|
|
|
974
1211
|
const actual4 = jsOnParse(
|
|
975
1212
|
handlers,
|
|
976
|
-
'{"$add": {"not": "array"}}'
|
|
1213
|
+
'{"$add": {"not": "array"}}'
|
|
977
1214
|
)
|
|
978
1215
|
|
|
979
|
-
expect(actual4)
|
|
980
|
-
.to.deep.equal({ $add: { not: 'array' } })
|
|
1216
|
+
expect(actual4).to.deep.equal({ $add: { not: 'array' } })
|
|
981
1217
|
```
|
|
982
1218
|
|
|
983
1219
|
### monokai
|
|
@@ -997,7 +1233,7 @@ const nanolightTs: (code: string) => HArgs1[];
|
|
|
997
1233
|
```
|
|
998
1234
|
|
|
999
1235
|
A TypeScript/JavaScript syntax highlighting tokenizer
|
|
1000
|
-
built using [`
|
|
1236
|
+
built using [`createTokenizer`](#createTokenizer).
|
|
1001
1237
|
|
|
1002
1238
|
#### code
|
|
1003
1239
|
|
|
@@ -1015,311 +1251,31 @@ const codeJs =
|
|
|
1015
1251
|
'const answerToLifeTheUniverseAndEverything = ' +
|
|
1016
1252
|
"{ 42: 42 }['42'] /* 42 */"
|
|
1017
1253
|
|
|
1018
|
-
expect(nanolightTs(codeJs)).to.deep.equal(
|
|
1254
|
+
expect(nanolightTs(codeJs)).to.deep.equal([
|
|
1255
|
+
['span', { class: 'keyword-1' }, 'const'],
|
|
1256
|
+
' ',
|
|
1019
1257
|
[
|
|
1020
|
-
|
|
1021
|
-
'
|
|
1022
|
-
|
|
1023
|
-
{ class: 'identifier-4' },
|
|
1024
|
-
'answerToLifeTheUniverseAndEverything',
|
|
1025
|
-
],
|
|
1026
|
-
' ',
|
|
1027
|
-
['span', { class: 'operator' }, '='],
|
|
1028
|
-
' ',
|
|
1029
|
-
['span', { class: 'punctuation' }, '{'],
|
|
1030
|
-
' ',
|
|
1031
|
-
['span', { class: 'number' }, '42'],
|
|
1032
|
-
['span', { class: 'operator' }, ':'],
|
|
1033
|
-
' ',
|
|
1034
|
-
['span', { class: 'number' }, '42'],
|
|
1035
|
-
' ',
|
|
1036
|
-
['span', { class: 'punctuation' }, '}'],
|
|
1037
|
-
['span', { class: 'punctuation' }, '['],
|
|
1038
|
-
['span', { class: 'string' }, "'42'"],
|
|
1039
|
-
['span', { class: 'punctuation' }, ']'],
|
|
1040
|
-
' ',
|
|
1041
|
-
['span', { class: 'comment' }, '/* 42 */'],
|
|
1258
|
+
'span',
|
|
1259
|
+
{ class: 'identifier-4' },
|
|
1260
|
+
'answerToLifeTheUniverseAndEverything',
|
|
1042
1261
|
],
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
#### Returns
|
|
1061
|
-
|
|
1062
|
-
A tag function that escapes interpolated values
|
|
1063
|
-
using the provided escape map.
|
|
1064
|
-
|
|
1065
|
-
#### Usage Examples
|
|
1066
|
-
|
|
1067
|
-
```ts
|
|
1068
|
-
const escapeFn = (value: any): string =>
|
|
1069
|
-
isArray(value)
|
|
1070
|
-
? value.map(escapeFn).join(', ')
|
|
1071
|
-
: value === true || value === false
|
|
1072
|
-
? `b'${+value}'`
|
|
1073
|
-
: value instanceof Date
|
|
1074
|
-
? `'${value.toISOString().replace(/^(.+)T(.+)\..*$/, '$1 $2')}'`
|
|
1075
|
-
: isFiniteNumber(value)
|
|
1076
|
-
? `${value}`
|
|
1077
|
-
: isString(value)
|
|
1078
|
-
? `'${value.replace(/'/g, "''")}'`
|
|
1079
|
-
: 'NULL'
|
|
1080
|
-
|
|
1081
|
-
const sql = newEscape(escapeFn)
|
|
1082
|
-
|
|
1083
|
-
const actual = sql`
|
|
1084
|
-
SELECT *
|
|
1085
|
-
FROM table_name
|
|
1086
|
-
WHERE column_name IN (
|
|
1087
|
-
${[
|
|
1088
|
-
true,
|
|
1089
|
-
null,
|
|
1090
|
-
undefined,
|
|
1091
|
-
NaN,
|
|
1092
|
-
Infinity,
|
|
1093
|
-
42,
|
|
1094
|
-
'42',
|
|
1095
|
-
"4'2",
|
|
1096
|
-
/42/,
|
|
1097
|
-
new Date(323325000000),
|
|
1098
|
-
]}
|
|
1099
|
-
)`
|
|
1100
|
-
|
|
1101
|
-
const expected = `
|
|
1102
|
-
SELECT *
|
|
1103
|
-
FROM table_name
|
|
1104
|
-
WHERE column_name IN (
|
|
1105
|
-
b'1', NULL, NULL, NULL, NULL, 42, '42', '4''2', NULL, '1980-03-31 04:30:00'
|
|
1106
|
-
)`
|
|
1107
|
-
|
|
1108
|
-
expect(actual).to.equal(expected)
|
|
1109
|
-
```
|
|
1110
|
-
|
|
1111
|
-
### newNounForm
|
|
1112
|
-
|
|
1113
|
-
```ts
|
|
1114
|
-
const newNounForm: (locale: string, forms: Partial<Record<Intl.LDMLPluralRule, string>>) => (value: number) => string;
|
|
1115
|
-
```
|
|
1116
|
-
|
|
1117
|
-
Creates a function that
|
|
1118
|
-
returns the appropriate noun form
|
|
1119
|
-
based on a numeric value using `Intl.PluralRules`.
|
|
1120
|
-
|
|
1121
|
-
Different languages have different plural rules.
|
|
1122
|
-
The `Intl.PluralRules` API provides locale-aware
|
|
1123
|
-
plural category selection.
|
|
1124
|
-
Possible categories are:
|
|
1125
|
-
|
|
1126
|
-
- `zero`: for zero items
|
|
1127
|
-
(used in some languages like Arabic, Latvian)
|
|
1128
|
-
- `one`: for singular (e.g., 1 item)
|
|
1129
|
-
- `two`: for dual
|
|
1130
|
-
(used in some languages like Arabic, Hebrew)
|
|
1131
|
-
- `few`: for small plurals (e.g., 2-4 in Polish)
|
|
1132
|
-
- `many`: for larger plurals (e.g., 5-21 in Polish)
|
|
1133
|
-
- `other`: fallback category (used by all languages)
|
|
1134
|
-
|
|
1135
|
-
#### locale
|
|
1136
|
-
|
|
1137
|
-
A BCP 47 language tag (e.g., `pl`, `en`).
|
|
1138
|
-
|
|
1139
|
-
#### forms
|
|
1140
|
-
|
|
1141
|
-
An object mapping plural categories to noun forms.
|
|
1142
|
-
Not all categories need to be provided;
|
|
1143
|
-
if a category is missing,
|
|
1144
|
-
the function falls back to `other`,
|
|
1145
|
-
then to an empty string.
|
|
1146
|
-
|
|
1147
|
-
#### Returns
|
|
1148
|
-
|
|
1149
|
-
A function that takes a numeric value
|
|
1150
|
-
and returns the appropriate noun form.
|
|
1151
|
-
|
|
1152
|
-
#### Usage Examples
|
|
1153
|
-
|
|
1154
|
-
```ts
|
|
1155
|
-
const auto = newNounForm(
|
|
1156
|
-
'pl',
|
|
1157
|
-
{ one: 'auto', few: 'auta', other: 'aut' },
|
|
1158
|
-
)
|
|
1159
|
-
|
|
1160
|
-
expect(auto(0)).to.equal('aut')
|
|
1161
|
-
expect(auto(1)).to.equal('auto')
|
|
1162
|
-
expect(auto(17)).to.equal('aut')
|
|
1163
|
-
expect(auto(42)).to.equal('auta')
|
|
1164
|
-
|
|
1165
|
-
const car = newNounForm('en', { one: 'car', other: 'cars' })
|
|
1166
|
-
|
|
1167
|
-
expect(car(0)).to.equal('cars')
|
|
1168
|
-
expect(car(1)).to.equal('car')
|
|
1169
|
-
expect(car(17)).to.equal('cars')
|
|
1170
|
-
expect(car(42)).to.equal('cars')
|
|
1171
|
-
|
|
1172
|
-
const empty = newNounForm('en', {})
|
|
1173
|
-
|
|
1174
|
-
expect(empty(0)).to.equal('')
|
|
1175
|
-
expect(empty(1)).to.equal('')
|
|
1176
|
-
expect(empty(17)).to.equal('')
|
|
1177
|
-
expect(empty(42)).to.equal('')
|
|
1178
|
-
```
|
|
1179
|
-
|
|
1180
|
-
### newTokenizer
|
|
1181
|
-
|
|
1182
|
-
```ts
|
|
1183
|
-
const newTokenizer: <M, T>(decorator: (chunk: string, metadata?: M) => T, ...specs: [M, string | RegExp][]) => (code: string) => T[];
|
|
1184
|
-
```
|
|
1185
|
-
|
|
1186
|
-
A helper for building simple tokenizers
|
|
1187
|
-
(see also [`nanolightTs`](#nanolightTs)).
|
|
1188
|
-
|
|
1189
|
-
#### decorator
|
|
1190
|
-
|
|
1191
|
-
A function that wraps each matched chunk.
|
|
1192
|
-
It receives the matched text (`chunk`)
|
|
1193
|
-
and optionally the `metadata`
|
|
1194
|
-
associated with the pattern that produced the match.
|
|
1195
|
-
For unmatched text between patterns,
|
|
1196
|
-
`metadata` is `undefined`.
|
|
1197
|
-
|
|
1198
|
-
#### specs
|
|
1199
|
-
|
|
1200
|
-
An array of tuples `[metadata, pattern]` where:
|
|
1201
|
-
- `metadata`: arbitrary data (e.g., a CSS class name)
|
|
1202
|
-
passed to `decorator` when the pattern matches.
|
|
1203
|
-
- `pattern`: a `string` or `RegExp`
|
|
1204
|
-
to match against the input.
|
|
1205
|
-
|
|
1206
|
-
#### Returns
|
|
1207
|
-
|
|
1208
|
-
A tokenizer function that accepts a code string
|
|
1209
|
-
and returns an array of decorated tokens.
|
|
1210
|
-
|
|
1211
|
-
#### Remarks
|
|
1212
|
-
|
|
1213
|
-
1. Matches starting at an earlier position
|
|
1214
|
-
take precedence.
|
|
1215
|
-
2. Among matches at the same position,
|
|
1216
|
-
the longer one wins.
|
|
1217
|
-
3. Among matches of the same position and length,
|
|
1218
|
-
the one defined earlier wins.
|
|
1219
|
-
|
|
1220
|
-
#### Usage Examples
|
|
1221
|
-
|
|
1222
|
-
```ts
|
|
1223
|
-
const tokenizer1 = newTokenizer(
|
|
1224
|
-
(chunk, metadata) => ({ chunk, metadata }),
|
|
1225
|
-
['keyword', /\b(if|else|for)\b/],
|
|
1226
|
-
['string', /"[^"]*"/],
|
|
1227
|
-
)
|
|
1228
|
-
const result1 = tokenizer1('if "hello" else "world"')
|
|
1229
|
-
|
|
1230
|
-
expect(result1).to.deep.equal(
|
|
1231
|
-
[
|
|
1232
|
-
{ chunk: 'if', metadata: 'keyword' },
|
|
1233
|
-
{ chunk: ' ', metadata: undefined },
|
|
1234
|
-
{ chunk: '"hello"', metadata: 'string' },
|
|
1235
|
-
{ chunk: ' ', metadata: undefined },
|
|
1236
|
-
{ chunk: 'else', metadata: 'keyword' },
|
|
1237
|
-
{ chunk: ' ', metadata: undefined },
|
|
1238
|
-
{ chunk: '"world"', metadata: 'string' },
|
|
1239
|
-
],
|
|
1240
|
-
)
|
|
1241
|
-
|
|
1242
|
-
const tokenizer2 = newTokenizer(
|
|
1243
|
-
(chunk, metadata) => `${metadata}:${chunk}`,
|
|
1244
|
-
['tag', 'BEGIN'],
|
|
1245
|
-
['end', 'END'],
|
|
1246
|
-
)
|
|
1247
|
-
const result2 = tokenizer2('aBEGINbENDc')
|
|
1248
|
-
|
|
1249
|
-
expect(result2).to.deep.equal(
|
|
1250
|
-
[
|
|
1251
|
-
'undefined:a',
|
|
1252
|
-
'tag:BEGIN',
|
|
1253
|
-
'undefined:b',
|
|
1254
|
-
'end:END',
|
|
1255
|
-
'undefined:c',
|
|
1256
|
-
],
|
|
1257
|
-
)
|
|
1258
|
-
|
|
1259
|
-
const tokenizer3 = newTokenizer(
|
|
1260
|
-
(chunk) => chunk,
|
|
1261
|
-
['test', /test/],
|
|
1262
|
-
)
|
|
1263
|
-
const result3 = tokenizer3('')
|
|
1264
|
-
|
|
1265
|
-
expect(result3).to.deep.equal([])
|
|
1266
|
-
|
|
1267
|
-
const tokenizer4 = newTokenizer(
|
|
1268
|
-
(chunk, metadata) => ({ chunk, metadata }),
|
|
1269
|
-
['start', /^test/],
|
|
1270
|
-
)
|
|
1271
|
-
const result4 = tokenizer4('test here')
|
|
1272
|
-
|
|
1273
|
-
expect(result4).to.deep.equal(
|
|
1274
|
-
[
|
|
1275
|
-
{ chunk: 'test', metadata: 'start' },
|
|
1276
|
-
{ chunk: ' here', metadata: undefined },
|
|
1277
|
-
],
|
|
1278
|
-
)
|
|
1279
|
-
|
|
1280
|
-
const tokenizer5 = newTokenizer(
|
|
1281
|
-
(chunk, metadata) => metadata,
|
|
1282
|
-
['later', /x/],
|
|
1283
|
-
['earlier', /y/],
|
|
1284
|
-
)
|
|
1285
|
-
const result5 = tokenizer5('yx')
|
|
1286
|
-
|
|
1287
|
-
expect(result5).to.deep.equal(['earlier', 'later'])
|
|
1288
|
-
|
|
1289
|
-
const tokenizer6 = newTokenizer(
|
|
1290
|
-
(chunk) => chunk,
|
|
1291
|
-
['short', 'a'],
|
|
1292
|
-
['long', 'abc'],
|
|
1293
|
-
)
|
|
1294
|
-
const result6 = tokenizer6('abc')
|
|
1295
|
-
|
|
1296
|
-
expect(result6).to.deep.equal(['abc'])
|
|
1297
|
-
|
|
1298
|
-
const tokenizer7 = newTokenizer(
|
|
1299
|
-
(chunk) => chunk,
|
|
1300
|
-
['empty', ''],
|
|
1301
|
-
['word', /\w+/],
|
|
1302
|
-
)
|
|
1303
|
-
const result7 = tokenizer7('hello')
|
|
1304
|
-
|
|
1305
|
-
expect(result7).to.deep.equal(['hello'])
|
|
1306
|
-
|
|
1307
|
-
const tokenizer8 = newTokenizer(
|
|
1308
|
-
(chunk) => chunk,
|
|
1309
|
-
['test', /xyz/],
|
|
1310
|
-
)
|
|
1311
|
-
const result8 = tokenizer8('abc')
|
|
1312
|
-
|
|
1313
|
-
expect(result8).to.deep.equal(['abc'])
|
|
1314
|
-
|
|
1315
|
-
const tokenizer9 = newTokenizer(
|
|
1316
|
-
(_chunk, metadata) => metadata,
|
|
1317
|
-
['a', 'a'],
|
|
1318
|
-
['b', 'b'],
|
|
1319
|
-
)
|
|
1320
|
-
const result9 = tokenizer9('aabb')
|
|
1321
|
-
|
|
1322
|
-
expect(result9).to.deep.equal(['a', 'a', 'b', 'b'])
|
|
1262
|
+
' ',
|
|
1263
|
+
['span', { class: 'operator' }, '='],
|
|
1264
|
+
' ',
|
|
1265
|
+
['span', { class: 'punctuation' }, '{'],
|
|
1266
|
+
' ',
|
|
1267
|
+
['span', { class: 'number' }, '42'],
|
|
1268
|
+
['span', { class: 'operator' }, ':'],
|
|
1269
|
+
' ',
|
|
1270
|
+
['span', { class: 'number' }, '42'],
|
|
1271
|
+
' ',
|
|
1272
|
+
['span', { class: 'punctuation' }, '}'],
|
|
1273
|
+
['span', { class: 'punctuation' }, '['],
|
|
1274
|
+
['span', { class: 'string' }, "'42'"],
|
|
1275
|
+
['span', { class: 'punctuation' }, ']'],
|
|
1276
|
+
' ',
|
|
1277
|
+
['span', { class: 'comment' }, '/* 42 */'],
|
|
1278
|
+
])
|
|
1323
1279
|
```
|
|
1324
1280
|
|
|
1325
1281
|
### omit
|
|
@@ -1385,9 +1341,10 @@ A new object with only the specified keys.
|
|
|
1385
1341
|
```ts
|
|
1386
1342
|
const ref = { a: 42, b: '42', c: 17 }
|
|
1387
1343
|
|
|
1388
|
-
expect(pick(ref, ['a', 'b'])).to.deep.equal(
|
|
1389
|
-
|
|
1390
|
-
|
|
1344
|
+
expect(pick(ref, ['a', 'b'])).to.deep.equal({
|
|
1345
|
+
a: 42,
|
|
1346
|
+
b: '42',
|
|
1347
|
+
})
|
|
1391
1348
|
```
|
|
1392
1349
|
|
|
1393
1350
|
### rwd
|
|
@@ -1442,36 +1399,34 @@ const style: CRoot = {
|
|
|
1442
1399
|
|
|
1443
1400
|
rwd(style, '.r6', 200, 50, [6], [3], [1, 1, 2])
|
|
1444
1401
|
|
|
1445
|
-
expect(style).to.deep.equal(
|
|
1446
|
-
{
|
|
1447
|
-
|
|
1448
|
-
|
|
1402
|
+
expect(style).to.deep.equal({
|
|
1403
|
+
body: {
|
|
1404
|
+
margin: 0,
|
|
1405
|
+
},
|
|
1406
|
+
'.r6': {
|
|
1407
|
+
border: 'solid red 1px',
|
|
1408
|
+
'.no-border': {
|
|
1409
|
+
border: 'none',
|
|
1449
1410
|
},
|
|
1411
|
+
boxSizing: 'border-box',
|
|
1412
|
+
display: 'block',
|
|
1413
|
+
float: 'left',
|
|
1414
|
+
width: '100%',
|
|
1415
|
+
height: '100px',
|
|
1416
|
+
},
|
|
1417
|
+
'@media(min-width:600px)': {
|
|
1450
1418
|
'.r6': {
|
|
1451
|
-
|
|
1452
|
-
'
|
|
1453
|
-
border: 'none',
|
|
1454
|
-
},
|
|
1455
|
-
boxSizing: 'border-box',
|
|
1456
|
-
display: 'block',
|
|
1457
|
-
float: 'left',
|
|
1458
|
-
width: '100%',
|
|
1459
|
-
height: '100px',
|
|
1460
|
-
},
|
|
1461
|
-
'@media(min-width:600px)': {
|
|
1462
|
-
'.r6': {
|
|
1463
|
-
width: 'calc(100% / 3)',
|
|
1464
|
-
height: '50px',
|
|
1465
|
-
},
|
|
1419
|
+
width: 'calc(100% / 3)',
|
|
1420
|
+
height: '50px',
|
|
1466
1421
|
},
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1422
|
+
},
|
|
1423
|
+
'@media(min-width:1200px)': {
|
|
1424
|
+
'.r6': {
|
|
1425
|
+
width: 'calc(50% / 3)',
|
|
1426
|
+
height: '50px',
|
|
1472
1427
|
},
|
|
1473
1428
|
},
|
|
1474
|
-
)
|
|
1429
|
+
})
|
|
1475
1430
|
```
|
|
1476
1431
|
|
|
1477
1432
|
### s
|
|
@@ -1525,18 +1480,19 @@ const svg2 = s('svg', { 'xlink:href': false })
|
|
|
1525
1480
|
|
|
1526
1481
|
expect(svg2.getAttributeNS(XLINK_NS, 'href')).to.be.null
|
|
1527
1482
|
|
|
1528
|
-
const svg3 = s(
|
|
1529
|
-
'
|
|
1530
|
-
|
|
1531
|
-
)
|
|
1483
|
+
const svg3 = s('svg', {
|
|
1484
|
+
'xlink:href': 'http://example.com',
|
|
1485
|
+
})
|
|
1532
1486
|
|
|
1533
|
-
expect(svg3.getAttributeNS(XLINK_NS, 'href'))
|
|
1534
|
-
|
|
1487
|
+
expect(svg3.getAttributeNS(XLINK_NS, 'href')).to.equal(
|
|
1488
|
+
'http://example.com'
|
|
1489
|
+
)
|
|
1535
1490
|
|
|
1536
1491
|
const svg4 = s('svg', { 'xlink:title': 42 })
|
|
1537
1492
|
|
|
1538
|
-
expect(svg4.getAttributeNS(XLINK_NS, 'title'))
|
|
1539
|
-
|
|
1493
|
+
expect(svg4.getAttributeNS(XLINK_NS, 'title')).to.equal(
|
|
1494
|
+
'42'
|
|
1495
|
+
)
|
|
1540
1496
|
```
|
|
1541
1497
|
|
|
1542
1498
|
### svgUse
|
|
@@ -1575,51 +1531,63 @@ expect(svg.children.length).to.equal(1)
|
|
|
1575
1531
|
|
|
1576
1532
|
const useElement = svg.children[0]
|
|
1577
1533
|
|
|
1578
|
-
expect(
|
|
1579
|
-
.
|
|
1534
|
+
expect(
|
|
1535
|
+
useElement.getAttributeNS(XLINK_NS, 'href')
|
|
1536
|
+
).to.equal('#icon-home')
|
|
1580
1537
|
|
|
1581
|
-
const svgWithViewBox =
|
|
1582
|
-
|
|
1538
|
+
const svgWithViewBox = svgUse('icon-star', {
|
|
1539
|
+
viewBox: '0 0 24 24',
|
|
1540
|
+
})
|
|
1583
1541
|
|
|
1584
|
-
expect(svgWithViewBox.getAttribute('viewBox'))
|
|
1585
|
-
|
|
1542
|
+
expect(svgWithViewBox.getAttribute('viewBox')).to.equal(
|
|
1543
|
+
'0 0 24 24'
|
|
1544
|
+
)
|
|
1586
1545
|
|
|
1587
1546
|
const useViewBox = svgWithViewBox.children[0]
|
|
1588
1547
|
|
|
1589
|
-
expect(
|
|
1590
|
-
.
|
|
1548
|
+
expect(
|
|
1549
|
+
useViewBox.getAttributeNS(XLINK_NS, 'href')
|
|
1550
|
+
).to.equal('#icon-star')
|
|
1591
1551
|
|
|
1592
|
-
const svgWithClass =
|
|
1593
|
-
|
|
1552
|
+
const svgWithClass = svgUse('icon-menu', {
|
|
1553
|
+
class: 'icon-btn',
|
|
1554
|
+
})
|
|
1594
1555
|
|
|
1595
|
-
expect(svgWithClass.getAttribute('class'))
|
|
1596
|
-
|
|
1556
|
+
expect(svgWithClass.getAttribute('class')).to.equal(
|
|
1557
|
+
'icon-btn'
|
|
1558
|
+
)
|
|
1597
1559
|
|
|
1598
1560
|
expect(svgWithClass.children.length).to.equal(1)
|
|
1599
1561
|
|
|
1600
1562
|
const useClass = svgWithClass.children[0]
|
|
1601
1563
|
|
|
1602
|
-
expect(useClass.getAttributeNS(XLINK_NS, 'href'))
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
const svgWithMultipleAttrs = svgUse(
|
|
1606
|
-
'icon-settings',
|
|
1607
|
-
{ width: 24, height: 24, class: 'icon' },
|
|
1564
|
+
expect(useClass.getAttributeNS(XLINK_NS, 'href')).to.equal(
|
|
1565
|
+
'#icon-menu'
|
|
1608
1566
|
)
|
|
1609
1567
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1568
|
+
const svgWithMultipleAttrs = svgUse('icon-settings', {
|
|
1569
|
+
width: 24,
|
|
1570
|
+
height: 24,
|
|
1571
|
+
class: 'icon',
|
|
1572
|
+
})
|
|
1573
|
+
|
|
1574
|
+
expect(svgWithMultipleAttrs.getAttribute('width')).to.equal(
|
|
1575
|
+
'24'
|
|
1576
|
+
)
|
|
1612
1577
|
|
|
1613
|
-
expect(
|
|
1614
|
-
.
|
|
1578
|
+
expect(
|
|
1579
|
+
svgWithMultipleAttrs.getAttribute('height')
|
|
1580
|
+
).to.equal('24')
|
|
1615
1581
|
|
|
1616
|
-
expect(svgWithMultipleAttrs.getAttribute('class'))
|
|
1617
|
-
|
|
1582
|
+
expect(svgWithMultipleAttrs.getAttribute('class')).to.equal(
|
|
1583
|
+
'icon'
|
|
1584
|
+
)
|
|
1618
1585
|
|
|
1619
1586
|
const useMultiple = svgWithMultipleAttrs.children[0]
|
|
1620
1587
|
|
|
1621
|
-
expect(
|
|
1622
|
-
.
|
|
1588
|
+
expect(
|
|
1589
|
+
useMultiple.getAttributeNS(XLINK_NS, 'href')
|
|
1590
|
+
).to.equal('#icon-settings')
|
|
1623
1591
|
```
|
|
1624
1592
|
|
|
1625
1593
|
### uuidV1
|
|
@@ -1683,17 +1651,19 @@ for (let i = 1; i <= 22136; ++i) {
|
|
|
1683
1651
|
}
|
|
1684
1652
|
}
|
|
1685
1653
|
|
|
1686
|
-
expect(
|
|
1687
|
-
.
|
|
1654
|
+
expect(
|
|
1655
|
+
uuidV1(new Date(), '000123456789abc').split('-')[4]
|
|
1656
|
+
).to.equal('123456789abc')
|
|
1688
1657
|
|
|
1689
|
-
expect(
|
|
1690
|
-
.
|
|
1658
|
+
expect(
|
|
1659
|
+
uuidV1(new Date(), '123456789').split('-')[4]
|
|
1660
|
+
).to.equal('000123456789')
|
|
1691
1661
|
|
|
1692
1662
|
expect(
|
|
1693
|
-
uuidV1(new Date(323325000000))
|
|
1694
|
-
|
|
1695
|
-
)
|
|
1696
|
-
|
|
1663
|
+
uuidV1(new Date(323325000000)).startsWith(
|
|
1664
|
+
'c1399400-9a71-11bd'
|
|
1665
|
+
)
|
|
1666
|
+
).to.be.true
|
|
1697
1667
|
```
|
|
1698
1668
|
|
|
1699
1669
|
### vivify
|
|
@@ -1749,58 +1719,48 @@ const ref: any = {}
|
|
|
1749
1719
|
|
|
1750
1720
|
vivify(ref).one.two[3][4]
|
|
1751
1721
|
|
|
1752
|
-
expect(ref).to.deep.equal(
|
|
1753
|
-
{
|
|
1754
|
-
|
|
1755
|
-
two: [undefined, undefined, undefined, []],
|
|
1756
|
-
},
|
|
1722
|
+
expect(ref).to.deep.equal({
|
|
1723
|
+
one: {
|
|
1724
|
+
two: [undefined, undefined, undefined, []],
|
|
1757
1725
|
},
|
|
1758
|
-
)
|
|
1726
|
+
})
|
|
1759
1727
|
|
|
1760
1728
|
vivify(ref).one.two[3][4] = 5
|
|
1761
1729
|
|
|
1762
|
-
expect(ref).to.deep.equal(
|
|
1763
|
-
{
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
],
|
|
1771
|
-
},
|
|
1730
|
+
expect(ref).to.deep.equal({
|
|
1731
|
+
one: {
|
|
1732
|
+
two: [
|
|
1733
|
+
undefined,
|
|
1734
|
+
undefined,
|
|
1735
|
+
undefined,
|
|
1736
|
+
[undefined, undefined, undefined, undefined, 5],
|
|
1737
|
+
],
|
|
1772
1738
|
},
|
|
1773
|
-
)
|
|
1739
|
+
})
|
|
1774
1740
|
|
|
1775
1741
|
vivify(ref).one.two[3].length = 1
|
|
1776
1742
|
|
|
1777
|
-
expect(ref).to.deep.equal(
|
|
1778
|
-
{
|
|
1779
|
-
|
|
1780
|
-
two: [undefined, undefined, undefined, [undefined]],
|
|
1781
|
-
},
|
|
1743
|
+
expect(ref).to.deep.equal({
|
|
1744
|
+
one: {
|
|
1745
|
+
two: [undefined, undefined, undefined, [undefined]],
|
|
1782
1746
|
},
|
|
1783
|
-
)
|
|
1747
|
+
})
|
|
1784
1748
|
|
|
1785
1749
|
vivify(ref).one.two[3] = 4
|
|
1786
1750
|
|
|
1787
|
-
expect(ref).to.deep.equal(
|
|
1788
|
-
{
|
|
1789
|
-
|
|
1790
|
-
two: [undefined, undefined, undefined, 4],
|
|
1791
|
-
},
|
|
1751
|
+
expect(ref).to.deep.equal({
|
|
1752
|
+
one: {
|
|
1753
|
+
two: [undefined, undefined, undefined, 4],
|
|
1792
1754
|
},
|
|
1793
|
-
)
|
|
1755
|
+
})
|
|
1794
1756
|
|
|
1795
1757
|
expect(vivify(ref).one.two.length).to.equal(4)
|
|
1796
1758
|
|
|
1797
|
-
expect(ref).to.deep.equal(
|
|
1798
|
-
{
|
|
1799
|
-
|
|
1800
|
-
two: [undefined, undefined, undefined, 4],
|
|
1801
|
-
},
|
|
1759
|
+
expect(ref).to.deep.equal({
|
|
1760
|
+
one: {
|
|
1761
|
+
two: [undefined, undefined, undefined, 4],
|
|
1802
1762
|
},
|
|
1803
|
-
)
|
|
1763
|
+
})
|
|
1804
1764
|
|
|
1805
1765
|
vivify(ref).one.two = 3
|
|
1806
1766
|
|
|
@@ -1814,8 +1774,8 @@ delete vivify(ref).one.two
|
|
|
1814
1774
|
|
|
1815
1775
|
expect(ref).to.deep.equal({ one: {} })
|
|
1816
1776
|
|
|
1817
|
-
expect(vivify(ref).one.toString instanceof Function)
|
|
1818
|
-
.
|
|
1777
|
+
expect(vivify(ref).one.toString instanceof Function).to.be
|
|
1778
|
+
.true
|
|
1819
1779
|
|
|
1820
1780
|
expect(vivify(ref).one.toString.name).to.equal('toString')
|
|
1821
1781
|
|
|
@@ -1835,14 +1795,17 @@ expect(ref).to.deep.equal({ one: { two: { three: {} } } })
|
|
|
1835
1795
|
|
|
1836
1796
|
vivify(ref).one.two.three.four = 5
|
|
1837
1797
|
|
|
1838
|
-
expect(ref)
|
|
1839
|
-
|
|
1798
|
+
expect(ref).to.deep.equal({
|
|
1799
|
+
one: { two: { three: { four: 5 } } },
|
|
1800
|
+
})
|
|
1840
1801
|
|
|
1841
|
-
expect(
|
|
1842
|
-
.
|
|
1802
|
+
expect(
|
|
1803
|
+
vivify(ref).one.two.three.four.toString.name
|
|
1804
|
+
).to.equal('toString')
|
|
1843
1805
|
|
|
1844
|
-
expect(ref)
|
|
1845
|
-
|
|
1806
|
+
expect(ref).to.deep.equal({
|
|
1807
|
+
one: { two: { three: { four: 5 } } },
|
|
1808
|
+
})
|
|
1846
1809
|
|
|
1847
1810
|
const u = undefined
|
|
1848
1811
|
|