@jdeighan/coffee-utils 12.0.0 → 12.0.2
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/package.json +7 -4
- package/src/DataStores.coffee +88 -2
- package/src/DataStores.js +114 -2
- package/src/GiftSet.coffee +61 -0
- package/src/GiftSet.js +80 -0
- package/src/KeyedSet.coffee +153 -0
- package/src/KeyedSet.js +187 -0
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@jdeighan/coffee-utils",
|
3
3
|
"type": "module",
|
4
|
-
"version": "12.0.
|
4
|
+
"version": "12.0.2",
|
5
5
|
"description": "A set of utility functions for CoffeeScript",
|
6
6
|
"main": "coffee_utils.js",
|
7
7
|
"exports": {
|
@@ -22,6 +22,8 @@
|
|
22
22
|
"./section": "./src/Section.js",
|
23
23
|
"./sectionmap": "./src/SectionMap.js",
|
24
24
|
"./fsa": "./src/fsa.js",
|
25
|
+
"./giftset": "./src/GiftSet.js",
|
26
|
+
"./keyedset": "./src/KeyedSet.js",
|
25
27
|
"./package.json": "./package.json"
|
26
28
|
},
|
27
29
|
"engines": {
|
@@ -48,14 +50,15 @@
|
|
48
50
|
},
|
49
51
|
"homepage": "https://github.com/johndeighan/coffee-utils#readme",
|
50
52
|
"dependencies": {
|
51
|
-
"@jdeighan/base-utils": "^1.0.
|
53
|
+
"@jdeighan/base-utils": "^1.0.11",
|
52
54
|
"cross-env": "^7.0.3",
|
55
|
+
"immer": "^9.0.16",
|
53
56
|
"js-yaml": "^4.1.0",
|
54
57
|
"n-readlines": "^1.0.1",
|
55
58
|
"readline-sync": "^1.4.10",
|
56
|
-
"svelte": "^3.
|
59
|
+
"svelte": "^3.54.0"
|
57
60
|
},
|
58
61
|
"devDependencies": {
|
59
|
-
"@jdeighan/unit-tester": "^2.0.
|
62
|
+
"@jdeighan/unit-tester": "^2.0.71"
|
60
63
|
}
|
61
64
|
}
|
package/src/DataStores.coffee
CHANGED
@@ -10,6 +10,7 @@ import {
|
|
10
10
|
withExt, slurp, barf, newerDestFileExists,
|
11
11
|
} from '@jdeighan/coffee-utils/fs'
|
12
12
|
import {fromTAML} from '@jdeighan/coffee-utils/taml'
|
13
|
+
import {createDraft, finishDraft, produce} from 'immer'
|
13
14
|
|
14
15
|
# ---------------------------------------------------------------------------
|
15
16
|
|
@@ -18,8 +19,8 @@ export class WritableDataStore
|
|
18
19
|
constructor: (value=undef) ->
|
19
20
|
@store = writable value
|
20
21
|
|
21
|
-
subscribe: (
|
22
|
-
return @store.subscribe(
|
22
|
+
subscribe: (func) ->
|
23
|
+
return @store.subscribe(func)
|
23
24
|
|
24
25
|
set: (value) ->
|
25
26
|
@store.set(value)
|
@@ -29,6 +30,91 @@ export class WritableDataStore
|
|
29
30
|
|
30
31
|
# ---------------------------------------------------------------------------
|
31
32
|
|
33
|
+
export class BaseDataStore
|
34
|
+
|
35
|
+
constructor: (@value=undef) ->
|
36
|
+
@lSubscribers = []
|
37
|
+
|
38
|
+
subscribe: (func) ->
|
39
|
+
func @value
|
40
|
+
@lSubscribers.push func
|
41
|
+
return () ->
|
42
|
+
pos = @lSubscribers.indexOf func
|
43
|
+
@lSubscribers.splice pos, 1
|
44
|
+
|
45
|
+
set: (val) ->
|
46
|
+
@value = val
|
47
|
+
@alertSubscribers()
|
48
|
+
return
|
49
|
+
|
50
|
+
update: (func) ->
|
51
|
+
@value = func(@value)
|
52
|
+
@alertSubscribers()
|
53
|
+
return
|
54
|
+
|
55
|
+
alertSubscribers: () ->
|
56
|
+
for func in @lSubscribers
|
57
|
+
func @value
|
58
|
+
return
|
59
|
+
|
60
|
+
# ---------------------------------------------------------------------------
|
61
|
+
|
62
|
+
export class ImmerDataStore extends BaseDataStore
|
63
|
+
|
64
|
+
constructor: () ->
|
65
|
+
super [] # initialize with an empty array
|
66
|
+
|
67
|
+
getNewState: () ->
|
68
|
+
|
69
|
+
return produce state, draft =>
|
70
|
+
@addGift draft, description, image
|
71
|
+
return
|
72
|
+
|
73
|
+
addGift: (draft, description, image) ->
|
74
|
+
draft.push {
|
75
|
+
id: 1
|
76
|
+
description
|
77
|
+
image
|
78
|
+
}
|
79
|
+
|
80
|
+
# ---------------------------------------------------------------------------
|
81
|
+
|
82
|
+
export class ToDoDataStore
|
83
|
+
# --- implemented with immer
|
84
|
+
|
85
|
+
constructor: () ->
|
86
|
+
@lToDos = []
|
87
|
+
@lSubscribers = []
|
88
|
+
|
89
|
+
subscribe: (func) ->
|
90
|
+
func(@lToDos)
|
91
|
+
@lSubscribers.push func
|
92
|
+
return () ->
|
93
|
+
index = @lSubscribers.indexOf func
|
94
|
+
@lSubscribers.splice index, 1
|
95
|
+
|
96
|
+
alertSubscribers: () ->
|
97
|
+
for func in @lSubscribers
|
98
|
+
func(@lToDos)
|
99
|
+
return
|
100
|
+
|
101
|
+
set: (value) ->
|
102
|
+
# --- Set new value
|
103
|
+
@alertSubscribers()
|
104
|
+
|
105
|
+
update: (func) ->
|
106
|
+
# --- Update value
|
107
|
+
@alertSubscribers()
|
108
|
+
|
109
|
+
add: (name) ->
|
110
|
+
@lToDos.push {
|
111
|
+
text: name
|
112
|
+
done: false
|
113
|
+
}
|
114
|
+
return
|
115
|
+
|
116
|
+
# ---------------------------------------------------------------------------
|
117
|
+
|
32
118
|
export class LocalStorageDataStore extends WritableDataStore
|
33
119
|
|
34
120
|
constructor: (@masterKey, defValue=undef) ->
|
package/src/DataStores.js
CHANGED
@@ -33,14 +33,20 @@ import {
|
|
33
33
|
fromTAML
|
34
34
|
} from '@jdeighan/coffee-utils/taml';
|
35
35
|
|
36
|
+
import {
|
37
|
+
createDraft,
|
38
|
+
finishDraft,
|
39
|
+
produce
|
40
|
+
} from 'immer';
|
41
|
+
|
36
42
|
// ---------------------------------------------------------------------------
|
37
43
|
export var WritableDataStore = class WritableDataStore {
|
38
44
|
constructor(value = undef) {
|
39
45
|
this.store = writable(value);
|
40
46
|
}
|
41
47
|
|
42
|
-
subscribe(
|
43
|
-
return this.store.subscribe(
|
48
|
+
subscribe(func) {
|
49
|
+
return this.store.subscribe(func);
|
44
50
|
}
|
45
51
|
|
46
52
|
set(value) {
|
@@ -53,6 +59,112 @@ export var WritableDataStore = class WritableDataStore {
|
|
53
59
|
|
54
60
|
};
|
55
61
|
|
62
|
+
// ---------------------------------------------------------------------------
|
63
|
+
export var BaseDataStore = class BaseDataStore {
|
64
|
+
constructor(value1 = undef) {
|
65
|
+
this.value = value1;
|
66
|
+
this.lSubscribers = [];
|
67
|
+
}
|
68
|
+
|
69
|
+
subscribe(func) {
|
70
|
+
func(this.value);
|
71
|
+
this.lSubscribers.push(func);
|
72
|
+
return function() {
|
73
|
+
var pos;
|
74
|
+
pos = this.lSubscribers.indexOf(func);
|
75
|
+
return this.lSubscribers.splice(pos, 1);
|
76
|
+
};
|
77
|
+
}
|
78
|
+
|
79
|
+
set(val) {
|
80
|
+
this.value = val;
|
81
|
+
this.alertSubscribers();
|
82
|
+
}
|
83
|
+
|
84
|
+
update(func) {
|
85
|
+
this.value = func(this.value);
|
86
|
+
this.alertSubscribers();
|
87
|
+
}
|
88
|
+
|
89
|
+
alertSubscribers() {
|
90
|
+
var func, i, len, ref;
|
91
|
+
ref = this.lSubscribers;
|
92
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
93
|
+
func = ref[i];
|
94
|
+
func(this.value);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
|
98
|
+
};
|
99
|
+
|
100
|
+
// ---------------------------------------------------------------------------
|
101
|
+
export var ImmerDataStore = class ImmerDataStore extends BaseDataStore {
|
102
|
+
constructor() {
|
103
|
+
super([]); // initialize with an empty array
|
104
|
+
}
|
105
|
+
|
106
|
+
getNewState() {
|
107
|
+
return produce(state, draft(() => {
|
108
|
+
return this.addGift(draft, description, image);
|
109
|
+
}));
|
110
|
+
}
|
111
|
+
|
112
|
+
addGift(draft, description, image) {
|
113
|
+
return draft.push({
|
114
|
+
id: 1,
|
115
|
+
description,
|
116
|
+
image
|
117
|
+
});
|
118
|
+
}
|
119
|
+
|
120
|
+
};
|
121
|
+
|
122
|
+
// ---------------------------------------------------------------------------
|
123
|
+
export var ToDoDataStore = class ToDoDataStore {
|
124
|
+
// --- implemented with immer
|
125
|
+
constructor() {
|
126
|
+
this.lToDos = [];
|
127
|
+
this.lSubscribers = [];
|
128
|
+
}
|
129
|
+
|
130
|
+
subscribe(func) {
|
131
|
+
func(this.lToDos);
|
132
|
+
this.lSubscribers.push(func);
|
133
|
+
return function() {
|
134
|
+
var index;
|
135
|
+
index = this.lSubscribers.indexOf(func);
|
136
|
+
return this.lSubscribers.splice(index, 1);
|
137
|
+
};
|
138
|
+
}
|
139
|
+
|
140
|
+
alertSubscribers() {
|
141
|
+
var func, i, len, ref;
|
142
|
+
ref = this.lSubscribers;
|
143
|
+
for (i = 0, len = ref.length; i < len; i++) {
|
144
|
+
func = ref[i];
|
145
|
+
func(this.lToDos);
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
set(value) {
|
150
|
+
// --- Set new value
|
151
|
+
return this.alertSubscribers();
|
152
|
+
}
|
153
|
+
|
154
|
+
update(func) {
|
155
|
+
// --- Update value
|
156
|
+
return this.alertSubscribers();
|
157
|
+
}
|
158
|
+
|
159
|
+
add(name) {
|
160
|
+
this.lToDos.push({
|
161
|
+
text: name,
|
162
|
+
done: false
|
163
|
+
});
|
164
|
+
}
|
165
|
+
|
166
|
+
};
|
167
|
+
|
56
168
|
// ---------------------------------------------------------------------------
|
57
169
|
export var LocalStorageDataStore = class LocalStorageDataStore extends WritableDataStore {
|
58
170
|
constructor(masterKey1, defValue = undef) {
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# GiftSet.coffee
|
2
|
+
|
3
|
+
import {produce, enableMapSet} from 'immer'
|
4
|
+
import {assert, croak, LOG, LOGVALUE} from '@jdeighan/base-utils'
|
5
|
+
import {
|
6
|
+
dbgEnter, dbgReturn, dbg,
|
7
|
+
} from '@jdeighan/base-utils/debug'
|
8
|
+
import {
|
9
|
+
undef, defined, notdefined, OL,
|
10
|
+
isString, isNonEmptyString, isArray, isHash, isArrayOfStrings,
|
11
|
+
isEmpty, nonEmpty,
|
12
|
+
} from '@jdeighan/coffee-utils'
|
13
|
+
|
14
|
+
enableMapSet()
|
15
|
+
|
16
|
+
# ---------------------------------------------------------------------------
|
17
|
+
|
18
|
+
export LOGMAP = (label, map) ->
|
19
|
+
|
20
|
+
lLines = ["MAP #{label}:"]
|
21
|
+
for [key, value] from map.entries()
|
22
|
+
lLines.push " #{OL(key)}: #{OL(value)}"
|
23
|
+
LOG lLines.join("\n")
|
24
|
+
LOG()
|
25
|
+
return
|
26
|
+
|
27
|
+
# ---------------------------------------------------------------------------
|
28
|
+
|
29
|
+
export addGift = produce (draft, giftName, hData={}) ->
|
30
|
+
|
31
|
+
assert (draft instanceof Map), "draft is not a Map"
|
32
|
+
if draft.get giftName
|
33
|
+
throw new Error("Gift #{giftName} already exists")
|
34
|
+
hValue = {hData...}
|
35
|
+
hValue.name = giftName
|
36
|
+
draft.set giftName, hValue
|
37
|
+
return
|
38
|
+
|
39
|
+
# ---------------------------------------------------------------------------
|
40
|
+
|
41
|
+
export reserveGift = produce (draft, giftName, user) ->
|
42
|
+
|
43
|
+
assert (draft instanceof Map), "draft is not a Map"
|
44
|
+
gift = draft.get giftName
|
45
|
+
assert gift?, "No such gift: #{giftName}"
|
46
|
+
gift.reservedBy = user
|
47
|
+
return
|
48
|
+
|
49
|
+
# ---------------------------------------------------------------------------
|
50
|
+
|
51
|
+
export cancelReservation = produce (draft, giftName) ->
|
52
|
+
|
53
|
+
assert (draft instanceof Map), "draft is not a Map"
|
54
|
+
gift = draft.get giftName
|
55
|
+
assert defined(gift), "No such gift: #{giftName}"
|
56
|
+
delete gift.reservedBy
|
57
|
+
return
|
58
|
+
|
59
|
+
# ---------------------------------------------------------------------------
|
60
|
+
|
61
|
+
|
package/src/GiftSet.js
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
// Generated by CoffeeScript 2.7.0
|
2
|
+
// GiftSet.coffee
|
3
|
+
import {
|
4
|
+
produce,
|
5
|
+
enableMapSet
|
6
|
+
} from 'immer';
|
7
|
+
|
8
|
+
import {
|
9
|
+
assert,
|
10
|
+
croak,
|
11
|
+
LOG,
|
12
|
+
LOGVALUE
|
13
|
+
} from '@jdeighan/base-utils';
|
14
|
+
|
15
|
+
import {
|
16
|
+
dbgEnter,
|
17
|
+
dbgReturn,
|
18
|
+
dbg
|
19
|
+
} from '@jdeighan/base-utils/debug';
|
20
|
+
|
21
|
+
import {
|
22
|
+
undef,
|
23
|
+
defined,
|
24
|
+
notdefined,
|
25
|
+
OL,
|
26
|
+
isString,
|
27
|
+
isNonEmptyString,
|
28
|
+
isArray,
|
29
|
+
isHash,
|
30
|
+
isArrayOfStrings,
|
31
|
+
isEmpty,
|
32
|
+
nonEmpty
|
33
|
+
} from '@jdeighan/coffee-utils';
|
34
|
+
|
35
|
+
enableMapSet();
|
36
|
+
|
37
|
+
// ---------------------------------------------------------------------------
|
38
|
+
export var LOGMAP = function(label, map) {
|
39
|
+
var key, lLines, ref, value, x;
|
40
|
+
lLines = [`MAP ${label}:`];
|
41
|
+
ref = map.entries();
|
42
|
+
for (x of ref) {
|
43
|
+
[key, value] = x;
|
44
|
+
lLines.push(` ${OL(key)}: ${OL(value)}`);
|
45
|
+
}
|
46
|
+
LOG(lLines.join("\n"));
|
47
|
+
LOG();
|
48
|
+
};
|
49
|
+
|
50
|
+
// ---------------------------------------------------------------------------
|
51
|
+
export var addGift = produce(function(draft, giftName, hData = {}) {
|
52
|
+
var hValue;
|
53
|
+
assert(draft instanceof Map, "draft is not a Map");
|
54
|
+
if (draft.get(giftName)) {
|
55
|
+
throw new Error(`Gift ${giftName} already exists`);
|
56
|
+
}
|
57
|
+
hValue = {...hData};
|
58
|
+
hValue.name = giftName;
|
59
|
+
draft.set(giftName, hValue);
|
60
|
+
});
|
61
|
+
|
62
|
+
// ---------------------------------------------------------------------------
|
63
|
+
export var reserveGift = produce(function(draft, giftName, user) {
|
64
|
+
var gift;
|
65
|
+
assert(draft instanceof Map, "draft is not a Map");
|
66
|
+
gift = draft.get(giftName);
|
67
|
+
assert(gift != null, `No such gift: ${giftName}`);
|
68
|
+
gift.reservedBy = user;
|
69
|
+
});
|
70
|
+
|
71
|
+
// ---------------------------------------------------------------------------
|
72
|
+
export var cancelReservation = produce(function(draft, giftName) {
|
73
|
+
var gift;
|
74
|
+
assert(draft instanceof Map, "draft is not a Map");
|
75
|
+
gift = draft.get(giftName);
|
76
|
+
assert(defined(gift), `No such gift: ${giftName}`);
|
77
|
+
delete gift.reservedBy;
|
78
|
+
});
|
79
|
+
|
80
|
+
// ---------------------------------------------------------------------------
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# KeyedSet.coffee
|
2
|
+
|
3
|
+
import {assert, croak, LOG, LOGVALUE} from '@jdeighan/base-utils'
|
4
|
+
import {
|
5
|
+
dbgEnter, dbgReturn, dbg,
|
6
|
+
} from '@jdeighan/base-utils/debug'
|
7
|
+
import {
|
8
|
+
undef, defined, notdefined, OL, deepCopy,
|
9
|
+
isString, isNonEmptyString, isArray, isHash, isArrayOfStrings,
|
10
|
+
isEmpty, nonEmpty,
|
11
|
+
} from '@jdeighan/coffee-utils'
|
12
|
+
|
13
|
+
# ---------------------------------------------------------------------------
|
14
|
+
|
15
|
+
export class KeyedSet extends Map
|
16
|
+
|
17
|
+
constructor: (@setName, lKeyNames, @sep='|') ->
|
18
|
+
# --- lKeyNames can be:
|
19
|
+
# 1. a non-empty string
|
20
|
+
# 2. an array of non-empty strings
|
21
|
+
|
22
|
+
dbgEnter 'KeyedSet'
|
23
|
+
super()
|
24
|
+
assert isNonEmptyString(@setName), "bad set name: #{OL(@setName)}"
|
25
|
+
if isString(lKeyNames)
|
26
|
+
assert nonEmpty(lKeyNames), "empty string key name"
|
27
|
+
@lKeyNames = [lKeyNames]
|
28
|
+
@numKeys = 1
|
29
|
+
else if isArray(lKeyNames)
|
30
|
+
assert nonEmpty(lKeyNames), "empty key name array"
|
31
|
+
for name in lKeyNames
|
32
|
+
assert isNonEmptyString(name),
|
33
|
+
"name not a non-empty string: #{OL(name)}"
|
34
|
+
@numKeys = lKeyNames.length
|
35
|
+
@lKeyNames = lKeyNames
|
36
|
+
else
|
37
|
+
croak "Invalid key names: #{OL(lKeyNames)}"
|
38
|
+
dbg "key is #{OL(@lKeyNames)}"
|
39
|
+
dbgReturn 'KeyedSet'
|
40
|
+
|
41
|
+
# ..........................................................
|
42
|
+
|
43
|
+
add: (keyVal, hData={}) ->
|
44
|
+
|
45
|
+
dbgEnter 'add', keyVal, hData
|
46
|
+
assert ! @has(keyVal), "adding duplicate key #{OL(keyVal)}"
|
47
|
+
assert isHash(hData), "hData not a hash: #{OL(hData)}"
|
48
|
+
dbg "not a duplicate"
|
49
|
+
|
50
|
+
hItem = deepCopy hData
|
51
|
+
|
52
|
+
# --- Add key values to hItem
|
53
|
+
lKeyVals = @getKeyValues keyVal
|
54
|
+
for name,i in @lKeyNames
|
55
|
+
assert notdefined(hItem[name]),
|
56
|
+
"hData has key #{name}"
|
57
|
+
hItem[name] = lKeyVals[i]
|
58
|
+
|
59
|
+
key = @getKey(keyVal)
|
60
|
+
dbg 'key', key
|
61
|
+
dbg 'value', hItem
|
62
|
+
@set key, hItem # set() is a method in Map, the base class
|
63
|
+
@length = @size # add to all methods that change size
|
64
|
+
dbgReturn 'add'
|
65
|
+
return this # allow chaining
|
66
|
+
|
67
|
+
# ..........................................................
|
68
|
+
|
69
|
+
getKey: (keyVal) ->
|
70
|
+
# --- Get the actual key used in the underlying Map object
|
71
|
+
|
72
|
+
dbgEnter 'getKey'
|
73
|
+
key = @getKeyValues(keyVal).join(@sep)
|
74
|
+
dbgReturn 'getKey', key
|
75
|
+
return key
|
76
|
+
|
77
|
+
# ..........................................................
|
78
|
+
|
79
|
+
getKeyValues: (keyVal) ->
|
80
|
+
# --- Accepts either a string or an array of strings
|
81
|
+
# But all keys must be non-empty strings
|
82
|
+
# Always returns an array
|
83
|
+
|
84
|
+
dbgEnter 'getKeyValues', keyVal
|
85
|
+
if isString(keyVal)
|
86
|
+
lKeyVals = [keyVal]
|
87
|
+
else if isArray(keyVal)
|
88
|
+
lKeyVals = keyVal
|
89
|
+
else
|
90
|
+
croak "Bad key value: #{OL(keyVal)}"
|
91
|
+
assert (lKeyVals.length == @numKeys), "Bad # keys in #{OL(keyVal)}"
|
92
|
+
for val in lKeyVals
|
93
|
+
assert isNonEmptyString(val), "Bad key val: #{OL(val)}"
|
94
|
+
dbgReturn 'getKeyValues', lKeyVals
|
95
|
+
return lKeyVals
|
96
|
+
|
97
|
+
# ..........................................................
|
98
|
+
|
99
|
+
has: (keyVal) ->
|
100
|
+
|
101
|
+
return super @getKey(keyVal)
|
102
|
+
|
103
|
+
# ..........................................................
|
104
|
+
|
105
|
+
update: (keyVal, hData={}) ->
|
106
|
+
|
107
|
+
dbgEnter 'update', keyVal, hData
|
108
|
+
key = @getKey(keyVal)
|
109
|
+
dbg "key = #{OL(key)}"
|
110
|
+
hItem = @get(key)
|
111
|
+
dbg 'hItem', hItem
|
112
|
+
assert defined(hItem), "updating missing key #{OL(keyVal)}"
|
113
|
+
for key,val of hData
|
114
|
+
hItem[key] = val
|
115
|
+
dbgReturn 'update'
|
116
|
+
return this # allow chaining
|
117
|
+
|
118
|
+
# ..........................................................
|
119
|
+
|
120
|
+
remove: (keyVal) ->
|
121
|
+
|
122
|
+
key = @getKey(keyVal)
|
123
|
+
if ! @delete key
|
124
|
+
croak "No key #{OL(keyVal)} in #{@setName}"
|
125
|
+
@length = @size # add to all methods that change size
|
126
|
+
return this # allow chaining
|
127
|
+
|
128
|
+
# ..........................................................
|
129
|
+
|
130
|
+
getAllItems: () ->
|
131
|
+
# --- Useful for unit tests, but it's usually better
|
132
|
+
# to use a generator like .entries()
|
133
|
+
|
134
|
+
return Array.from(@values())
|
135
|
+
|
136
|
+
# ..........................................................
|
137
|
+
|
138
|
+
get: (keyVal) ->
|
139
|
+
# --- Override to require that it exists
|
140
|
+
|
141
|
+
item = super @getKey(keyVal)
|
142
|
+
assert defined(item), "No such item: #{OL(keyVal)} in #{@setName}"
|
143
|
+
return item
|
144
|
+
|
145
|
+
# ..........................................................
|
146
|
+
|
147
|
+
dump: () ->
|
148
|
+
|
149
|
+
console.log "DUMP #{@setName}:"
|
150
|
+
for [key, value] from @entries()
|
151
|
+
console.log "#{OL(key)}: #{OL(value)}"
|
152
|
+
|
153
|
+
# ---------------------------------------------------------------------------
|
package/src/KeyedSet.js
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
// Generated by CoffeeScript 2.7.0
|
2
|
+
// KeyedSet.coffee
|
3
|
+
import {
|
4
|
+
assert,
|
5
|
+
croak,
|
6
|
+
LOG,
|
7
|
+
LOGVALUE
|
8
|
+
} from '@jdeighan/base-utils';
|
9
|
+
|
10
|
+
import {
|
11
|
+
dbgEnter,
|
12
|
+
dbgReturn,
|
13
|
+
dbg
|
14
|
+
} from '@jdeighan/base-utils/debug';
|
15
|
+
|
16
|
+
import {
|
17
|
+
undef,
|
18
|
+
defined,
|
19
|
+
notdefined,
|
20
|
+
OL,
|
21
|
+
deepCopy,
|
22
|
+
isString,
|
23
|
+
isNonEmptyString,
|
24
|
+
isArray,
|
25
|
+
isHash,
|
26
|
+
isArrayOfStrings,
|
27
|
+
isEmpty,
|
28
|
+
nonEmpty
|
29
|
+
} from '@jdeighan/coffee-utils';
|
30
|
+
|
31
|
+
// ---------------------------------------------------------------------------
|
32
|
+
export var KeyedSet = class KeyedSet extends Map {
|
33
|
+
constructor(setName, lKeyNames, sep = '|') {
|
34
|
+
var j, len, name;
|
35
|
+
// --- lKeyNames can be:
|
36
|
+
// 1. a non-empty string
|
37
|
+
// 2. an array of non-empty strings
|
38
|
+
dbgEnter('KeyedSet');
|
39
|
+
super();
|
40
|
+
this.setName = setName;
|
41
|
+
this.sep = sep;
|
42
|
+
assert(isNonEmptyString(this.setName), `bad set name: ${OL(this.setName)}`);
|
43
|
+
if (isString(lKeyNames)) {
|
44
|
+
assert(nonEmpty(lKeyNames), "empty string key name");
|
45
|
+
this.lKeyNames = [lKeyNames];
|
46
|
+
this.numKeys = 1;
|
47
|
+
} else if (isArray(lKeyNames)) {
|
48
|
+
assert(nonEmpty(lKeyNames), "empty key name array");
|
49
|
+
for (j = 0, len = lKeyNames.length; j < len; j++) {
|
50
|
+
name = lKeyNames[j];
|
51
|
+
assert(isNonEmptyString(name), `name not a non-empty string: ${OL(name)}`);
|
52
|
+
}
|
53
|
+
this.numKeys = lKeyNames.length;
|
54
|
+
this.lKeyNames = lKeyNames;
|
55
|
+
} else {
|
56
|
+
croak(`Invalid key names: ${OL(lKeyNames)}`);
|
57
|
+
}
|
58
|
+
dbg(`key is ${OL(this.lKeyNames)}`);
|
59
|
+
dbgReturn('KeyedSet');
|
60
|
+
}
|
61
|
+
|
62
|
+
// ..........................................................
|
63
|
+
add(keyVal, hData = {}) {
|
64
|
+
var hItem, i, j, key, lKeyVals, len, name, ref;
|
65
|
+
dbgEnter('add', keyVal, hData);
|
66
|
+
assert(!this.has(keyVal), `adding duplicate key ${OL(keyVal)}`);
|
67
|
+
assert(isHash(hData), `hData not a hash: ${OL(hData)}`);
|
68
|
+
dbg("not a duplicate");
|
69
|
+
hItem = deepCopy(hData);
|
70
|
+
// --- Add key values to hItem
|
71
|
+
lKeyVals = this.getKeyValues(keyVal);
|
72
|
+
ref = this.lKeyNames;
|
73
|
+
for (i = j = 0, len = ref.length; j < len; i = ++j) {
|
74
|
+
name = ref[i];
|
75
|
+
assert(notdefined(hItem[name]), `hData has key ${name}`);
|
76
|
+
hItem[name] = lKeyVals[i];
|
77
|
+
}
|
78
|
+
key = this.getKey(keyVal);
|
79
|
+
dbg('key', key);
|
80
|
+
dbg('value', hItem);
|
81
|
+
this.set(key, hItem); // set() is a method in Map, the base class
|
82
|
+
this.length = this.size; // add to all methods that change size
|
83
|
+
dbgReturn('add');
|
84
|
+
return this; // allow chaining
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
// ..........................................................
|
89
|
+
getKey(keyVal) {
|
90
|
+
var key;
|
91
|
+
// --- Get the actual key used in the underlying Map object
|
92
|
+
dbgEnter('getKey');
|
93
|
+
key = this.getKeyValues(keyVal).join(this.sep);
|
94
|
+
dbgReturn('getKey', key);
|
95
|
+
return key;
|
96
|
+
}
|
97
|
+
|
98
|
+
// ..........................................................
|
99
|
+
getKeyValues(keyVal) {
|
100
|
+
var j, lKeyVals, len, val;
|
101
|
+
// --- Accepts either a string or an array of strings
|
102
|
+
// But all keys must be non-empty strings
|
103
|
+
// Always returns an array
|
104
|
+
dbgEnter('getKeyValues', keyVal);
|
105
|
+
if (isString(keyVal)) {
|
106
|
+
lKeyVals = [keyVal];
|
107
|
+
} else if (isArray(keyVal)) {
|
108
|
+
lKeyVals = keyVal;
|
109
|
+
} else {
|
110
|
+
croak(`Bad key value: ${OL(keyVal)}`);
|
111
|
+
}
|
112
|
+
assert(lKeyVals.length === this.numKeys, `Bad # keys in ${OL(keyVal)}`);
|
113
|
+
for (j = 0, len = lKeyVals.length; j < len; j++) {
|
114
|
+
val = lKeyVals[j];
|
115
|
+
assert(isNonEmptyString(val), `Bad key val: ${OL(val)}`);
|
116
|
+
}
|
117
|
+
dbgReturn('getKeyValues', lKeyVals);
|
118
|
+
return lKeyVals;
|
119
|
+
}
|
120
|
+
|
121
|
+
// ..........................................................
|
122
|
+
has(keyVal) {
|
123
|
+
return super.has(this.getKey(keyVal));
|
124
|
+
}
|
125
|
+
|
126
|
+
// ..........................................................
|
127
|
+
update(keyVal, hData = {}) {
|
128
|
+
var hItem, key, val;
|
129
|
+
dbgEnter('update', keyVal, hData);
|
130
|
+
key = this.getKey(keyVal);
|
131
|
+
dbg(`key = ${OL(key)}`);
|
132
|
+
hItem = this.get(key);
|
133
|
+
dbg('hItem', hItem);
|
134
|
+
assert(defined(hItem), `updating missing key ${OL(keyVal)}`);
|
135
|
+
for (key in hData) {
|
136
|
+
val = hData[key];
|
137
|
+
hItem[key] = val;
|
138
|
+
}
|
139
|
+
dbgReturn('update');
|
140
|
+
return this; // allow chaining
|
141
|
+
}
|
142
|
+
|
143
|
+
|
144
|
+
// ..........................................................
|
145
|
+
remove(keyVal) {
|
146
|
+
var key;
|
147
|
+
key = this.getKey(keyVal);
|
148
|
+
if (!this.delete(key)) {
|
149
|
+
croak(`No key ${OL(keyVal)} in ${this.setName}`);
|
150
|
+
}
|
151
|
+
this.length = this.size; // add to all methods that change size
|
152
|
+
return this; // allow chaining
|
153
|
+
}
|
154
|
+
|
155
|
+
|
156
|
+
// ..........................................................
|
157
|
+
getAllItems() {
|
158
|
+
// --- Useful for unit tests, but it's usually better
|
159
|
+
// to use a generator like .entries()
|
160
|
+
return Array.from(this.values());
|
161
|
+
}
|
162
|
+
|
163
|
+
// ..........................................................
|
164
|
+
get(keyVal) {
|
165
|
+
var item;
|
166
|
+
// --- Override to require that it exists
|
167
|
+
item = super.get(this.getKey(keyVal));
|
168
|
+
assert(defined(item), `No such item: ${OL(keyVal)} in ${this.setName}`);
|
169
|
+
return item;
|
170
|
+
}
|
171
|
+
|
172
|
+
// ..........................................................
|
173
|
+
dump() {
|
174
|
+
var key, ref, results, value, x;
|
175
|
+
console.log(`DUMP ${this.setName}:`);
|
176
|
+
ref = this.entries();
|
177
|
+
results = [];
|
178
|
+
for (x of ref) {
|
179
|
+
[key, value] = x;
|
180
|
+
results.push(console.log(`${OL(key)}: ${OL(value)}`));
|
181
|
+
}
|
182
|
+
return results;
|
183
|
+
}
|
184
|
+
|
185
|
+
};
|
186
|
+
|
187
|
+
// ---------------------------------------------------------------------------
|