@lumjs/core 1.0.0-beta.2 → 1.0.0

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.
@@ -0,0 +1,98 @@
1
+ // Get the extended type list.
2
+ const TYPES = require('./typelist');
3
+ const {isObj, isArray, isTypedArray} = require('./basics');
4
+
5
+ const TOSTRING = [TYPES.F, TYPES.SY];
6
+
7
+ /**
8
+ * Stringify a Javascript value.
9
+ *
10
+ * We typically just use `JSON.stringify()` but that doesn't work on
11
+ * some types. So this function adds string formats for:
12
+ * - `function`
13
+ * - `symbol`
14
+ * - `TypedArray`
15
+ * - `Map`
16
+ * - `Set`
17
+ * - `Error`
18
+ * I may add even more extended types in the future, but that's enough
19
+ * for now.
20
+ *
21
+ * This is NOT meant for serializing data, and does not use a JSON-friendly
22
+ * output format. I'm writing a different library for that.
23
+ *
24
+ * @param {*} what - The value to stringify.
25
+ * @param {integer} [recurse=1] Recurse objects to this depth.
26
+ * @param {boolean} [addNew=false] Use 'new Class()' instead of 'Class()'.
27
+ * @returns {string} The stringified value.
28
+ * @alias module:@lumjs/core/types.stringify
29
+ */
30
+ function stringify (what, recurse=1, addNew=false)
31
+ {
32
+ const whatType = typeof what;
33
+ if (TOSTRING.includes(whatType)) return what.toString();
34
+
35
+ // A few formatting helpers used below.
36
+
37
+ const classname = () => (addNew ? 'new ' : '') + what.constructor.name;
38
+ const construct = val => `${classname()}(${val})`;
39
+ const reconstruct = val => construct(stringify(val,recurse,addNew));
40
+ const arrayish = vals => reconstruct(Array.from(vals));
41
+
42
+ if (isTypedArray(what))
43
+ { // This one is pretty simple.
44
+ return construct(what.toString());
45
+ }
46
+
47
+ if (what instanceof Map)
48
+ {
49
+ return arrayish(what.entries());
50
+ }
51
+
52
+ if (what instanceof Set)
53
+ {
54
+ return arrayish(what.values());
55
+ }
56
+
57
+ if (what instanceof Error)
58
+ {
59
+ return construct(what.message);
60
+ }
61
+
62
+ if (recurse && isObj(what))
63
+ { // Recursion mode enabled.
64
+ let out = '';
65
+ if (isArray(what))
66
+ { // Stringify an array.
67
+ out = '[';
68
+ out += what.map(item => stringify(item, recurse-1, addNew)).join(',');
69
+ out += ']';
70
+ }
71
+ else
72
+ { // Stringify a plain object.
73
+ out = '{';
74
+ function add(key, pre='')
75
+ {
76
+ out += `${pre}${key}:${stringify(what[key], recurse-1, addNew)}`
77
+ }
78
+ const keys = Object.keys(what);
79
+ //console.debug("keys!", keys);
80
+ if (keys.length > 0)
81
+ { // Let's add the first key, then all subsequent keys.
82
+ add(keys.shift());
83
+ for (const key of keys)
84
+ {
85
+ add(key, ',');
86
+ }
87
+ }
88
+ out += '}';
89
+ }
90
+ return out;
91
+ }
92
+ else
93
+ { // If we reached here, there's no special methods, use JSON.
94
+ return JSON.stringify(what);
95
+ }
96
+ }
97
+
98
+ module.exports = stringify;
@@ -0,0 +1,168 @@
1
+ const def = require('./def');
2
+ const {O, F, S, B, N, U, SY, BI} = require('./js');
3
+ const
4
+ {
5
+ isObj, isComplex, isNil, notNil, isScalar, isArray, isTypedArray,
6
+ isArguments, isProperty, doesDescriptor,
7
+ } = require('./basics');
8
+
9
+ /**
10
+ * A map of **Types**, including *special* and *union* types.
11
+ *
12
+ * Contains the same `O, F, S, B, N, U, SY, BI` properties as also
13
+ * found in the top-level {@link module:@lumjs/core/types} module.
14
+ * While most of the core JS types simply use `typeof` as their
15
+ * test, this maps the `O` (`object`) type to the `isObj` test.
16
+ *
17
+ * Will also contain a few helper functions, and a map of tests
18
+ * that are used by `isType`, `isa`, `needType`, and `needs`.
19
+ * Any one of these properties may be passed to those functions as
20
+ * the desired *type* a desired value must be.
21
+ *
22
+ * We will list the types added by the `types` module in
23
+ * the *Properties* table, and any types added by other *core modules*
24
+ * in the *Members* list, prior to listing the *Methods*.
25
+ *
26
+ * @namespace module:@lumjs/core/types.TYPES
27
+ * @property {string} NULL - Represents `null` values.
28
+ * @property {string} ARGS - Represents an *argument* object.
29
+ * @property {string} PROP - A `string` or a `symbol`.
30
+ * @property {string} ARRAY - An `Array` object.
31
+ * @property {string} TYPEDARRAY - A `TypedArray` object.
32
+ * @property {string} DESCRIPTOR - A *Descriptor* object (Data or Accessor).
33
+ * @property {string} COMPLEX - A `function` or an `object`.
34
+ * @property {string} SCALAR - A non-null value that is **not** *complex*.
35
+ * @property {string} NIL - Either `null` or `undefined`.
36
+ * @property {string} NOTNIL - Anything other than `null` or `undefined`.
37
+ * @property {string} MAP - A `Map` object.
38
+ * @property {object} tests - A map of tests for the above types.
39
+ */
40
+ const TYPES = {};
41
+
42
+ /**
43
+ * Add a new type to the `TYPES`.
44
+ * @name module:@lumjs/core/types.TYPES.add
45
+ * @function
46
+ * @param {(string|object)} name - If a `string` the property name to use.
47
+ * When adding the property the string will be forced to uppercase.
48
+ *
49
+ * If an `object` then its a shortcut for adding a bunch of types at once.
50
+ * Each key will be the `name`, and the type of the *value* can be one of:
51
+ * - `string` → Use as the `ident` parameter.
52
+ * - `function` → Use as the `test` parameter.
53
+ * - `object` → Supports `id`, `test`, and `export` parameters.
54
+ *
55
+ * @param {?string} [ident] The identifier string for the type.
56
+ * If not specified or `null`, it will default to a completely lowercase
57
+ * version of the `name` parameter.
58
+ * @param {function} [test] A type check test.
59
+ * Must accept a single value to test, must return a boolean.
60
+ * @param {string} [exportTest] A name to export the test as.
61
+ * The test will be added to the `types` module with this name.
62
+ *
63
+ * @returns {void}
64
+ */
65
+
66
+ /**
67
+ * Get a list of type properties available in `TYPES`.
68
+ * @name module:@lumjs/core/types.TYPES.keys
69
+ * @function
70
+ * @returns {string[]}
71
+ */
72
+
73
+ /**
74
+ * Get a list of type identifier values available in `TYPES`.
75
+ * @name module:@lumjs/core/types.TYPES.list
76
+ * @function
77
+ * @returns {string[]}
78
+ */
79
+
80
+ // Let's setup the TYPES with its magic functions.
81
+ const dt = def(TYPES);
82
+ dt('tests', {})
83
+ ('keys',
84
+ {
85
+ get()
86
+ {
87
+ return Object.keys(this);
88
+ }
89
+ })
90
+ ('list',
91
+ {
92
+ get()
93
+ {
94
+ return Object.values(this);
95
+ }
96
+ })
97
+ ('add', function(name, ident, test, exportTest)
98
+ {
99
+ if (isObj(name))
100
+ { // A shortcut for adding multiple items.
101
+ for (let key in name)
102
+ {
103
+ const val = name[key];
104
+ if (typeof val === S)
105
+ { // It's a 'name': 'ident' mapping.
106
+ TYPES.add(key, val);
107
+ }
108
+ else if (typeof val === F)
109
+ { // It's a 'name': test() mapping.
110
+ TYPES.add(key, null, val);
111
+ }
112
+ else if (isObj(val))
113
+ { // An object, can have 'id', 'test', and 'export' properties.
114
+ TYPES.add(key, val.id, val.test, val.export);
115
+ }
116
+ else
117
+ { // Unsupported.
118
+ throw new TypeError("Invalid add type property");
119
+ }
120
+ }
121
+ }
122
+ else if (typeof name === S)
123
+ {
124
+ if (typeof ident !== S)
125
+ { // Use a lowercase version of the name.
126
+ ident = name.toLowerCase();
127
+ }
128
+ // And just to be sure.
129
+ name = name.toUpperCase();
130
+ dt(name, {enumerable: true, value: ident});
131
+ if (typeof test === F)
132
+ { // A custom test, mapped to the ident.
133
+ def(TYPES.tests, ident, test);
134
+ if (typeof exportTest === S && isObj(TYPES.$module))
135
+ { // So extensions can add new type tests.
136
+ TYPES.$module.exports[exportTest] = test;
137
+ }
138
+ }
139
+ }
140
+ else
141
+ {
142
+ throw new TypeError("Invalid arguments");
143
+ }
144
+ });
145
+
146
+ // Now we'll actually set up the built-in TYPES.
147
+ TYPES.add(
148
+ {
149
+ // First the simplest ones that just use `typeof`.
150
+ F, S, B, N, U, SY, BI,
151
+ // Next the custom object test, overriding `typeof`.
152
+ O: {id: O, test: isObj},
153
+ // Custom type defs that never had typeof tests.
154
+ NULL: v => (v === null),
155
+ ARGS: {id: 'arguments', test: isArguments},
156
+ PROP: {id: 'property', test: isProperty},
157
+ ARRAY: isArray,
158
+ TYPEDARRAY: isTypedArray,
159
+ DESCRIPTOR: doesDescriptor,
160
+ COMPLEX: isComplex,
161
+ SCALAR: isScalar,
162
+ NIL: isNil,
163
+ NOTNIL: notNil,
164
+ MAP: v => v instanceof Map,
165
+ });
166
+
167
+ module.exports = TYPES;
168
+
package/package.json CHANGED
@@ -1,7 +1,23 @@
1
1
  {
2
2
  "name": "@lumjs/core",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0",
4
4
  "main": "lib/index.js",
5
+ "exports":
6
+ {
7
+ ".": "./lib/index.js",
8
+ "./types": "./lib/types/index.js",
9
+ "./arrays": "./lib/arrays.js",
10
+ "./context": "./lib/context.js",
11
+ "./strings": "./lib/strings.js",
12
+ "./flags": "./lib/flags.js",
13
+ "./obj": "./lib/obj/index.js",
14
+ "./opt": "./lib/opt.js",
15
+ "./modules": "./lib/moduless.js",
16
+ "./meta": "./lib/meta.js",
17
+ "./enum": "./lib/enum.js",
18
+ "./observable": "./lib/observable.js",
19
+ "./package.json": "./package.json"
20
+ },
5
21
  "license": "MIT",
6
22
  "repository":
7
23
  {
@@ -10,6 +26,13 @@
10
26
  },
11
27
  "devDependencies":
12
28
  {
13
- "@lumjs/tests": "^1.0.0"
29
+ "@lumjs/tests": "^1.3.0",
30
+ "jsdoc": "^3.6.10"
31
+ },
32
+ "scripts":
33
+ {
34
+ "-TODO-1": "Use `lumtest` once its available",
35
+ "test": "prove -e node --ext js ./test",
36
+ "build-docs": "jsdoc -c ./jsdoc.json"
14
37
  }
15
38
  }
