From c28fa78963f1d2c23c8b73db9d0a025ab5bffa18 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Thu, 10 Feb 2022 16:55:15 +0100 Subject: [PATCH] feat(@vates/predicates): utils to compose predicates --- @vates/predicates/.npmignore | 1 + @vates/predicates/README.md | 90 ++++++++++++++++++++++++++++++++++ @vates/predicates/USAGE.md | 57 +++++++++++++++++++++ @vates/predicates/index.js | 69 ++++++++++++++++++++++++++ @vates/predicates/package.json | 36 ++++++++++++++ CHANGELOG.unreleased.md | 1 + 6 files changed, 254 insertions(+) create mode 120000 @vates/predicates/.npmignore create mode 100644 @vates/predicates/README.md create mode 100644 @vates/predicates/USAGE.md create mode 100644 @vates/predicates/index.js create mode 100644 @vates/predicates/package.json diff --git a/@vates/predicates/.npmignore b/@vates/predicates/.npmignore new file mode 120000 index 000000000..008d1b9b9 --- /dev/null +++ b/@vates/predicates/.npmignore @@ -0,0 +1 @@ +../../scripts/npmignore \ No newline at end of file diff --git a/@vates/predicates/README.md b/@vates/predicates/README.md new file mode 100644 index 000000000..b83161a3b --- /dev/null +++ b/@vates/predicates/README.md @@ -0,0 +1,90 @@ + + +# @vates/predicates + +[![Package Version](https://badgen.net/npm/v/@vates/predicates)](https://npmjs.org/package/@vates/predicates) ![License](https://badgen.net/npm/license/@vates/predicates) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/predicates)](https://bundlephobia.com/result?p=@vates/predicates) [![Node compatibility](https://badgen.net/npm/node/@vates/predicates)](https://npmjs.org/package/@vates/predicates) + +> Utilities to compose predicates + +## Install + +Installation of the [npm package](https://npmjs.org/package/@vates/predicates): + +``` +> npm install --save @vates/predicates +``` + +## Usage + +`undefined` predicates are ignored and `undefined` is returned if all predicates are `undefined`, this permits the most efficient composition: + +```js +const compositePredicate = every(undefined, some(predicate2, undefined)) + +// ends up as + +const compositePredicate = predicate2 +``` + +Predicates can also be passed wrapped in an array: + +```js +const compositePredicate = every([predicate1, some([predicate2, predicate3])]) +``` + +`this` and all arguments are passed to the nested predicates. + +### `every(predicates)` + +> Returns a predicate that returns `true` iff every predicate returns `true`. + +```js +const isBetween3And7 = every( + n => n >= 3, + n => n <= 7 +) + +isBetween3And10(0) +// → false + +isBetween3And10(5) +// → true + +isBetween3And10(10) +// → false +``` + +### `some(predicates)` + +> Returns a predicate that returns `true` iff some predicate returns `true`. + +```js +const isAliceOrBob = some( + name => name === 'Alice', + name => name === 'Bob' +) + +isAliceOrBob('Alice') +// → true + +isAliceOrBob('Bob') +// → true + +isAliceOrBob('Oscar') +// → false +``` + +## Contributions + +Contributions are _very_ welcomed, either on the documentation or on +the code. + +You may: + +- report any [issue](https://github.com/vatesfr/xen-orchestra/issues) + you've encountered; +- fork and create a pull request. + +## License + +[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr) diff --git a/@vates/predicates/USAGE.md b/@vates/predicates/USAGE.md new file mode 100644 index 000000000..be38f3ceb --- /dev/null +++ b/@vates/predicates/USAGE.md @@ -0,0 +1,57 @@ +`undefined` predicates are ignored and `undefined` is returned if all predicates are `undefined`, this permits the most efficient composition: + +```js +const compositePredicate = every(undefined, some(predicate2, undefined)) + +// ends up as + +const compositePredicate = predicate2 +``` + +Predicates can also be passed wrapped in an array: + +```js +const compositePredicate = every([predicate1, some([predicate2, predicate3])]) +``` + +`this` and all arguments are passed to the nested predicates. + +### `every(predicates)` + +> Returns a predicate that returns `true` iff every predicate returns `true`. + +```js +const isBetween3And7 = every( + n => n >= 3, + n => n <= 7 +) + +isBetween3And10(0) +// → false + +isBetween3And10(5) +// → true + +isBetween3And10(10) +// → false +``` + +### `some(predicates)` + +> Returns a predicate that returns `true` iff some predicate returns `true`. + +```js +const isAliceOrBob = some( + name => name === 'Alice', + name => name === 'Bob' +) + +isAliceOrBob('Alice') +// → true + +isAliceOrBob('Bob') +// → true + +isAliceOrBob('Oscar') +// → false +``` diff --git a/@vates/predicates/index.js b/@vates/predicates/index.js new file mode 100644 index 000000000..b7ba215a7 --- /dev/null +++ b/@vates/predicates/index.js @@ -0,0 +1,69 @@ +const { + isArray, + prototype: { filter }, +} = Array + +class InvalidPredicate extends TypeError { + constructor(value) { + super('not a valid predicate') + this.value = value + } +} + +function isDefinedPredicate(value) { + if (value === undefined) { + return false + } + + if (typeof value !== 'function') { + throw new InvalidPredicate(value) + } + + return true +} + +function handleArgs() { + let predicates + if (!(arguments.length === 1 && isArray((predicates = arguments[0])))) { + predicates = arguments + } + return filter.call(predicates, isDefinedPredicate) +} + +exports.every = function every() { + const predicates = handleArgs.apply(this, arguments) + const n = predicates.length + if (n === 0) { + return + } + if (n === 1) { + return predicates[0] + } + return function everyPredicate() { + for (let i = 0; i < n; ++i) { + if (!predicates[0].apply(this, arguments)) { + return false + } + } + return true + } +} + +exports.some = function some() { + const predicates = handleArgs.apply(this, arguments) + const n = predicates.length + if (n === 0) { + return + } + if (n === 1) { + return predicates[0] + } + return function somePredicate() { + for (let i = 0; i < n; ++i) { + if (predicates[0].apply(this, arguments)) { + return true + } + } + return false + } +} diff --git a/@vates/predicates/package.json b/@vates/predicates/package.json new file mode 100644 index 000000000..101dda0b7 --- /dev/null +++ b/@vates/predicates/package.json @@ -0,0 +1,36 @@ +{ + "private": false, + "name": "@vates/predicates", + "description": "Utilities to compose predicates", + "keywords": [ + "and", + "combine", + "compose", + "every", + "function", + "functions", + "or", + "predicate", + "predicates", + "some" + ], + "homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@vates/predicates", + "bugs": "https://github.com/vatesfr/xen-orchestra/issues", + "repository": { + "directory": "@vates/predicates", + "type": "git", + "url": "https://github.com/vatesfr/xen-orchestra.git" + }, + "author": { + "name": "Vates SAS", + "url": "https://vates.fr" + }, + "license": "ISC", + "version": "0.0.0", + "engines": { + "node": ">=6" + }, + "scripts": { + "postversion": "npm publish --access public" + } +} diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index c8dc96246..65b1a44bc 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -28,6 +28,7 @@ > > In case of conflict, the highest (lowest in previous list) `$version` wins. +- @vates/predicates major - @xen-orchestra/mixins minor - @xen-orchestra/backups patch - @xen-orchestra/proxy patch