diff options
author | (quasar) nebula <qznebula@protonmail.com> | 2023-10-01 17:01:21 -0300 |
---|---|---|
committer | (quasar) nebula <qznebula@protonmail.com> | 2023-10-01 17:04:16 -0300 |
commit | ab7591e45e7e31b4e2c0e2f81e224672145993fa (patch) | |
tree | 11dcccc57e71424baa3b73a3eca58dabc56dca05 /src/data/composite/data | |
parent | dfcf911501211bbfc64b8ce6a964b70c6406447f (diff) |
data, test: refactor utilities into own file
Primarily for more precies test coverage mapping, but also to make navigation a bit easier and consolidate complex functions with lots of imports out of the same space as other, more simple or otherwise specialized files.
Diffstat (limited to 'src/data/composite/data')
-rw-r--r-- | src/data/composite/data/excludeFromList.js | 56 | ||||
-rw-r--r-- | src/data/composite/data/fillMissingListItems.js | 51 | ||||
-rw-r--r-- | src/data/composite/data/index.js | 8 | ||||
-rw-r--r-- | src/data/composite/data/withFlattenedList.js | 47 | ||||
-rw-r--r-- | src/data/composite/data/withPropertiesFromList.js | 92 | ||||
-rw-r--r-- | src/data/composite/data/withPropertiesFromObject.js | 87 | ||||
-rw-r--r-- | src/data/composite/data/withPropertyFromList.js | 56 | ||||
-rw-r--r-- | src/data/composite/data/withPropertyFromObject.js | 69 | ||||
-rw-r--r-- | src/data/composite/data/withUnflattenedList.js | 62 |
9 files changed, 528 insertions, 0 deletions
diff --git a/src/data/composite/data/excludeFromList.js b/src/data/composite/data/excludeFromList.js new file mode 100644 index 00000000..718f2294 --- /dev/null +++ b/src/data/composite/data/excludeFromList.js @@ -0,0 +1,56 @@ +// Filters particular values out of a list. Note that this will always +// completely skip over null, but can be used to filter out any other +// primitive or object value. +// +// See also: +// - fillMissingListItems +// +// More list utilities: +// - withFlattenedList +// - withPropertyFromList +// - withPropertiesFromList +// - withUnflattenedList +// + +import {input, templateCompositeFrom} from '#composite'; +import {empty} from '#sugar'; + +export default templateCompositeFrom({ + annotation: `excludeFromList`, + + inputs: { + list: input(), + + item: input({defaultValue: null}), + items: input({type: 'array', defaultValue: null}), + }, + + outputs: ({ + [input.staticDependency('list')]: list, + }) => [list ?? '#list'], + + steps: () => [ + { + dependencies: [ + input.staticDependency('list'), + input('list'), + input('item'), + input('items'), + ], + + compute: (continuation, { + [input.staticDependency('list')]: listName, + [input('list')]: listContents, + [input('item')]: excludeItem, + [input('items')]: excludeItems, + }) => continuation({ + [listName ?? '#list']: + listContents.filter(item => { + if (excludeItem !== null && item === excludeItem) return false; + if (!empty(excludeItems) && excludeItems.includes(item)) return false; + return true; + }), + }), + }, + ], +}); diff --git a/src/data/composite/data/fillMissingListItems.js b/src/data/composite/data/fillMissingListItems.js new file mode 100644 index 00000000..c06eceda --- /dev/null +++ b/src/data/composite/data/fillMissingListItems.js @@ -0,0 +1,51 @@ +// Replaces items of a list, which are null or undefined, with some fallback +// value. By default, this replaces the passed dependency. +// +// See also: +// - excludeFromList +// +// More list utilities: +// - withFlattenedList +// - withPropertyFromList +// - withPropertiesFromList +// - withUnflattenedList +// + +import {input, templateCompositeFrom} from '#composite'; + +export default templateCompositeFrom({ + annotation: `fillMissingListItems`, + + inputs: { + list: input({type: 'array'}), + fill: input({acceptsNull: true}), + }, + + outputs: ({ + [input.staticDependency('list')]: list, + }) => [list ?? '#list'], + + steps: () => [ + { + dependencies: [input('list'), input('fill')], + compute: (continuation, { + [input('list')]: list, + [input('fill')]: fill, + }) => continuation({ + ['#filled']: + list.map(item => item ?? fill), + }), + }, + + { + dependencies: [input.staticDependency('list'), '#filled'], + compute: (continuation, { + [input.staticDependency('list')]: list, + ['#filled']: filled, + }) => continuation({ + [list ?? '#list']: + filled, + }), + }, + ], +}); diff --git a/src/data/composite/data/index.js b/src/data/composite/data/index.js new file mode 100644 index 00000000..ecd05129 --- /dev/null +++ b/src/data/composite/data/index.js @@ -0,0 +1,8 @@ +export {default as excludeFromList} from './excludeFromList.js'; +export {default as fillMissingListItems} from './fillMissingListItems.js'; +export {default as withFlattenedList} from './withFlattenedList.js'; +export {default as withPropertiesFromList} from './withPropertiesFromList.js'; +export {default as withPropertiesFromObject} from './withPropertiesFromObject.js'; +export {default as withPropertyFromList} from './withPropertyFromList.js'; +export {default as withPropertyFromObject} from './withPropertyFromObject.js'; +export {default as withUnflattenedList} from './withUnflattenedList.js'; diff --git a/src/data/composite/data/withFlattenedList.js b/src/data/composite/data/withFlattenedList.js new file mode 100644 index 00000000..b08edb4e --- /dev/null +++ b/src/data/composite/data/withFlattenedList.js @@ -0,0 +1,47 @@ +// Flattens an array with one level of nested arrays, providing as dependencies +// both the flattened array as well as the original starting indices of each +// successive source array. +// +// See also: +// - withFlattenedList +// +// More list utilities: +// - excludeFromList +// - fillMissingListItems +// - withPropertyFromList +// - withPropertiesFromList +// + +import {input, templateCompositeFrom} from '#composite'; + +export default templateCompositeFrom({ + annotation: `withFlattenedList`, + + inputs: { + list: input({type: 'array'}), + }, + + outputs: ['#flattenedList', '#flattenedIndices'], + + steps: () => [ + { + dependencies: [input('list')], + compute(continuation, { + [input('list')]: sourceList, + }) { + const flattenedList = sourceList.flat(); + const indices = []; + let lastEndIndex = 0; + for (const {length} of sourceList) { + indices.push(lastEndIndex); + lastEndIndex += length; + } + + return continuation({ + ['#flattenedList']: flattenedList, + ['#flattenedIndices']: indices, + }); + }, + }, + ], +}); diff --git a/src/data/composite/data/withPropertiesFromList.js b/src/data/composite/data/withPropertiesFromList.js new file mode 100644 index 00000000..76ba696c --- /dev/null +++ b/src/data/composite/data/withPropertiesFromList.js @@ -0,0 +1,92 @@ +// Gets the listed properties from each of a list of objects, providing lists +// of property values each into a dependency prefixed with the same name as the +// list (by default). +// +// Like withPropertyFromList, this doesn't alter indices. +// +// See also: +// - withPropertiesFromObject +// - withPropertyFromList +// +// More list utilities: +// - excludeFromList +// - fillMissingListItems +// - withFlattenedList +// - withUnflattenedList +// + +import {input, templateCompositeFrom} from '#composite'; +import {isString, validateArrayItems} from '#validators'; + +export default templateCompositeFrom({ + annotation: `withPropertiesFromList`, + + inputs: { + list: input({type: 'array'}), + + properties: input({ + validate: validateArrayItems(isString), + }), + + prefix: input.staticValue({type: 'string', defaultValue: null}), + }, + + outputs: ({ + [input.staticDependency('list')]: list, + [input.staticValue('properties')]: properties, + [input.staticValue('prefix')]: prefix, + }) => + (properties + ? properties.map(property => + (prefix + ? `${prefix}.${property}` + : list + ? `${list}.${property}` + : `#list.${property}`)) + : ['#lists']), + + steps: () => [ + { + dependencies: [input('list'), input('properties')], + compute: (continuation, { + [input('list')]: list, + [input('properties')]: properties, + }) => continuation({ + ['#lists']: + Object.fromEntries( + properties.map(property => [ + property, + list.map(item => item[property] ?? null), + ])), + }), + }, + + { + dependencies: [ + input.staticDependency('list'), + input.staticValue('properties'), + input.staticValue('prefix'), + '#lists', + ], + + compute: (continuation, { + [input.staticDependency('list')]: list, + [input.staticValue('properties')]: properties, + [input.staticValue('prefix')]: prefix, + ['#lists']: lists, + }) => + (properties + ? continuation( + Object.fromEntries( + properties.map(property => [ + (prefix + ? `${prefix}.${property}` + : list + ? `${list}.${property}` + : `#list.${property}`), + lists[property], + ]))) + : continuation({'#lists': lists})), + }, + ], +}); diff --git a/src/data/composite/data/withPropertiesFromObject.js b/src/data/composite/data/withPropertiesFromObject.js new file mode 100644 index 00000000..21726b58 --- /dev/null +++ b/src/data/composite/data/withPropertiesFromObject.js @@ -0,0 +1,87 @@ +// Gets the listed properties from some object, providing each property's value +// as a dependency prefixed with the same name as the object (by default). +// If the object itself is null, all provided dependencies will be null; +// if it's missing only select properties, those will be provided as null. +// +// See also: +// - withPropertiesFromList +// - withPropertyFromObject +// + +import {input, templateCompositeFrom} from '#composite'; +import {isString, validateArrayItems} from '#validators'; + +export default templateCompositeFrom({ + annotation: `withPropertiesFromObject`, + + inputs: { + object: input({type: 'object', acceptsNull: true}), + + properties: input({ + type: 'array', + validate: validateArrayItems(isString), + }), + + prefix: input.staticValue({type: 'string', defaultValue: null}), + }, + + outputs: ({ + [input.staticDependency('object')]: object, + [input.staticValue('properties')]: properties, + [input.staticValue('prefix')]: prefix, + }) => + (properties + ? properties.map(property => + (prefix + ? `${prefix}.${property}` + : object + ? `${object}.${property}` + : `#object.${property}`)) + : ['#object']), + + steps: () => [ + { + dependencies: [input('object'), input('properties')], + compute: (continuation, { + [input('object')]: object, + [input('properties')]: properties, + }) => continuation({ + ['#entries']: + (object === null + ? properties.map(property => [property, null]) + : properties.map(property => [property, object[property]])), + }), + }, + + { + dependencies: [ + input.staticDependency('object'), + input.staticValue('properties'), + input.staticValue('prefix'), + '#entries', + ], + + compute: (continuation, { + [input.staticDependency('object')]: object, + [input.staticValue('properties')]: properties, + [input.staticValue('prefix')]: prefix, + ['#entries']: entries, + }) => + (properties + ? continuation( + Object.fromEntries( + entries.map(([property, value]) => [ + (prefix + ? `${prefix}.${property}` + : object + ? `${object}.${property}` + : `#object.${property}`), + value ?? null, + ]))) + : continuation({ + ['#object']: + Object.fromEntries(entries), + })), + }, + ], +}); diff --git a/src/data/composite/data/withPropertyFromList.js b/src/data/composite/data/withPropertyFromList.js new file mode 100644 index 00000000..3ce05fdf --- /dev/null +++ b/src/data/composite/data/withPropertyFromList.js @@ -0,0 +1,56 @@ +// Gets a property from each of a list of objects (in a dependency) and +// provides the results. +// +// This doesn't alter any list indices, so positions which were null in the +// original list are kept null here. Objects which don't have the specified +// property are retained in-place as null. +// +// See also: +// - withPropertiesFromList +// - withPropertyFromObject +// +// More list utilities: +// - excludeFromList +// - fillMissingListItems +// - withFlattenedList +// - withUnflattenedList +// + +import {empty} from '#sugar'; + +// todo: OUHHH THIS ONE'S NOT UPDATED YET LOL +export default function({ + list, + property, + into = null, +}) { + into ??= + (list.startsWith('#') + ? `${list}.${property}` + : `#${list}.${property}`); + + return { + annotation: `withPropertyFromList`, + flags: {expose: true, compose: true}, + + expose: { + mapDependencies: {list}, + mapContinuation: {into}, + options: {property}, + + compute(continuation, {list, '#options': {property}}) { + if (list === undefined || empty(list)) { + return continuation({into: []}); + } + + return continuation({ + into: + list.map(item => + (item === null || item === undefined + ? null + : item[property] ?? null)), + }); + }, + }, + }; +} diff --git a/src/data/composite/data/withPropertyFromObject.js b/src/data/composite/data/withPropertyFromObject.js new file mode 100644 index 00000000..b31bab15 --- /dev/null +++ b/src/data/composite/data/withPropertyFromObject.js @@ -0,0 +1,69 @@ +// Gets a property of some object (in a dependency) and provides that value. +// If the object itself is null, or the object doesn't have the listed property, +// the provided dependency will also be null. +// +// See also: +// - withPropertiesFromObject +// - withPropertyFromList +// + +import {input, templateCompositeFrom} from '#composite'; + +export default templateCompositeFrom({ + annotation: `withPropertyFromObject`, + + inputs: { + object: input({type: 'object', acceptsNull: true}), + property: input({type: 'string'}), + }, + + outputs: ({ + [input.staticDependency('object')]: object, + [input.staticValue('property')]: property, + }) => + (object && property + ? (object.startsWith('#') + ? [`${object}.${property}`] + : [`#${object}.${property}`]) + : ['#value']), + + steps: () => [ + { + dependencies: [ + input.staticDependency('object'), + input.staticValue('property'), + ], + + compute: (continuation, { + [input.staticDependency('object')]: object, + [input.staticValue('property')]: property, + }) => continuation({ + '#output': + (object && property + ? (object.startsWith('#') + ? `${object}.${property}` + : `#${object}.${property}`) + : '#value'), + }), + }, + + { + dependencies: [ + '#output', + input('object'), + input('property'), + ], + + compute: (continuation, { + ['#output']: output, + [input('object')]: object, + [input('property')]: property, + }) => continuation({ + [output]: + (object === null + ? null + : object[property] ?? null), + }), + }, + ], +}); diff --git a/src/data/composite/data/withUnflattenedList.js b/src/data/composite/data/withUnflattenedList.js new file mode 100644 index 00000000..3cfc247b --- /dev/null +++ b/src/data/composite/data/withUnflattenedList.js @@ -0,0 +1,62 @@ +// After mapping the contents of a flattened array in-place (being careful to +// retain the original indices by replacing unmatched results with null instead +// of filtering them out), this function allows for recombining them. It will +// filter out null and undefined items by default (pass {filter: false} to +// disable this). + +import {input, templateCompositeFrom} from '#composite'; +import {isWholeNumber, validateArrayItems} from '#validators'; + +export default templateCompositeFrom({ + annotation: `withUnflattenedList`, + + inputs: { + list: input({ + type: 'array', + defaultDependency: '#flattenedList', + }), + + indices: input({ + validate: validateArrayItems(isWholeNumber), + defaultDependency: '#flattenedIndices', + }), + + filter: input({ + type: 'boolean', + defaultValue: true, + }), + }, + + outputs: ['#unflattenedList'], + + steps: () => [ + { + dependencies: [input('list'), input('indices'), input('filter')], + compute(continuation, { + [input('list')]: list, + [input('indices')]: indices, + [input('filter')]: filter, + }) { + const unflattenedList = []; + + for (let i = 0; i < indices.length; i++) { + const startIndex = indices[i]; + const endIndex = + (i === indices.length - 1 + ? list.length + : indices[i + 1]); + + const values = list.slice(startIndex, endIndex); + unflattenedList.push( + (filter + ? values.filter(value => value !== null && value !== undefined) + : values)); + } + + return continuation({ + ['#unflattenedList']: unflattenedList, + }); + }, + }, + ], +}); |