package/test/arrays.js ADDED
@@ -0,0 +1,19 @@
1
+ // Current test count.
2
+ const plan = 5;
3
+ // A new test instance.
4
+ const t = require('@lumjs/tests').new({module, plan});
5
+ // The arrays core module
6
+ const arr = require('../lib/arrays');
7
+
8
+ t.ok(arr.containsAny(['hello','world'], 'world'), 'containsAny([a], a)');
9
+ t.ok(!arr.containsAny(['hello','world'], 'universe'), '!containsAny([a], b)');
10
+ t.ok(arr.containsAll(['hello','darkness','my','old','friend'], 'hello', 'friend'), 'containsAll([a,b,c], a, c)');
11
+ t.ok(!arr.containsAll(['nothing','to','see'], 'nothing', 'here'), '!containsAll([a,b,c], a, d)');
12
+
13
+ const a1 = ['hello', 'darkness', 'my', 'old', 'friend'];
14
+ arr.removeItems(a1, 'darkness');
15
+ t.isJSON(a1, ['hello','my','old','friend'], 'removeFromArray(...)');
16
+
17
+ // All done.
18
+ t.output();
19
+
package/test/meta.js ADDED
@@ -0,0 +1,17 @@
1
+ // Current test count.
2
+ const plan = 1;
3
+ // A new test instance.
4
+ const t = require('@lumjs/tests').new({module, plan});
5
+ // The meta core module
6
+ const meta = require('../lib/meta');
7
+
8
+ t.dies(()=>meta.NYI(), 'NYI()');
9
+
10
+ // TODO:
11
+ // - stacktrace()
12
+ // - AbstractClass
13
+ // - Functions.*
14
+
15
+ // All done.
16
+ t.output();
17
+
package/test/types.js CHANGED
@@ -1,20 +1,14 @@
1
1
  // Current test count.
