@rokkit/data 1.0.0-next.100 → 1.0.0-next.105
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 +8 -24
- package/src/dataset.js +72 -71
- package/src/filter.js +17 -18
- package/src/formatter.js +15 -16
- package/src/infer.js +74 -62
- package/src/model.js +44 -44
- package/src/parser.js +42 -42
- package/src/renamer.js +40 -42
- package/src/rollup.js +22 -22
- package/LICENSE +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/data",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.105",
|
|
4
4
|
"description": "Contains generic data manipulation functions that can be used in various components.",
|
|
5
5
|
"author": "Jerry Thomas <me@jerrythomas.name>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,16 +11,10 @@
|
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"@vitest/coverage-v8": "^2.1.1",
|
|
19
|
-
"@vitest/ui": "~2.1.1",
|
|
20
|
-
"typescript": "^5.6.2",
|
|
21
|
-
"vite": "^5.4.6",
|
|
22
|
-
"vitest": "~2.1.1",
|
|
23
|
-
"shared-config": "1.0.0-next.100"
|
|
14
|
+
"scripts": {
|
|
15
|
+
"prepublishOnly": "tsc --project tsconfig.build.json",
|
|
16
|
+
"clean": "rm -rf dist",
|
|
17
|
+
"build": "bun clean && bun prepublishOnly"
|
|
24
18
|
},
|
|
25
19
|
"files": [
|
|
26
20
|
"src/**/*.js",
|
|
@@ -37,20 +31,10 @@
|
|
|
37
31
|
},
|
|
38
32
|
"dependencies": {
|
|
39
33
|
"@lukeed/uuid": "^2.0.1",
|
|
34
|
+
"@rokkit/core": "1.0.0-next.104",
|
|
40
35
|
"d3-array": "^3.2.4",
|
|
41
36
|
"d3-collection": "^1.0.7",
|
|
42
37
|
"ramda": "^0.30.1",
|
|
43
|
-
"svelte": "^
|
|
44
|
-
"@rokkit/core": "1.0.0-next.100"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"format": "prettier --write .",
|
|
48
|
-
"lint": "eslint --fix .",
|
|
49
|
-
"test:ci": "vitest run",
|
|
50
|
-
"test:ui": "vitest --ui",
|
|
51
|
-
"test": "vitest",
|
|
52
|
-
"coverage": "vitest run --coverage",
|
|
53
|
-
"latest": "pnpm upgrade --latest && pnpm test:ci",
|
|
54
|
-
"release": "pnpm publish --access public"
|
|
38
|
+
"svelte": "^5.10.0"
|
|
55
39
|
}
|
|
56
|
-
}
|
|
40
|
+
}
|
package/src/dataset.js
CHANGED
|
@@ -3,54 +3,6 @@ import { descending } from 'd3-array'
|
|
|
3
3
|
import { pickAllowedConfig, defaultConfig, includeAll } from './constants'
|
|
4
4
|
import { deriveSortableColumn } from './infer'
|
|
5
5
|
import { groupDataByKeys, fillAlignedData, getAlignGenerator, aggregateData } from './rollup'
|
|
6
|
-
/**
|
|
7
|
-
* Dataset is a collection of data with a set of operations that can be performed on it.
|
|
8
|
-
* @param {Array} data - The data to be stored in the dataset.
|
|
9
|
-
* @param {Object} options - The configuration options for the dataset.
|
|
10
|
-
* @returns {Object} - An object with a set of operations that can be performed on the dataset.
|
|
11
|
-
*/
|
|
12
|
-
// eslint-disable-next-line max-lines-per-function
|
|
13
|
-
export function dataset(data, options = {}) {
|
|
14
|
-
const config = { ...clone(defaultConfig), ...options }
|
|
15
|
-
|
|
16
|
-
const actions = {
|
|
17
|
-
// configuration
|
|
18
|
-
override: (props) => dataset(data, { ...config, ...pickAllowedConfig(props) }),
|
|
19
|
-
where: (condition) => dataset(data, where(config, condition)),
|
|
20
|
-
groupBy: (...fields) => dataset(data, groupBy(config, ...fields)),
|
|
21
|
-
alignBy: (...fields) => dataset(data, alignBy(config, ...fields)),
|
|
22
|
-
using: (template) => dataset(data, usingTemplate(config, template)),
|
|
23
|
-
summarize: (from, fields) => dataset(data, summarize(config, from, fields)),
|
|
24
|
-
// alter keys
|
|
25
|
-
rename: (how) => dataset(renameKeys(data, how)),
|
|
26
|
-
drop: (...fields) => dataset(dropKeys(data, ...fields)),
|
|
27
|
-
// alter rows
|
|
28
|
-
sortBy: (...fields) => dataset(sortDataBy(data, ...fields)),
|
|
29
|
-
delete: () => dataset(deleteRows(data, config.filter || includeAll)),
|
|
30
|
-
update: (value) => dataset(updateRows(data, config.filter || identity, value)),
|
|
31
|
-
fillNA: (value) => dataset(fillNA(data, value)),
|
|
32
|
-
// transform data
|
|
33
|
-
apply: (callback) => dataset(data.map(callback)),
|
|
34
|
-
rollup: () => dataset(rollup(data, config)),
|
|
35
|
-
select: (...cols) => selectKeys(data, config, ...cols),
|
|
36
|
-
// set operations
|
|
37
|
-
union: (other) => dataset(data.concat(other)),
|
|
38
|
-
minus: (other) => dataset(data.filter((d) => !other.find((x) => equals(x, d)))),
|
|
39
|
-
intersect: (other) => dataset(data.filter((d) => other.find((x) => equals(x, d)))),
|
|
40
|
-
// joins
|
|
41
|
-
innerJoin: (other, condition) => dataset(innerJoin(data, other.select(), condition)),
|
|
42
|
-
leftJoin: (other, condition) => dataset(leftJoin(data, other.select(), condition)),
|
|
43
|
-
rightJoin: (other, condition) => dataset(leftJoin(other.select(), data, condition)),
|
|
44
|
-
fullJoin: (other, condition) => dataset(fullJoin(data, other.select(), condition)),
|
|
45
|
-
crossJoin: (other) => dataset(crossJoin(data, other.select())),
|
|
46
|
-
semiJoin: (other, condition) => dataset(semiJoin(data, other.select(), condition)),
|
|
47
|
-
antiJoin: (other, condition) => dataset(antiJoin(data, other.select(), condition)),
|
|
48
|
-
nestedJoin: (other, condition) =>
|
|
49
|
-
dataset(nestedJoin(data, other.select(), condition, config.children))
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return actions
|
|
53
|
-
}
|
|
54
6
|
|
|
55
7
|
/**
|
|
56
8
|
* Adds a filter to the dataset using the provided condition. This filter is applied
|
|
@@ -252,19 +204,6 @@ function nestedJoin(first, second, condition, key = 'children') {
|
|
|
252
204
|
return result
|
|
253
205
|
}
|
|
254
206
|
|
|
255
|
-
/**
|
|
256
|
-
* Renames the keys of the data based on the provided mapping, or function
|
|
257
|
-
* that returns the new key. If the new key already exists, the value is overwritten.
|
|
258
|
-
*
|
|
259
|
-
* @param {Array<Object>} data - The data to rename the keys of.
|
|
260
|
-
* @param {Object|Function} how - The mapping of old keys to new keys, or a function that performs renaming.
|
|
261
|
-
* returns Array<Object> - The updated data with renamed keys.
|
|
262
|
-
*/
|
|
263
|
-
function renameKeys(data, how) {
|
|
264
|
-
const rename = typeof how === 'function' ? how : renameKeysUsingMap(how)
|
|
265
|
-
return data.map(rename)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
207
|
/**
|
|
269
208
|
* Drops the specified keys from the data.
|
|
270
209
|
*
|
|
@@ -342,6 +281,19 @@ function renameKeysUsingMap(lookup) {
|
|
|
342
281
|
.reduce((acc, [k, v]) => ({ ...acc, [lookup[k] || k]: v }), {})
|
|
343
282
|
}
|
|
344
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Renames the keys of the data based on the provided mapping, or function
|
|
286
|
+
* that returns the new key. If the new key already exists, the value is overwritten.
|
|
287
|
+
*
|
|
288
|
+
* @param {Array<Object>} data - The data to rename the keys of.
|
|
289
|
+
* @param {Object|Function} how - The mapping of old keys to new keys, or a function that performs renaming.
|
|
290
|
+
* returns Array<Object> - The updated data with renamed keys.
|
|
291
|
+
*/
|
|
292
|
+
function renameKeys(data, how) {
|
|
293
|
+
const rename = typeof how === 'function' ? how : renameKeysUsingMap(how)
|
|
294
|
+
return data.map(rename)
|
|
295
|
+
}
|
|
296
|
+
|
|
345
297
|
/**
|
|
346
298
|
* Sorts data by the specified columns.
|
|
347
299
|
*
|
|
@@ -378,6 +330,21 @@ function selectKeys(data, config, ...cols) {
|
|
|
378
330
|
return result
|
|
379
331
|
}
|
|
380
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Returns the default aggregator which rolls up the columns other than the group_by columns into a single object.
|
|
335
|
+
*
|
|
336
|
+
* @param {import('./types').Metadata} metadata - The metadata for the columns to be aggregated.
|
|
337
|
+
* @param {Object} config - The configuration used to build the aggregator.
|
|
338
|
+
*
|
|
339
|
+
* @returns {Object} An object containing the default aggregator for the specified metadata and configuration.
|
|
340
|
+
*/
|
|
341
|
+
export function defaultAggregator(config) {
|
|
342
|
+
return {
|
|
343
|
+
mapper: omit(config.group_by),
|
|
344
|
+
reducers: [{ field: config.children, formula: identity }]
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
381
348
|
/**
|
|
382
349
|
* Summarizes the dataset by the specified columns.
|
|
383
350
|
* @param {import('./types').dataset} df - The dataset object to summarize.
|
|
@@ -413,16 +380,50 @@ function rollup(data, config) {
|
|
|
413
380
|
}
|
|
414
381
|
|
|
415
382
|
/**
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
* @param {
|
|
419
|
-
* @
|
|
420
|
-
*
|
|
421
|
-
* @returns {Object} An object containing the default aggregator for the specified metadata and configuration.
|
|
383
|
+
* Dataset is a collection of data with a set of operations that can be performed on it.
|
|
384
|
+
* @param {Array} data - The data to be stored in the dataset.
|
|
385
|
+
* @param {Object} options - The configuration options for the dataset.
|
|
386
|
+
* @returns {Object} - An object with a set of operations that can be performed on the dataset.
|
|
422
387
|
*/
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
388
|
+
// eslint-disable-next-line max-lines-per-function
|
|
389
|
+
export function dataset(data, options = {}) {
|
|
390
|
+
const config = { ...clone(defaultConfig), ...options }
|
|
391
|
+
|
|
392
|
+
const actions = {
|
|
393
|
+
// configuration
|
|
394
|
+
override: (props) => dataset(data, { ...config, ...pickAllowedConfig(props) }),
|
|
395
|
+
where: (condition) => dataset(data, where(config, condition)),
|
|
396
|
+
groupBy: (...fields) => dataset(data, groupBy(config, ...fields)),
|
|
397
|
+
alignBy: (...fields) => dataset(data, alignBy(config, ...fields)),
|
|
398
|
+
using: (template) => dataset(data, usingTemplate(config, template)),
|
|
399
|
+
summarize: (from, fields) => dataset(data, summarize(config, from, fields)),
|
|
400
|
+
// alter keys
|
|
401
|
+
rename: (how) => dataset(renameKeys(data, how)),
|
|
402
|
+
drop: (...fields) => dataset(dropKeys(data, ...fields)),
|
|
403
|
+
// alter rows
|
|
404
|
+
sortBy: (...fields) => dataset(sortDataBy(data, ...fields)),
|
|
405
|
+
delete: () => dataset(deleteRows(data, config.filter || includeAll)),
|
|
406
|
+
update: (value) => dataset(updateRows(data, config.filter || identity, value)),
|
|
407
|
+
fillNA: (value) => dataset(fillNA(data, value)),
|
|
408
|
+
// transform data
|
|
409
|
+
apply: (callback) => dataset(data.map(callback)),
|
|
410
|
+
rollup: () => dataset(rollup(data, config)),
|
|
411
|
+
select: (...cols) => selectKeys(data, config, ...cols),
|
|
412
|
+
// set operations
|
|
413
|
+
union: (other) => dataset(data.concat(other)),
|
|
414
|
+
minus: (other) => dataset(data.filter((d) => !other.find((x) => equals(x, d)))),
|
|
415
|
+
intersect: (other) => dataset(data.filter((d) => other.find((x) => equals(x, d)))),
|
|
416
|
+
// joins
|
|
417
|
+
innerJoin: (other, condition) => dataset(innerJoin(data, other.select(), condition)),
|
|
418
|
+
leftJoin: (other, condition) => dataset(leftJoin(data, other.select(), condition)),
|
|
419
|
+
rightJoin: (other, condition) => dataset(leftJoin(other.select(), data, condition)),
|
|
420
|
+
fullJoin: (other, condition) => dataset(fullJoin(data, other.select(), condition)),
|
|
421
|
+
crossJoin: (other) => dataset(crossJoin(data, other.select())),
|
|
422
|
+
semiJoin: (other, condition) => dataset(semiJoin(data, other.select(), condition)),
|
|
423
|
+
antiJoin: (other, condition) => dataset(antiJoin(data, other.select(), condition)),
|
|
424
|
+
nestedJoin: (other, condition) =>
|
|
425
|
+
dataset(nestedJoin(data, other.select(), condition, config.children))
|
|
427
426
|
}
|
|
427
|
+
|
|
428
|
+
return actions
|
|
428
429
|
}
|
package/src/filter.js
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import { filterOperations } from './constants'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a row matches the given value based on the specified operator.
|
|
5
|
+
* Supports both standard and negated operators by checking the operator string
|
|
6
|
+
* for a leading '!' character, which indicates negation.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} row - The row object in which to search for the value match.
|
|
9
|
+
* @param {any} value - The value to match against the row's properties.
|
|
10
|
+
* @param {string} operator - The operator used to compare the row's properties to the value.
|
|
11
|
+
* When operator starts with '!', it negates the result of the comparison.
|
|
12
|
+
* @returns {boolean} - Returns true if the row matches the condition; otherwise, returns false.
|
|
13
|
+
*/
|
|
14
|
+
function findMatchingRow(row, value, operator) {
|
|
15
|
+
const op = operator.startsWith('!') ? operator.slice(1) : operator
|
|
16
|
+
const matched = Object.keys(row).some((key) => filterOperations[op](row[key], value))
|
|
17
|
+
|
|
18
|
+
return operator.startsWith('!') ? !matched : matched
|
|
19
|
+
}
|
|
3
20
|
/**
|
|
4
21
|
* Filters an array of objects based on a specified condition defined in the options.
|
|
5
22
|
* If a column is provided, it filters based on the value of that column. Otherwise, it filters
|
|
@@ -22,24 +39,6 @@ export function filterObjectArray(data, options) {
|
|
|
22
39
|
return data.filter((row) => findMatchingRow(row, value, operator))
|
|
23
40
|
}
|
|
24
41
|
|
|
25
|
-
/**
|
|
26
|
-
* Checks if a row matches the given value based on the specified operator.
|
|
27
|
-
* Supports both standard and negated operators by checking the operator string
|
|
28
|
-
* for a leading '!' character, which indicates negation.
|
|
29
|
-
*
|
|
30
|
-
* @param {Object} row - The row object in which to search for the value match.
|
|
31
|
-
* @param {any} value - The value to match against the row's properties.
|
|
32
|
-
* @param {string} operator - The operator used to compare the row's properties to the value.
|
|
33
|
-
* When operator starts with '!', it negates the result of the comparison.
|
|
34
|
-
* @returns {boolean} - Returns true if the row matches the condition; otherwise, returns false.
|
|
35
|
-
*/
|
|
36
|
-
function findMatchingRow(row, value, operator) {
|
|
37
|
-
const op = operator.startsWith('!') ? operator.slice(1) : operator
|
|
38
|
-
const matched = Object.keys(row).some((key) => filterOperations[op](row[key], value))
|
|
39
|
-
|
|
40
|
-
return operator.startsWith('!') ? !matched : matched
|
|
41
|
-
}
|
|
42
|
-
|
|
43
42
|
/**
|
|
44
43
|
* Applies multiple filtering conditions to an array of objects. This function iterates through each filter option,
|
|
45
44
|
* successively narrowing down the data.
|
package/src/formatter.js
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
import { identity } from 'ramda'
|
|
2
|
-
|
|
3
|
-
const formatterMap = {
|
|
4
|
-
currency: getCurrencyFormatter,
|
|
5
|
-
integer: (language) => (val) => val.toLocaleString(language, { maximumFractionDigits: 0 }),
|
|
6
|
-
number: (language, decimalPlaces) => (val) =>
|
|
7
|
-
val.toLocaleString(language, {
|
|
8
|
-
minimumFractionDigits: decimalPlaces,
|
|
9
|
-
maximumFractionDigits: decimalPlaces
|
|
10
|
-
}),
|
|
11
|
-
date: (language) => (val) => val.toLocaleDateString(language),
|
|
12
|
-
time: (language) => (val) => val.toLocaleTimeString(language),
|
|
13
|
-
object: () => JSON.stringify,
|
|
14
|
-
array: () => JSON.stringify, // Combined formatter for 'object' and 'array'
|
|
15
|
-
ellipsis: () => () => '...' // Static formatter, no need for parameters
|
|
16
|
-
}
|
|
17
|
-
|
|
18
2
|
/**
|
|
19
3
|
* Returns a currency formatter function that formats a value as a currency string.
|
|
20
4
|
* @param {string} language - The IETF language tag used for locale-specific formatting.
|
|
@@ -39,6 +23,21 @@ function getCurrencyFormatter(language, decimalPlaces) {
|
|
|
39
23
|
return formatter
|
|
40
24
|
}
|
|
41
25
|
|
|
26
|
+
const formatterMap = {
|
|
27
|
+
currency: getCurrencyFormatter,
|
|
28
|
+
integer: (language) => (val) => val.toLocaleString(language, { maximumFractionDigits: 0 }),
|
|
29
|
+
number: (language, decimalPlaces) => (val) =>
|
|
30
|
+
val.toLocaleString(language, {
|
|
31
|
+
minimumFractionDigits: decimalPlaces,
|
|
32
|
+
maximumFractionDigits: decimalPlaces
|
|
33
|
+
}),
|
|
34
|
+
date: (language) => (val) => val.toLocaleDateString(language),
|
|
35
|
+
time: (language) => (val) => val.toLocaleTimeString(language),
|
|
36
|
+
object: () => JSON.stringify,
|
|
37
|
+
array: () => JSON.stringify, // Combined formatter for 'object' and 'array'
|
|
38
|
+
ellipsis: () => () => '...' // Static formatter, no need for parameters
|
|
39
|
+
}
|
|
40
|
+
|
|
42
41
|
/**
|
|
43
42
|
* Creates a formatter function that formats a value according to the specified type and optional locale settings.
|
|
44
43
|
* Supported types include 'default' (no formatting), 'integer', 'number', 'date', 'time',, 'object', 'array', 'currency' & 'ellipsis''
|
package/src/infer.js
CHANGED
|
@@ -7,27 +7,30 @@ import { typeOf } from './utils'
|
|
|
7
7
|
import { deriveColumnProperties } from './metadata'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Validates an array to ensure it can be used to create a sorting configuration.
|
|
11
11
|
*
|
|
12
|
-
* @param {
|
|
13
|
-
* @
|
|
14
|
-
* @throws {Error} Throws an error if the value type is not supported or the value format is invalid.
|
|
12
|
+
* @param {Array} value - The array to validate.
|
|
13
|
+
* @throws {Error} Throws an error if the array does not correspond to a [string, boolean] pair.
|
|
15
14
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const configGenerators = {
|
|
20
|
-
array: arrayConfigGenerator,
|
|
21
|
-
object: objectConfigGenerator,
|
|
22
|
-
string: stringConfigGenerator
|
|
15
|
+
function arrayValidator(value) {
|
|
16
|
+
if (value.length !== 2 || typeof value[0] !== 'string' || typeof value[1] !== 'boolean') {
|
|
17
|
+
throw new Error('Array should be a pair of [string, boolean]')
|
|
23
18
|
}
|
|
19
|
+
}
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Validates an object to ensure it has the necessary properties for creating a sorting configuration.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} value - The object to validate.
|
|
25
|
+
* @throws {Error} Throws an error if the object does not have a 'name' property or has an invalid 'sorter'.
|
|
26
|
+
*/
|
|
27
|
+
function objectValidator(value) {
|
|
28
|
+
if (typeof value.name !== 'string') {
|
|
29
|
+
throw new Error('The property "name" is required and must be a string')
|
|
30
|
+
}
|
|
31
|
+
if (value.sorter !== undefined && typeof value.sorter !== 'function') {
|
|
32
|
+
throw new Error('Sorter should be a function')
|
|
28
33
|
}
|
|
29
|
-
|
|
30
|
-
return configGenerator(value)
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
/**
|
|
@@ -63,32 +66,28 @@ function stringConfigGenerator(value) {
|
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
/**
|
|
66
|
-
*
|
|
69
|
+
* Derives the sorting information for a column based on the type of the input value.
|
|
67
70
|
*
|
|
68
|
-
* @param {
|
|
69
|
-
* @
|
|
71
|
+
* @param {import('./types').SortableColumn} value - The value that determines the sorting behavior of the column.
|
|
72
|
+
* @returns {import('./types').ColumnSorter} An object containing the 'name' of the column and a 'sorter' function.
|
|
73
|
+
* @throws {Error} Throws an error if the value type is not supported or the value format is invalid.
|
|
70
74
|
*/
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
throw new Error('Array should be a pair of [string, boolean]')
|
|
74
|
-
}
|
|
75
|
-
}
|
|
75
|
+
export function deriveSortableColumn(value) {
|
|
76
|
+
const type = typeOf(value)
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* @throws {Error} Throws an error if the object does not have a 'name' property or has an invalid 'sorter'.
|
|
82
|
-
*/
|
|
83
|
-
function objectValidator(value) {
|
|
84
|
-
if (typeof value.name !== 'string') {
|
|
85
|
-
throw new Error('The property "name" is required and must be a string')
|
|
78
|
+
const configGenerators = {
|
|
79
|
+
array: arrayConfigGenerator,
|
|
80
|
+
object: objectConfigGenerator,
|
|
81
|
+
string: stringConfigGenerator
|
|
86
82
|
}
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
|
|
84
|
+
const configGenerator = configGenerators[type]
|
|
85
|
+
if (!configGenerator) {
|
|
86
|
+
throw new Error(`Invalid value type: ${type}`)
|
|
89
87
|
}
|
|
90
|
-
}
|
|
91
88
|
|
|
89
|
+
return configGenerator(value)
|
|
90
|
+
}
|
|
92
91
|
/**
|
|
93
92
|
* Creates a deep scan sample object that contains a union of all keys from all
|
|
94
93
|
* objects in the array, pulling the values from the last item that contains the key.
|
|
@@ -96,7 +95,8 @@ function objectValidator(value) {
|
|
|
96
95
|
* @param {Array<Object>} data - An array of objects to deep scan and collect sample values.
|
|
97
96
|
* @returns {Object} A sample object consolidating all unique keys with their latest non-null values.
|
|
98
97
|
*/
|
|
99
|
-
export function getDeepScanSample(data) {
|
|
98
|
+
export function getDeepScanSample(data, deepScan = true) {
|
|
99
|
+
if (!deepScan) return [data[0]]
|
|
100
100
|
return data.reduce(
|
|
101
101
|
(acc, cur) => ({
|
|
102
102
|
...acc,
|
|
@@ -178,31 +178,6 @@ export function inferDataType(values) {
|
|
|
178
178
|
return type
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
/**
|
|
182
|
-
* Derives column metadata from the data to be used in a tabular component.
|
|
183
|
-
*
|
|
184
|
-
* @param {Array} data - The data to derive column metadata from.
|
|
185
|
-
* @returns {Array<import('./types').ColumnMetadata>} - The derived column metadata.
|
|
186
|
-
*/
|
|
187
|
-
export function deriveMetadata(dataArray, options = {}) {
|
|
188
|
-
let { columns = [] } = options
|
|
189
|
-
if (dataArray.length === 0) return columns
|
|
190
|
-
const { actions, language } = { ...defaultViewOptions, ...options }
|
|
191
|
-
|
|
192
|
-
if (columns.length === 0) {
|
|
193
|
-
columns = deriveColumns(dataArray, options)
|
|
194
|
-
} else {
|
|
195
|
-
columns.forEach((col) => {
|
|
196
|
-
// if (col.sortable && !col.order) col.order = 'none'
|
|
197
|
-
if (!col.fields) col.fields = { text: col.name }
|
|
198
|
-
})
|
|
199
|
-
}
|
|
200
|
-
columns = addFormatters(columns, language)
|
|
201
|
-
if (actions.length > 0) columns = deriveActions(columns, actions)
|
|
202
|
-
|
|
203
|
-
return columns
|
|
204
|
-
}
|
|
205
|
-
|
|
206
181
|
/**
|
|
207
182
|
* Adds a formatter function to each column in the array based on the column type. If a formatter
|
|
208
183
|
* function is already present, it will not be overwritten. The formatter is created based on the
|
|
@@ -261,3 +236,40 @@ export function deriveActions(columns, input) {
|
|
|
261
236
|
}))
|
|
262
237
|
return [...actionColumns, ...columns]
|
|
263
238
|
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Derives column metadata from the data to be used in a tabular component.
|
|
242
|
+
* @param {Array<import('./types').ColumnMetadata>} columns
|
|
243
|
+
* @param {Array} dataArray - The data to derive column metadata from.
|
|
244
|
+
* @param {Object} [options] - The options to use when deriving column metadata.
|
|
245
|
+
* @returns {Array<import('./types').ColumnMetadata>} - The derived column metadata.
|
|
246
|
+
*/
|
|
247
|
+
function deriveColumnMetadata(columns, dataArray, options) {
|
|
248
|
+
if (columns.length === 0) {
|
|
249
|
+
columns = deriveColumns(dataArray, options)
|
|
250
|
+
} else {
|
|
251
|
+
columns.forEach((col) => {
|
|
252
|
+
// if (col.sortable && !col.order) col.order = 'none'
|
|
253
|
+
if (!col.fields) col.fields = { text: col.name }
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
return columns
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Derives column metadata from the data to be used in a tabular component.
|
|
260
|
+
*
|
|
261
|
+
* @param {Array} data - The data to derive column metadata from.
|
|
262
|
+
* @param {Object} [options] - The options to use when deriving column metadata.
|
|
263
|
+
* @returns {Array<import('./types').ColumnMetadata>} - The derived column metadata.
|
|
264
|
+
*/
|
|
265
|
+
export function deriveMetadata(dataArray, options = {}) {
|
|
266
|
+
let { columns = [] } = options
|
|
267
|
+
if (dataArray.length === 0) return columns
|
|
268
|
+
const { actions, language } = { ...defaultViewOptions, ...options }
|
|
269
|
+
|
|
270
|
+
columns = deriveColumnMetadata(columns, dataArray, options)
|
|
271
|
+
columns = addFormatters(columns, language)
|
|
272
|
+
if (actions.length > 0) columns = deriveActions(columns, actions)
|
|
273
|
+
|
|
274
|
+
return columns
|
|
275
|
+
}
|
package/src/model.js
CHANGED
|
@@ -2,6 +2,50 @@ import { identity, clone as copy } from 'ramda'
|
|
|
2
2
|
import { getDeepScanSample } from './infer'
|
|
3
3
|
import { typeOf } from './utils'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Derives a model from the input data
|
|
7
|
+
*
|
|
8
|
+
* @param {Array|Object} data - the data to derive the model from
|
|
9
|
+
* @param {Boolean} sparse - whether the data set contains sparse data
|
|
10
|
+
* @returns {Array} - the derived model
|
|
11
|
+
*/
|
|
12
|
+
function deriveModel(data, sparse = false) {
|
|
13
|
+
const result = []
|
|
14
|
+
let item = data
|
|
15
|
+
|
|
16
|
+
if (Array.isArray(data)) item = sparse ? getDeepScanSample(data) : data[0]
|
|
17
|
+
|
|
18
|
+
const kv = Object.entries(item)
|
|
19
|
+
kv.forEach(([key, value]) => {
|
|
20
|
+
result.push({
|
|
21
|
+
name: key,
|
|
22
|
+
type: typeOf(value)
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
return result
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Merges two models, returning a new model
|
|
30
|
+
*
|
|
31
|
+
* @param {Array} first - the first model
|
|
32
|
+
* @param {Array} second - the second model
|
|
33
|
+
* @param {Boolean} override - whether to override the type of the first model
|
|
34
|
+
* @returns {Array} - the merged model
|
|
35
|
+
*/
|
|
36
|
+
function mergeModels(first, second, override = false) {
|
|
37
|
+
second.forEach(({ name, type }) => {
|
|
38
|
+
const existing = first.find((x) => x.name === name)
|
|
39
|
+
if (existing) {
|
|
40
|
+
if (existing.type !== type) existing.mixedTypes = true
|
|
41
|
+
if (override) existing.type = type
|
|
42
|
+
} else {
|
|
43
|
+
first.push({ name, type })
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
return first
|
|
47
|
+
}
|
|
48
|
+
|
|
5
49
|
/**
|
|
6
50
|
* A model is a representation of a dataset
|
|
7
51
|
* @returns an object with methods to manipulate the model
|
|
@@ -54,47 +98,3 @@ export function model() {
|
|
|
54
98
|
}
|
|
55
99
|
return actions
|
|
56
100
|
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Derives a model from the input data
|
|
60
|
-
*
|
|
61
|
-
* @param {Array|Object} data - the data to derive the model from
|
|
62
|
-
* @param {Boolean} sparse - whether the data set contains sparse data
|
|
63
|
-
* @returns {Array} - the derived model
|
|
64
|
-
*/
|
|
65
|
-
function deriveModel(data, sparse = false) {
|
|
66
|
-
const result = []
|
|
67
|
-
let item = data
|
|
68
|
-
|
|
69
|
-
if (Array.isArray(data)) item = sparse ? getDeepScanSample(data) : data[0]
|
|
70
|
-
|
|
71
|
-
const kv = Object.entries(item)
|
|
72
|
-
kv.forEach(([key, value]) => {
|
|
73
|
-
result.push({
|
|
74
|
-
name: key,
|
|
75
|
-
type: typeOf(value)
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
return result
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Merges two models, returning a new model
|
|
83
|
-
*
|
|
84
|
-
* @param {Array} first - the first model
|
|
85
|
-
* @param {Array} second - the second model
|
|
86
|
-
* @param {Boolean} override - whether to override the type of the first model
|
|
87
|
-
* @returns {Array} - the merged model
|
|
88
|
-
*/
|
|
89
|
-
function mergeModels(first, second, override = false) {
|
|
90
|
-
second.forEach(({ name, type }) => {
|
|
91
|
-
const existing = first.find((x) => x.name === name)
|
|
92
|
-
if (existing) {
|
|
93
|
-
if (existing.type !== type) existing.mixedTypes = true
|
|
94
|
-
if (override) existing.type = type
|
|
95
|
-
} else {
|
|
96
|
-
first.push({ name, type })
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
return first
|
|
100
|
-
}
|
package/src/parser.js
CHANGED
|
@@ -28,6 +28,48 @@ export function getRegex() {
|
|
|
28
28
|
return new RegExp(pattern, 'gm')
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Replaces various shorthand operators with their standardized equivalent for filtering.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} operator - The operator to be replaced.
|
|
35
|
+
* @returns {string} - The replaced operator string.
|
|
36
|
+
*/
|
|
37
|
+
function replaceOperators(operator) {
|
|
38
|
+
return operator.replace(':', '~*').replace('=>', '>=').replace('=<', '<=')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Removes double quotes from the start and end of a string.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} str - The input string from which the surrounding quotes should be removed.
|
|
45
|
+
* @returns {string} - The unquoted string.
|
|
46
|
+
*/
|
|
47
|
+
function removeQuotes(str) {
|
|
48
|
+
const quoteMatch = str.match(/^"([^"]+)"$/)
|
|
49
|
+
return quoteMatch ? quoteMatch[1] : str
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Processes the filter value provided, converting it into a format suitable for filtering.
|
|
54
|
+
* If the operator includes a tilde (~), the value is converted to a RegExp.
|
|
55
|
+
*
|
|
56
|
+
* @param {string|number} value - The value to be processed. It could be a string needing quote removal or a number that needs parsing.
|
|
57
|
+
* @param {string} operator - The operator that determines the type of processing to be applied to the value.
|
|
58
|
+
* @returns {string|number|RegExp} - The processed value, which may be a RegExp if the operator involves pattern matching.
|
|
59
|
+
*/
|
|
60
|
+
function processValue(value, operator) {
|
|
61
|
+
// If the value can be parsed as an integer, do so. Otherwise, strip quotes if present.
|
|
62
|
+
|
|
63
|
+
value = !isNaN(parseInt(value)) ? parseInt(value) : removeQuotes(value)
|
|
64
|
+
// If the operator includes a tilde (~), treat the value as a regular expression,
|
|
65
|
+
// case-insensitive if '*' is present, sensitive otherwise.
|
|
66
|
+
if (operator.includes('~')) {
|
|
67
|
+
value = operator.includes('*') ? new RegExp(value, 'i') : new RegExp(value)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return value
|
|
71
|
+
}
|
|
72
|
+
|
|
31
73
|
/**
|
|
32
74
|
* Parses a search string and extracts filters based on a predefined regular expression pattern.
|
|
33
75
|
* Each filter captures column, operator, and value components. Any remaining text that
|
|
@@ -67,45 +109,3 @@ export function parseFilters(string) {
|
|
|
67
109
|
|
|
68
110
|
return results
|
|
69
111
|
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Replaces various shorthand operators with their standardized equivalent for filtering.
|
|
73
|
-
*
|
|
74
|
-
* @param {string} operator - The operator to be replaced.
|
|
75
|
-
* @returns {string} - The replaced operator string.
|
|
76
|
-
*/
|
|
77
|
-
function replaceOperators(operator) {
|
|
78
|
-
return operator.replace(':', '~*').replace('=>', '>=').replace('=<', '<=')
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Processes the filter value provided, converting it into a format suitable for filtering.
|
|
83
|
-
* If the operator includes a tilde (~), the value is converted to a RegExp.
|
|
84
|
-
*
|
|
85
|
-
* @param {string|number} value - The value to be processed. It could be a string needing quote removal or a number that needs parsing.
|
|
86
|
-
* @param {string} operator - The operator that determines the type of processing to be applied to the value.
|
|
87
|
-
* @returns {string|number|RegExp} - The processed value, which may be a RegExp if the operator involves pattern matching.
|
|
88
|
-
*/
|
|
89
|
-
function processValue(value, operator) {
|
|
90
|
-
// If the value can be parsed as an integer, do so. Otherwise, strip quotes if present.
|
|
91
|
-
|
|
92
|
-
value = !isNaN(parseInt(value)) ? parseInt(value) : removeQuotes(value)
|
|
93
|
-
// If the operator includes a tilde (~), treat the value as a regular expression,
|
|
94
|
-
// case-insensitive if '*' is present, sensitive otherwise.
|
|
95
|
-
if (operator.includes('~')) {
|
|
96
|
-
value = operator.includes('*') ? new RegExp(value, 'i') : new RegExp(value)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return value
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Removes double quotes from the start and end of a string.
|
|
104
|
-
*
|
|
105
|
-
* @param {string} str - The input string from which the surrounding quotes should be removed.
|
|
106
|
-
* @returns {string} - The unquoted string.
|
|
107
|
-
*/
|
|
108
|
-
function removeQuotes(str) {
|
|
109
|
-
const quoteMatch = str.match(/^"([^"]+)"$/)
|
|
110
|
-
return quoteMatch ? quoteMatch[1] : str
|
|
111
|
-
}
|
package/src/renamer.js
CHANGED
|
@@ -9,39 +9,25 @@ import { omit, identity } from 'ramda'
|
|
|
9
9
|
* @property {string} [separator='_'] - The separator to use between the key and the prefix or suffix.
|
|
10
10
|
* @property {Array} [keys] - The keys to rename.
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* @param {OptionsToRenameKeys} options - Options to rename keys
|
|
17
|
-
* @returns {Object} - A renamer object.
|
|
18
|
-
*
|
|
19
|
-
* Example:
|
|
13
|
+
* Renames the keys of an object using a lookup table.
|
|
20
14
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* renamer().setPrefix('a')
|
|
24
|
-
* .setSeparator('-')
|
|
25
|
-
* .setKeys(['a', 'b'])
|
|
26
|
-
* .get()
|
|
27
|
-
* renamer().setPrefix('a').get()
|
|
28
|
-
* renamer().setSuffix('b').get()
|
|
29
|
-
* renamer().setKeys(['a', 'b']).get()
|
|
15
|
+
* @param {Object} lookup - The lookup table.
|
|
16
|
+
* @returns {Function} - A function that takes an object and renames the keys using the lookup table.
|
|
30
17
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const rename = getRenamer(prefix, suffix, separator)
|
|
34
|
-
const renameObject = getObjectRenamer(rename, keys)
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
get: () => ({ rename, renameObject }),
|
|
38
|
-
setPrefix: (value) => renamer({ ...omit(['suffix'], options), prefix: value }),
|
|
39
|
-
setSuffix: (value) => renamer({ ...omit(['prefix'], options), suffix: value }),
|
|
40
|
-
setKeys: (value) => renamer({ ...options, keys: value }),
|
|
41
|
-
setSeparator: (value) => renamer({ ...options, separator: value })
|
|
42
|
-
}
|
|
18
|
+
function usingLookup(lookup) {
|
|
19
|
+
return (row) => Object.keys(row).reduce((acc, key) => ({ ...acc, [lookup[key]]: row[key] }), {})
|
|
43
20
|
}
|
|
44
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Renames the keys of an object using a function.
|
|
24
|
+
*
|
|
25
|
+
* @param {Function} rename - The function to rename the keys.
|
|
26
|
+
* @returns {Function} - A function that takes an object and renames the keys using the function.
|
|
27
|
+
*/
|
|
28
|
+
function forObject(rename) {
|
|
29
|
+
return (row) => Object.keys(row).reduce((acc, key) => ({ ...acc, [rename(key)]: row[key] }), {})
|
|
30
|
+
}
|
|
45
31
|
/**
|
|
46
32
|
* Generates a renamer function which adds a prefix or suffix to a string.
|
|
47
33
|
*
|
|
@@ -78,21 +64,33 @@ function getObjectRenamer(rename, keys) {
|
|
|
78
64
|
}
|
|
79
65
|
|
|
80
66
|
/**
|
|
81
|
-
*
|
|
67
|
+
* A generator function that produces a function to rename keys.
|
|
82
68
|
*
|
|
83
|
-
* @param {
|
|
84
|
-
* @returns {
|
|
85
|
-
*/
|
|
86
|
-
function usingLookup(lookup) {
|
|
87
|
-
return (row) => Object.keys(row).reduce((acc, key) => ({ ...acc, [lookup[key]]: row[key] }), {})
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Renames the keys of an object using a function.
|
|
69
|
+
* @param {OptionsToRenameKeys} options - Options to rename keys
|
|
70
|
+
* @returns {Object} - A renamer object.
|
|
92
71
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
72
|
+
* Example:
|
|
73
|
+
*
|
|
74
|
+
* renamer({ prefix: 'a', keys: ['a', 'b'] }).get()
|
|
75
|
+
* renamer({ prefix: 'a', separator: '-' }).get()
|
|
76
|
+
* renamer().setPrefix('a')
|
|
77
|
+
* .setSeparator('-')
|
|
78
|
+
* .setKeys(['a', 'b'])
|
|
79
|
+
* .get()
|
|
80
|
+
* renamer().setPrefix('a').get()
|
|
81
|
+
* renamer().setSuffix('b').get()
|
|
82
|
+
* renamer().setKeys(['a', 'b']).get()
|
|
95
83
|
*/
|
|
96
|
-
function
|
|
97
|
-
|
|
84
|
+
export function renamer(options = {}) {
|
|
85
|
+
const { prefix, suffix, separator = '_', keys = [] } = options
|
|
86
|
+
const rename = getRenamer(prefix, suffix, separator)
|
|
87
|
+
const renameObject = getObjectRenamer(rename, keys)
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
get: () => ({ rename, renameObject }),
|
|
91
|
+
setPrefix: (value) => renamer({ ...omit(['suffix'], options), prefix: value }),
|
|
92
|
+
setSuffix: (value) => renamer({ ...omit(['prefix'], options), suffix: value }),
|
|
93
|
+
setKeys: (value) => renamer({ ...options, keys: value }),
|
|
94
|
+
setSeparator: (value) => renamer({ ...options, separator: value })
|
|
95
|
+
}
|
|
98
96
|
}
|
package/src/rollup.js
CHANGED
|
@@ -1,27 +1,5 @@
|
|
|
1
1
|
import { omit, pick, mergeLeft, map, difference, uniq, pipe } from 'ramda'
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Groups data by specified keys.
|
|
5
|
-
*
|
|
6
|
-
* @param {Array} data - An array of data points.
|
|
7
|
-
* @param {Array} groupByKeys - Keys to group the data by.
|
|
8
|
-
* @param {Array} summaries - An array of summary objects to define aggregation operations.
|
|
9
|
-
* @returns {Object} Grouped data object with aggregation placeholders.
|
|
10
|
-
*/
|
|
11
|
-
export function groupDataByKeys(data, groupByKeys, summaries) {
|
|
12
|
-
const keysExtractor = pick(groupByKeys)
|
|
13
|
-
const groupedData = data.reduce((grouped, row) => {
|
|
14
|
-
const key = JSON.stringify(keysExtractor(row))
|
|
15
|
-
if (!grouped[key]) {
|
|
16
|
-
grouped[key] = { ...keysExtractor(row), ...initialValues(summaries) }
|
|
17
|
-
}
|
|
18
|
-
addToSummaries(grouped[key], row, summaries)
|
|
19
|
-
return grouped
|
|
20
|
-
}, {})
|
|
21
|
-
|
|
22
|
-
return Object.values(groupedData)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
3
|
/**
|
|
26
4
|
* Sets initial values for summaries.
|
|
27
5
|
*
|
|
@@ -49,6 +27,28 @@ export function addToSummaries(group, row, summaries) {
|
|
|
49
27
|
})
|
|
50
28
|
}
|
|
51
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Groups data by specified keys.
|
|
32
|
+
*
|
|
33
|
+
* @param {Array} data - An array of data points.
|
|
34
|
+
* @param {Array} groupByKeys - Keys to group the data by.
|
|
35
|
+
* @param {Array} summaries - An array of summary objects to define aggregation operations.
|
|
36
|
+
* @returns {Object} Grouped data object with aggregation placeholders.
|
|
37
|
+
*/
|
|
38
|
+
export function groupDataByKeys(data, groupByKeys, summaries) {
|
|
39
|
+
const keysExtractor = pick(groupByKeys)
|
|
40
|
+
const groupedData = data.reduce((grouped, row) => {
|
|
41
|
+
const key = JSON.stringify(keysExtractor(row))
|
|
42
|
+
if (!grouped[key]) {
|
|
43
|
+
grouped[key] = { ...keysExtractor(row), ...initialValues(summaries) }
|
|
44
|
+
}
|
|
45
|
+
addToSummaries(grouped[key], row, summaries)
|
|
46
|
+
return grouped
|
|
47
|
+
}, {})
|
|
48
|
+
|
|
49
|
+
return Object.values(groupedData)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
52
|
/**
|
|
53
53
|
* Fills grouped data with aligned data if specified in configuration.
|
|
54
54
|
*
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2022 Jerry Thomas
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|