feat(complex-matcher): -addPropertyClause, +getPropertyClausesStrings, +setPropertyClause
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import every from 'lodash/every'
|
||||
import findIndex from 'lodash/findIndex'
|
||||
import filter from 'lodash/filter'
|
||||
import forEach from 'lodash/forEach'
|
||||
import isArray from 'lodash/isArray'
|
||||
import isPlainObject from 'lodash/isPlainObject'
|
||||
import isString from 'lodash/isString'
|
||||
import map from 'lodash/map'
|
||||
import some from 'lodash/some'
|
||||
|
||||
import filterReduce from '../filter-reduce'
|
||||
import invoke from '../invoke'
|
||||
|
||||
// ===================================================================
|
||||
@@ -221,58 +223,107 @@ export const parse = invoke(() => {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const addPropertyClause = invoke(() => {
|
||||
const addAndClause = (node, child) => node.type === 'and'
|
||||
? createAnd(node.children.concat(child))
|
||||
: createAnd([ node, child ])
|
||||
const addOrClause = (node, child) => node.type === 'or'
|
||||
? createOr(node.children.concat(child))
|
||||
: createOr([ node, child ])
|
||||
const _getPropertyClauseStrings = ({ child }) => {
|
||||
const { type } = child
|
||||
|
||||
const addOrClauseToProperty = (node, child) => createProperty(
|
||||
node.name,
|
||||
addOrClause(node.child, child)
|
||||
if (type === 'or') {
|
||||
const strings = []
|
||||
forEach(child.children, child => {
|
||||
if (child.type === 'string') {
|
||||
strings.push(child.value)
|
||||
}
|
||||
})
|
||||
return strings
|
||||
}
|
||||
|
||||
if (type === 'string') {
|
||||
return [ child.value ]
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
// Find possible values for property clauses in a and clause.
|
||||
export const getPropertyClausesStrings = function () {
|
||||
if (!this) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const { type } = this
|
||||
|
||||
if (type === 'property') {
|
||||
return {
|
||||
[this.name]: _getPropertyClauseStrings(this)
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'and') {
|
||||
const strings = {}
|
||||
forEach(this.children, node => {
|
||||
if (node.type === 'property') {
|
||||
const { name } = node
|
||||
const values = strings[name]
|
||||
if (values) {
|
||||
values.push.apply(values, _getPropertyClauseStrings(node))
|
||||
} else {
|
||||
strings[name] = _getPropertyClauseStrings(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
return strings
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const removePropertyClause = function (name) {
|
||||
let type
|
||||
if (
|
||||
!this ||
|
||||
(type = this.type) === 'property' && this.name === name
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
if (type === 'and') {
|
||||
return createAnd(filter(this.children, node =>
|
||||
node.type !== 'property' || node.name !== name
|
||||
))
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const _addAndClause = (node, child, predicate, reducer) =>
|
||||
createAnd(filterReduce(
|
||||
node.type === 'and'
|
||||
? node.children
|
||||
: [ node ],
|
||||
predicate,
|
||||
reducer,
|
||||
child
|
||||
))
|
||||
|
||||
export const setPropertyClause = function (name, child) {
|
||||
const property = createProperty(
|
||||
name,
|
||||
isString(child) ? createString(child) : child
|
||||
)
|
||||
|
||||
const addPropertyClause_ = (node, name, child) => {
|
||||
if (!node) {
|
||||
return createProperty(name, child)
|
||||
}
|
||||
|
||||
const { type } = node
|
||||
|
||||
if (type === 'and') {
|
||||
const { children } = node
|
||||
const i = findIndex(children, node =>
|
||||
node.type === 'property' && node.name === name
|
||||
)
|
||||
return i === -1
|
||||
? createAnd([
|
||||
...children,
|
||||
createProperty(name, child)
|
||||
])
|
||||
: createAnd([
|
||||
...children.slice(0, i),
|
||||
addOrClauseToProperty(children[i], child),
|
||||
...children.slice(i + 1)
|
||||
])
|
||||
}
|
||||
|
||||
if (type === 'property' && node.name === name) {
|
||||
return addOrClauseToProperty(node, child)
|
||||
}
|
||||
|
||||
return addAndClause(node, createProperty(name, child))
|
||||
if (!this) {
|
||||
return property
|
||||
}
|
||||
|
||||
return function addPropertyClause (name, child) {
|
||||
if (isString(child)) {
|
||||
child = createString(child)
|
||||
}
|
||||
|
||||
return addPropertyClause_(this, name, child)
|
||||
}
|
||||
})
|
||||
return _addAndClause(
|
||||
this,
|
||||
property,
|
||||
node => node.type === 'property' && node.name === name,
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
@@ -324,7 +375,7 @@ export const toString = invoke(() => {
|
||||
and: ({ children }) => toStringGroup(children),
|
||||
not: ({ child }) => `!${toString(child)}`,
|
||||
or: ({ children }) => `|${toStringGroup(children)}`,
|
||||
property: ({ name, child }) => `${name}:${toString(child)}`,
|
||||
property: ({ name, child }) => `${toString(createString(name))}:${toString(child)}`,
|
||||
string: ({ value }) => isRawString(value)
|
||||
? value
|
||||
: `"${value.replace(/\\|"/g, match => `\\${match}`)}"`
|
||||
@@ -334,9 +385,11 @@ export const toString = invoke(() => {
|
||||
|
||||
// Special case for a root “and”: do not add braces.
|
||||
return function () {
|
||||
return this.type === 'and'
|
||||
? toStringTerms(this.children)
|
||||
: toString(this)
|
||||
return !this
|
||||
? ''
|
||||
: this.type === 'and'
|
||||
? toStringTerms(this.children)
|
||||
: toString(this)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import test from 'ava'
|
||||
|
||||
import {
|
||||
addPropertyClause,
|
||||
getPropertyClausesStrings,
|
||||
parse,
|
||||
setPropertyClause,
|
||||
toString
|
||||
} from './'
|
||||
import {
|
||||
@@ -10,25 +11,14 @@ import {
|
||||
pattern
|
||||
} from './index.fixtures'
|
||||
|
||||
test('addPropertyClause', t => {
|
||||
t.is(
|
||||
null::addPropertyClause('foo', 'bar')::toString(),
|
||||
'foo:bar'
|
||||
)
|
||||
|
||||
t.is(
|
||||
parse('baz')::addPropertyClause('foo', 'bar')::toString(),
|
||||
'baz foo:bar'
|
||||
)
|
||||
|
||||
t.is(
|
||||
parse('plip foo:baz plop')::addPropertyClause('foo', 'bar')::toString(),
|
||||
'plip foo:|(baz bar) plop'
|
||||
)
|
||||
|
||||
t.is(
|
||||
parse('foo:|(baz plop)')::addPropertyClause('foo', 'bar')::toString(),
|
||||
'foo:|(baz plop bar)'
|
||||
test('getPropertyClausesStrings', t => {
|
||||
let tmp = parse('foo bar:baz baz:|(foo bar)')::getPropertyClausesStrings()
|
||||
t.deepEqual(
|
||||
tmp,
|
||||
{
|
||||
bar: [ 'baz' ],
|
||||
baz: [ 'foo', 'bar' ]
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -36,6 +26,28 @@ test('parse', t => {
|
||||
t.deepEqual(parse(pattern), ast)
|
||||
})
|
||||
|
||||
test('setPropertyClause', t => {
|
||||
t.is(
|
||||
null::setPropertyClause('foo', 'bar')::toString(),
|
||||
'foo:bar'
|
||||
)
|
||||
|
||||
t.is(
|
||||
parse('baz')::setPropertyClause('foo', 'bar')::toString(),
|
||||
'baz foo:bar'
|
||||
)
|
||||
|
||||
t.is(
|
||||
parse('plip foo:baz plop')::setPropertyClause('foo', 'bar')::toString(),
|
||||
'plip plop foo:bar'
|
||||
)
|
||||
|
||||
t.is(
|
||||
parse('foo:|(baz plop)')::setPropertyClause('foo', 'bar')::toString(),
|
||||
'foo:bar'
|
||||
)
|
||||
})
|
||||
|
||||
test('toString', t => {
|
||||
t.is(pattern, ast::toString())
|
||||
})
|
||||
|
||||
45
src/common/filter-reduce.js
Normal file
45
src/common/filter-reduce.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import findIndex from 'lodash/findIndex'
|
||||
import identity from 'lodash/identity'
|
||||
|
||||
// Returns a copy of the array containing:
|
||||
// - the elements which did not matches the predicate
|
||||
// - the result of the reduction of the elements matching the
|
||||
// predicates
|
||||
//
|
||||
// As a special case, if the predicate is not provided, it is
|
||||
// considered to have not matched.
|
||||
const filterReduce = (array, predicate, reducer, initial) => {
|
||||
const { length } = array
|
||||
let i
|
||||
if (
|
||||
!length ||
|
||||
!predicate ||
|
||||
(i = findIndex(array, predicate)) === -1
|
||||
) {
|
||||
return initial == null
|
||||
? array.slice(0)
|
||||
: array.concat(initial)
|
||||
}
|
||||
|
||||
if (reducer == null) {
|
||||
reducer = identity
|
||||
}
|
||||
|
||||
const result = array.slice(0, i)
|
||||
let value = initial == null
|
||||
? array[i]
|
||||
: reducer(initial, array[i], i, array)
|
||||
|
||||
for (i = i + 1; i < length; ++i) {
|
||||
const current = array[i]
|
||||
if (predicate(current, i, array)) {
|
||||
value = reducer(value, current, i, array)
|
||||
} else {
|
||||
result.push(current)
|
||||
}
|
||||
}
|
||||
|
||||
result.push(value)
|
||||
return result
|
||||
}
|
||||
export { filterReduce as default }
|
||||
28
src/common/filter-reduce.spec.js
Normal file
28
src/common/filter-reduce.spec.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import test from 'ava'
|
||||
|
||||
import filterReduce from './filter-reduce'
|
||||
|
||||
const add = (a, b) => a + b
|
||||
const data = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
|
||||
const isEven = x => !(x & 1)
|
||||
|
||||
test('filterReduce', t => {
|
||||
// Returns all elements not matching the predicate and the result of
|
||||
// a reduction over those who do.
|
||||
t.deepEqual(
|
||||
filterReduce(data, isEven, add),
|
||||
[ 1, 3, 5, 7, 9, 20 ]
|
||||
)
|
||||
|
||||
// The default reducer is the identity.
|
||||
t.deepEqual(
|
||||
filterReduce(data, isEven),
|
||||
[ 1, 3, 5, 7, 9, 0 ]
|
||||
)
|
||||
|
||||
// If an initial value is passed it is used.
|
||||
t.deepEqual(
|
||||
filterReduce(data, isEven, add, 22),
|
||||
[ 1, 3, 5, 7, 9, 42 ]
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user