2
- const plan = 91;
2
+ const plan = 120;
3
3
  // A new test instance.
4
4
  const t = require('@lumjs/tests').new({module, plan});
5
5
  // The types core module
6
6
  const types = require('../lib/types');
7
7
 
8
- // And a quick reference to the type names.
8
+ // A quick reference to the type names.
9
9
  const TYP = types.TYPES;
10
-
11
- // Helper function.
12
- function stringify (what)
13
- {
14
- if (typeof what === TYP.F) return what.toString();
15
- if (typeof what === TYP.SY) return what.constructor.toString();
16
- return JSON.stringify(what);
17
- }
10
+ // And the stringify function.
11
+ const stringify = types.stringify;
18
12
 
19
13
  // Now for some further basics.
20
14
  t.ok(types.isObj({}), 'isObj({})');
@@ -55,10 +49,16 @@ class SubtypeClass extends TypeClass {}
55
49
  const typesInstance = new TypeClass();
56
50
  const subtypeInstance = new SubtypeClass();
57
51
 
52
+ class DifferentClass {}
53
+
54
+ const differentInstance = new DifferentClass();
55
+
58
56
  t.ok(types.isInstance(typesInstance, TypeClass), 'isInstance(typeInstance,TypeClass)');
59
57
  t.ok(types.isInstance(subtypeInstance, SubtypeClass), 'isInstance(subtypeInstance, SubtypeClass)');
