-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
212 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { fastCartesian } from './fast_cartesian.js' | ||
|
||
// Retrieve arguments passed to the main function for each iteration | ||
export const getArgs = function(iterables) { | ||
const args = fastCartesian(...iterables) | ||
const argsA = args.map(invokeArgs) | ||
return argsA | ||
} | ||
|
||
// If an argument is a function, its return value will be used instead. | ||
// This can be used to generate random input for example (fuzzy testing). | ||
const invokeArgs = function(eachArgs) { | ||
return eachArgs.map(invokeArg) | ||
} | ||
|
||
const invokeArg = function(arg) { | ||
if (typeof arg === 'function') { | ||
return arg() | ||
} | ||
|
||
return arg | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// Does a cartesian product on several iterables. | ||
// Works with any iterable, including arrays, strings, generators, maps, sets. | ||
export const fastCartesian = function(...iterables) { | ||
iterables.forEach(validateIterable) | ||
|
||
if (iterables.length === 0) { | ||
return [] | ||
} | ||
|
||
const result = [] | ||
iterate(iterables, result, [], 0) | ||
return result | ||
} | ||
|
||
const validateIterable = function(iterable) { | ||
if (iterable[Symbol.iterator] === undefined) { | ||
throw new TypeError(`Argument must be iterable: ${iterable}`) | ||
} | ||
} | ||
|
||
// We use imperative code as it faster than functional code, avoiding creating | ||
// extra arrays. We try re-use and mutate arrays as much as possible. | ||
// We need to make sure callers parameters are not mutated though. | ||
/* eslint-disable max-params, fp/no-loops, fp/no-mutating-methods */ | ||
const iterate = function(iterables, result, values, index) { | ||
const iterable = iterables[index] | ||
|
||
if (iterable === undefined) { | ||
result.push(values.slice()) | ||
return | ||
} | ||
|
||
for (const value of iterable) { | ||
values.push(value) | ||
iterate(iterables, result, values, index + 1) | ||
values.pop() | ||
} | ||
} | ||
/* eslint-enable max-params, fp/no-loops, fp/no-mutating-methods */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Parse and validate main input | ||
export const parseInput = function(inputArgs) { | ||
const iterables = inputArgs.slice(0, -1) | ||
|
||
const func = inputArgs[inputArgs.length - 1] | ||
validateFunc(func) | ||
|
||
return { iterables, func } | ||
} | ||
|
||
const validateFunc = function(func) { | ||
if (typeof func !== 'function') { | ||
throw new TypeError(`Last argument must be a function: ${func}`) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { parseInput } from './input.js' | ||
import { getArgs } from './args.js' | ||
import { getSuffixes } from './suffix.js' | ||
|
||
// Repeat a function with a combination of arguments. | ||
// Meant for test-driven development. | ||
export const testEach = function(...inputArgs) { | ||
const { iterables, func } = parseInput(inputArgs) | ||
|
||
const args = getArgs(iterables) | ||
|
||
const suffixes = getSuffixes(iterables) | ||
|
||
const results = args.map((values, index) => func(suffixes[index], ...values)) | ||
|
||
// Can use `Promise.all(results)` if `func` is async | ||
return { args, results } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { serializeArg } from './serialize.js' | ||
|
||
// Transform args into suffix parts | ||
export const reduceParts = function(args) { | ||
const { parts } = args.reduce(reducePart, { parts: [], index: 0 }) | ||
return parts | ||
} | ||
|
||
const reducePart = function({ parts, index }, arg) { | ||
const part = getPart(arg) | ||
const { part: partA, index: indexA } = fixDuplicate({ parts, part, index }) | ||
const partsA = [...parts, partA] | ||
return { parts: partsA, index: indexA } | ||
} | ||
|
||
const getPart = function(arg) { | ||
const part = serializeArg(arg) | ||
const partA = part.trim() | ||
const partB = truncatePart(partA) | ||
return partB | ||
} | ||
|
||
// Make suffix parts short by truncating them | ||
const truncatePart = function(part) { | ||
if (part.length <= MAX_PART_LENGTH) { | ||
return part | ||
} | ||
|
||
const partA = part.slice(0, MAX_PART_LENGTH) | ||
return `${partA}...` | ||
} | ||
|
||
const MAX_PART_LENGTH = 60 | ||
|
||
// Ensure suffix parts are unique by appending an incrementing counter when we | ||
// find duplicates | ||
const fixDuplicate = function({ parts, part, index }) { | ||
if (!isDuplicate({ parts, part })) { | ||
return { part, index } | ||
} | ||
|
||
const indexA = index + 1 | ||
const partA = `${part} ${indexA}` | ||
return { part: partA, index: indexA } | ||
} | ||
|
||
const isDuplicate = function({ parts, part }) { | ||
return parts.some(partA => partA === part) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { inspect } from 'util' | ||
|
||
import { isPlainObject } from './utils.js' | ||
|
||
// Serialize an argument so it can be used as a suffix | ||
export const serializeArg = function(arg) { | ||
// `{ suffix }` can be used to override the default suffix | ||
if (isPlainObject(arg) && typeof arg.suffix === 'string') { | ||
return arg.suffix | ||
} | ||
|
||
return serializeValue(arg) | ||
} | ||
|
||
const serializeValue = function(value) { | ||
if (typeof value === 'string') { | ||
return value | ||
} | ||
|
||
if (typeof value === 'function') { | ||
return serializeFunction(value) | ||
} | ||
|
||
return inspect(value, INSPECT_OPTS) | ||
} | ||
|
||
const serializeFunction = function(func) { | ||
if (func.name === '') { | ||
return 'function' | ||
} | ||
|
||
return func.name | ||
} | ||
|
||
// Make suffix short and on a single line | ||
const INSPECT_OPTS = { | ||
breakLength: Infinity, | ||
depth: 1, | ||
maxArrayLength: 3, | ||
compact: true, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { fastCartesian } from './fast_cartesian.js' | ||
import { reduceParts } from './part.js' | ||
|
||
// Retrieve unique suffixes for each iteration | ||
export const getSuffixes = function(iterables) { | ||
const parts = iterables.map(getParts) | ||
const partsA = fastCartesian(...parts) | ||
const suffixes = partsA.map(joinParts) | ||
return suffixes | ||
} | ||
|
||
const getParts = function(iterable) { | ||
const args = [...iterable] | ||
return reduceParts(args) | ||
} | ||
|
||
const joinParts = function(parts) { | ||
const suffix = parts.join(' ') | ||
return `| ${suffix}` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// Is a plain object, including `Object.create(null)` | ||
export const isPlainObject = function(value) { | ||
return ( | ||
typeof value === 'object' && | ||
value !== null && | ||
(value.constructor === Object || value.constructor === undefined) | ||
) | ||
} |