60
58
  t.ok(types.isInstance(subtypeInstance, TypeClass), 'isInstance(subtypeInstance, TypeClass)');
61
- t.ok(!types.isInstance(typesInstance, SubtypeClass), '!isInstance(typeInstance, SubtypeClass');
59
+ t.ok(!types.isInstance(typesInstance, SubtypeClass), '!isInstance(typeInstance, SubtypeClass)');
60
+ t.ok(!types.isInstance(differentInstance, TypeClass), '!isInstance(differentInstance, TypeClass)');
61
+ t.ok(!types.isInstance(typesInstance, DifferentClass), '!isInstance(typesInstance, DifferentClass)');
62
62
 
63
63
  function doesDesc (tests, not=false)
64
64
  {
@@ -126,7 +126,7 @@ testIsType(
126
126
  [TYP.SCALAR, true],
127
127
  [TYP.SCALAR, 'hi'],
128
128
  [TYP.PROP, 'woah'],
129
- [TYP.PROP, Symbol('woah'), TYP.PROP+',Symbol'],
129
+ [TYP.PROP, Symbol('woah')],
130
130
  ]);
131
131
 
132
132
  testIsType(
@@ -151,15 +151,6 @@ testIsType(
151
151
  [TYP.PROP, null],
152
152
  ], true);
153
153
 
154
- t.ok(types.containsAny(['hello','world'], 'world'), 'containsAny([a], a)');
155
- t.ok(!types.containsAny(['hello','world'], 'universe'), '!containsAny([a], b)');
156
- t.ok(types.containsAll(['hello','darkness','my','old','friend'], 'hello', 'friend'), 'containsAll([a,b,c], a, c)');
157
- t.ok(!types.containsAll(['nothing','to','see'], 'nothing', 'here'), '!containsAll([a,b,c], a, d)');
158
-
159
- const arr = ['hello', 'darkness', 'my', 'old', 'friend'];
160
- types.removeFromArray(arr, 'darkness');
161
- t.isJSON(arr, ['hello','my','old','friend'], 'removeFromArray(...)');
162
-
163
154
  (function(){ t.ok(types.unbound(this), 'unbound(unboundThis)'); })();
164
155
  (function(){ t.ok(!types.unbound(this), '!unbound(boundThis)') }).bind({})();
165
156
  typesInstance.notUnbound();
@@ -170,15 +161,107 @@ t.dies(function(){types.needObj(null); return true}, '!needObj(null)');
170
161
  t.ok((function(){types.needType(TYP.S, 'hi'); return true})(), "needType('string','hi')");
171
162
  t.dies(function(){types.needType(TYP.O, null); return true}, "!needType('object',null)");
172
163
 
164
+ { // Tests of isa() method.
165
+ let wants = [TYP.S, TYP.N];
166
+ t.ok(types.isa('hi', ...wants), 'isa(val, ...types)');
167
+ t.isa('hello', wants, ' ^ using Test.isa()');
168
+ t.isa(42, wants, ' ^ with second type');
169
+ t.ok(!types.isa({}, ...wants), '!isa(val, ...types)');
170
+ t.nota({}, wants, ' ^ using Test.nota()');
171
+
172
+ wants = [SubtypeClass, DifferentClass];
173
+ t.isa(subtypeInstance, wants, 'isa(val, ...classes)');
174
+ t.isa(differentInstance, wants, ' ^ with second class');
175
+ t.nota(typesInstance, wants, 'nota(val, ...classes)');
176
+
177
+ wants = [TYP.B, TypeClass];
178
+ t.isa(true, wants, 'isa() → with mixed types/classes');
179
+ t.isa(subtypeInstance, wants, ' ^ with second type/class');
180
+ }
181
+
182
+ { // Tests of needs() method.
183
+ let needs = [TYP.S, TYP.N];
184
+ t.lives(() => types.needs('hi', ...needs), 'needs(val, ...types)');
185
+ t.lives(() => types.needs(42, ...needs), ' ^ with second type');
186
+ t.dies(() => types.needs({}, ...needs), ' ^ throws on failure');
187
+
188
+ needs = [SubtypeClass, DifferentClass];
189
+ t.lives(() => types.needs(subtypeInstance, ...needs), 'needs(val, ...classes)');
190
+ t.lives(() => types.needs(differentInstance, ...needs), ' ^ with second class');
191
+ t.dies(() => types.needs(typesInstance, ...needs), ' ^ throws on failure');
192
+
193
+ needs = [TYP.B, TypeClass];
194
+ t.lives(() => types.needs(true, ...needs), 'needs() → with mixed types/classes');
195
+ t.lives(() => types.needs(subtypeInstance, ...needs), ' ^ with second type/class');
196
+ }
197
+
173
198
  { // Try a few versions of 'def'
174
199
  const obj = {};
175
200
  types.def(obj, 'test1', 'Test 1');
176
201
  t.is(obj.test1, 'Test 1', 'def(obj, name, value)');
177
202
  types.def(obj)('test2', 'Test 2');
178
203
  t.is(obj.test2, 'Test 2', 'def(obj)(name, value)');
204
+ obj.test2 = '2 Test';
205
+ t.is(obj.test2, 'Test 2', 'def() is read-only by default');
206
+
207
+ types.def(obj, true)('test3', 'Test 3');
208
+ t.is(Object.keys(obj).length, 1, 'def(obj, true)(...)');
209
+
210
+ types.def(obj, 'a1', function()
211
+ { // Returning a different property.
212
+ return this.test2;
213
+ },
214
+ function(val)
215
+ { // Assigning to a different property.
216
+ this.$$ = val;
217
+ });
218
+
219
+ const gs = 'def(obj, name, getter, setter)';
220
+ t.is(obj.a1, 'Test 2', gs+'~getter');
221
+ obj.a1 = 'A1->$$';
222
+ t.is(obj.$$, 'A1->$$', gs+'~setter');
223
+
224
+ types.def(obj, 'test4', 'Test 4', {writable: true});
225
+ t.is(obj.test4, 'Test 4', 'def(..., {writable}) → added property');
226
+ obj.test4 = '4 Test';
227
+ t.is(obj.test4, '4 Test', ' ^ and it was writable');
228
+
229
+ const td = types.def(obj);
230
+ td('getOnly', {get: function() { return 'GETTER'; }});
231
+ obj.getOnly = 'blah blah blah';
232
+ t.is(obj.getOnly, 'GETTER', 'def(..., {get: getter}) → getter worked');
233
+ td('setOnly', {set: function(val) { this.__ = val; }});
234
+ obj.setOnly = 'SETTER';
235
+ t.is(obj.__, 'SETTER', 'def(..., {set: setter}) → setter worked');
236
+ t.is(obj.setOnly, undefined, ' ^ get is undefined');
237
+
238
+ td('foobar', {value: 'FOO BAR'});
239
+ t.is(obj.foobar, 'FOO BAR', 'def(..., {value})');
240
+
241
+ let anObj = {value: 'BAR FOO'};
242
+ td('barfoo', anObj, false);
243
+ t.is(obj.barfoo, anObj, 'def(..., descriptor, false) → assigned object as value');
244
+
245
+ td('barfoo2', anObj);
246
+ t.is(anObj.configurable, true, 'def(..., descriptor) → descriptor is a reference');
247
+
248
+ anObj = {value: 'new test'};
249
+ td('barfoo3', anObj, true);
250
+
251
+ t.is(anObj.configurable, undefined, 'def(..., descriptor, true) → cloned descriptor');
252
+ t.is(obj.barfoo3, 'new test', ' ^ value was correct')
253
+
254
+ td(
255
+ {
256
+ hello: 'World',
257
+ goodbye: 'Universe',
258
+ });
259
+
260
+ t.ok((obj.hello === 'World' && obj.goodbye === 'Universe'), 'def(obj, {prop1: value1, prop2: value2})')
179
261
  }
180
262
 
181
- t.dies(()=>types.NYI(), 'NYI()');
263
+ // TODO: isa() and needs()
264
+ // TODO: stringify()
182
265
 
183
266
  // All done.
184
267
  t.output();
package/CHANGELOG.md DELETED
@@ -1,25 +0,0 @@
1
- # Changelog
2
- All notable changes to this project will be documented in this file.
3
-
4
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
-
7
- ## [Unreleased]
8
-
9
- ## [1.0.0-beta.2] - 2022-07-08
10
- ## Changed
11
- - Renamed `src` to `lib` as we're not compiling/transpiling this code.
12
- - Moved `index.js` into `lib` with the rest of the module files.
13
- - Added `modules.js` with a method for generating a name/id for a module.
14
- - Added `isSearch()`,`isReplacement()`, and `replaceItems()` to `strings.js`.
15
-
16
- ## [1.0.0-beta.1] - 2022-07-07
17
- ### Added
18
- - Initial release.
19
- - Pulled a bunch of the core libraries from the old Lum.js project.
20
- - Refactored and reorganized the libraries a lot.
21
-
22
- [Unreleased]: https://github.com/supernovus/lum.core.js/compare/v1.0.0-beta.1...HEAD
23
- [1.0.0-beta.2]: https://github.com/supernovus/lum.core.js/compare/v1.0.0-beta.1...v1.0.0-beta.2
24
- [1.0.0-beta.1]: https://github.com/supernovus/lum.core.js/releases/tag/v1.0.0-beta.1
25
-