chore(package): use complex-matcher package (#2536)

This commit is contained in:
Julien Fontanet 2017-12-27 11:16:39 +01:00 committed by GitHub
parent ef17cb1c6c
commit 9aed4f6fba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 45 additions and 504 deletions

View File

@ -57,6 +57,7 @@
"chartist-plugin-legend": "^0.6.1",
"chartist-plugin-tooltip": "0.0.11",
"classnames": "^2.2.3",
"complex-matcher": "^0.1.0",
"cookies-js": "^1.2.2",
"d3": "^4.12.0",
"dependency-check": "^2.9.2",

View File

@ -1,12 +0,0 @@
import { parse, toString } from './'
import { ast, pattern } from './index.fixtures'
export default ({ benchmark }) => {
benchmark('parse', () => {
parse(pattern)
})
benchmark('toString', () => {
;ast::toString()
})
}

View File

@ -1,20 +0,0 @@
import {
createAnd,
createOr,
createNot,
createProperty,
createString,
createTruthyProperty,
} from './'
export const pattern = 'foo !"\\\\ \\"" name:|(wonderwoman batman) hasCape?'
export const ast = createAnd([
createString('foo'),
createNot(createString('\\ "')),
createProperty(
'name',
createOr([createString('wonderwoman'), createString('batman')])
),
createTruthyProperty('hasCape'),
])

View File

@ -1,400 +0,0 @@
import every from 'lodash/every'
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'
// ===================================================================
const RAW_STRING_CHARS = invoke(() => {
const chars = { __proto__: null }
const add = (a, b = a) => {
let i = a.charCodeAt(0)
const j = b.charCodeAt(0)
while (i <= j) {
chars[String.fromCharCode(i++)] = true
}
}
add('$')
add('-')
add('.')
add('0', '9')
add('_')
add('A', 'Z')
add('a', 'z')
return chars
})
const isRawString = string => {
const { length } = string
for (let i = 0; i < length; ++i) {
if (!RAW_STRING_CHARS[string[i]]) {
return false
}
}
return true
}
// -------------------------------------------------------------------
export const createAnd = children =>
children.length === 1 ? children[0] : { type: 'and', children }
export const createOr = children =>
children.length === 1 ? children[0] : { type: 'or', children }
export const createNot = child => ({ type: 'not', child })
export const createProperty = (name, child) => ({
type: 'property',
name,
child,
})
export const createString = value => ({ type: 'string', value })
export const createTruthyProperty = name => ({ type: 'truthyProperty', name })
// -------------------------------------------------------------------
// *and = terms
// terms = term+
// term = ws (groupedAnd | or | not | property | truthyProperty | string) ws
// ws = ' '*
// groupedAnd = "(" and ")"
// *or = "|" ws "(" terms ")"
// *not = "!" term
// *property = string ws ":" term
// *truthyProperty = string ws "?"
// *string = quotedString | rawString
// quotedString = "\"" ( /[^"\]/ | "\\\\" | "\\\"" )+
// rawString = /[a-z0-9-_.]+/i
export const parse = invoke(() => {
let i
let n
let input
// -----
const backtrace = parser => () => {
const pos = i
const node = parser()
if (node != null) {
return node
}
i = pos
}
// -----
const parseAnd = () => parseTerms(createAnd)
const parseTerms = fn => {
let term = parseTerm()
if (!term) {
return
}
const terms = [term]
while ((term = parseTerm())) {
terms.push(term)
}
return fn(terms)
}
const parseTerm = () => {
parseWs()
const child =
parseGroupedAnd() ||
parseOr() ||
parseNot() ||
parseProperty() ||
parseTruthyProperty() ||
parseString()
if (child) {
parseWs()
return child
}
}
const parseWs = () => {
while (input[i] === ' ') {
++i
}
return true
}
const parseGroupedAnd = backtrace(() => {
let and
if (input[i++] === '(' && (and = parseAnd()) && input[i++] === ')') {
return and
}
})
const parseOr = backtrace(() => {
let or
if (
input[i++] === '|' &&
parseWs() &&
input[i++] === '(' &&
(or = parseTerms(createOr)) &&
input[i++] === ')'
) {
return or
}
})
const parseNot = backtrace(() => {
let child
if (input[i++] === '!' && (child = parseTerm())) {
return createNot(child)
}
})
const parseProperty = backtrace(() => {
let name, child
if (
(name = parseString()) &&
parseWs() &&
input[i++] === ':' &&
(child = parseTerm())
) {
return createProperty(name.value, child)
}
})
const parseString = () => {
let value
if (
(value = parseQuotedString()) != null ||
(value = parseRawString()) != null
) {
return createString(value)
}
}
const parseQuotedString = backtrace(() => {
if (input[i++] !== '"') {
return
}
const value = []
let char
while (i < n && (char = input[i++]) !== '"') {
if (char === '\\') {
char = input[i++]
}
value.push(char)
}
return value.join('')
})
const parseRawString = () => {
let value = ''
let c
while ((c = input[i]) && RAW_STRING_CHARS[c]) {
++i
value += c
}
if (value.length) {
return value
}
}
const parseTruthyProperty = backtrace(() => {
let name
if ((name = parseString()) && parseWs() && input[i++] === '?') {
return createTruthyProperty(name.value)
}
})
return input_ => {
if (!input_) {
return
}
i = 0
input = input_.split('')
n = input.length
try {
return parseAnd()
} finally {
input = null
}
}
})
// -------------------------------------------------------------------
const _getPropertyClauseStrings = ({ child }) => {
const { type } = 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
)
if (!this) {
return property
}
return _addAndClause(
this,
property,
node => node.type === 'property' && node.name === name
)
}
// -------------------------------------------------------------------
export const execute = invoke(() => {
const visitors = {
and: ({ children }, value) =>
every(children, child => child::execute(value)),
not: ({ child }, value) => !child::execute(value),
or: ({ children }, value) => some(children, child => child::execute(value)),
property: ({ name, child }, value) =>
value != null && child::execute(value[name]),
truthyProperty: ({ name }, value) => !!value[name],
string: invoke(() => {
const match = (pattern, value) => {
if (isString(value)) {
return value.toLowerCase().indexOf(pattern) !== -1
}
if (isArray(value) || isPlainObject(value)) {
return some(value, value => match(pattern, value))
}
return false
}
return ({ value: pattern }, value) => match(pattern.toLowerCase(), value)
}),
}
return function (value) {
return visitors[this.type](this, value)
}
})
// -------------------------------------------------------------------
export const toString = invoke(() => {
const toStringTerms = terms => map(terms, toString).join(' ')
const toStringGroup = terms => `(${toStringTerms(terms)})`
const visitors = {
and: ({ children }) => toStringGroup(children),
not: ({ child }) => `!${toString(child)}`,
or: ({ children }) => `|${toStringGroup(children)}`,
property: ({ name, child }) =>
`${toString(createString(name))}:${toString(child)}`,
string: ({ value }) =>
isRawString(value)
? value
: `"${value.replace(/\\|"/g, match => `\\${match}`)}"`,
truthyProperty: ({ name }) => `${toString(createString(name))}?`,
}
const toString = node => visitors[node.type](node)
// Special case for a root “and”: do not add braces.
return function () {
return !this
? ''
: this.type === 'and' ? toStringTerms(this.children) : toString(this)
}
})
// -------------------------------------------------------------------
export const create = pattern => {
pattern = parse(pattern)
if (!pattern) {
return
}
return value => pattern::execute(value)
}

View File

@ -1,47 +0,0 @@
/* eslint-env jest */
import {
getPropertyClausesStrings,
parse,
setPropertyClause,
toString,
} from './'
import { ast, pattern } from './index.fixtures'
it('getPropertyClausesStrings', () => {
const tmp = parse('foo bar:baz baz:|(foo bar)')::getPropertyClausesStrings()
expect(tmp).toEqual({
bar: ['baz'],
baz: ['foo', 'bar'],
})
})
it('parse', () => {
expect(parse(pattern)).toEqual(ast)
})
it('setPropertyClause', () => {
expect(null::setPropertyClause('foo', 'bar')::toString()).toBe('foo:bar')
expect(
parse('baz')
::setPropertyClause('foo', 'bar')
::toString()
).toBe('baz foo:bar')
expect(
parse('plip foo:baz plop')
::setPropertyClause('foo', 'bar')
::toString()
).toBe('plip plop foo:bar')
expect(
parse('foo:|(baz plop)')
::setPropertyClause('foo', 'bar')
::toString()
).toBe('foo:bar')
})
it('toString', () => {
expect(pattern).toBe(ast::toString())
})

View File

@ -1,9 +1,9 @@
import * as CM from 'complex-matcher'
import React from 'react'
import Component from './base-component'
import propTypes from './prop-types-decorator'
import Tags from './tags'
import { createString, createProperty, toString } from './complex-matcher'
@propTypes({
labels: propTypes.arrayOf(React.PropTypes.string).isRequired,
@ -19,7 +19,7 @@ export default class HomeTags extends Component {
_onClick = label => {
const s = encodeURIComponent(
createProperty('tags', createString(label))::toString()
new CM.Property('tags', new CM.String(label)).toString()
)
const t = encodeURIComponent(this.props.type)

View File

@ -1,3 +1,4 @@
import * as CM from 'complex-matcher'
import _ from 'intl'
import classNames from 'classnames'
import DropdownMenu from 'react-bootstrap-4/lib/DropdownMenu' // https://phabricator.babeljs.io/T6662 so Dropdown.Menu won't work like https://react-bootstrap.github.io/components.html#btn-dropdowns-custom
@ -29,7 +30,6 @@ import SingleLineRow from '../single-line-row'
import Tooltip from '../tooltip'
import { BlockLink } from '../link'
import { Container, Col } from '../grid'
import { create as createMatcher } from '../complex-matcher'
import { Input as DebouncedInput } from '../debounce-component-decorator'
import {
createCounter,
@ -326,6 +326,7 @@ export default class SortedTable extends Component {
this._getTotalNumberOfItems = createCounter(() => this.props.collection)
const createMatcher = str => CM.parse(str).createPredicate()
this._getItems = createSort(
createFilter(
() => this.props.collection,

View File

@ -506,7 +506,7 @@ export default class Home extends Component {
}
const parsed = ComplexMatcher.parse(filter)
const properties = parsed::ComplexMatcher.getPropertyClausesStrings()
const properties = ComplexMatcher.getPropertyClausesStrings(parsed)
const sort = this._getDefaultSort(props)
@ -537,14 +537,14 @@ export default class Home extends Component {
_getFilterFunction = createSelector(
this._getParsedFilter,
filter => filter && (value => filter::ComplexMatcher.execute(value))
filter => filter !== undefined && filter.createPredicate()
)
// Optionally can take the props to be able to use it in
// componentWillReceiveProps().
_setFilter (filter, props = this.props, replace) {
if (!isString(filter)) {
filter = filter::ComplexMatcher.toString()
filter = filter.toString()
}
const { pathname, query } = props.location
@ -600,13 +600,14 @@ export default class Home extends Component {
this._setFilter(
pools.length
? filter::ComplexMatcher.setPropertyClause(
? ComplexMatcher.setPropertyClause(
filter,
'$pool',
ComplexMatcher.createOr(
map(pools, pool => ComplexMatcher.createString(pool.id))
new ComplexMatcher.Or(
map(pools, pool => new ComplexMatcher.String(pool.id))
)
)
: filter::ComplexMatcher.removePropertyClause('$pool')
: ComplexMatcher.setPropertyClause(filter, '$pool', undefined)
)
}
_updateSelectedHosts = hosts => {
@ -614,13 +615,14 @@ export default class Home extends Component {
this._setFilter(
hosts.length
? filter::ComplexMatcher.setPropertyClause(
? ComplexMatcher.setPropertyClause(
filter,
'$container',
ComplexMatcher.createOr(
map(hosts, host => ComplexMatcher.createString(host.id))
new ComplexMatcher.Or(
map(hosts, host => new ComplexMatcher.String(host.id))
)
)
: filter::ComplexMatcher.removePropertyClause('$container')
: ComplexMatcher.setPropertyClause(filter, '$container', undefined)
)
}
_updateSelectedTags = tags => {
@ -628,13 +630,14 @@ export default class Home extends Component {
this._setFilter(
tags.length
? filter::ComplexMatcher.setPropertyClause(
? ComplexMatcher.setPropertyClause(
filter,
'tags',
ComplexMatcher.createOr(
map(tags, tag => ComplexMatcher.createString(tag.id))
new ComplexMatcher.Or(
map(tags, tag => new ComplexMatcher.String(tag.id))
)
)
: filter::ComplexMatcher.removePropertyClause('tags')
: ComplexMatcher.setPropertyClause(filter, 'tags', undefined)
)
}
_updateSelectedResourceSets = resourceSets => {
@ -642,13 +645,14 @@ export default class Home extends Component {
this._setFilter(
resourceSets.length
? filter::ComplexMatcher.setPropertyClause(
? ComplexMatcher.setPropertyClause(
filter,
'resourceSet',
ComplexMatcher.createOr(
map(resourceSets, set => ComplexMatcher.createString(set.id))
new ComplexMatcher.Or(
map(resourceSets, set => new ComplexMatcher.String(set.id))
)
)
: filter::ComplexMatcher.removePropertyClause('resourceSet')
: ComplexMatcher.setPropertyClause(filter, 'resourceSet', undefined)
)
}
_addCustomFilter = () => {

View File

@ -1,3 +1,4 @@
import * as CM from 'complex-matcher'
import _ from 'intl'
import Copiable from 'copiable'
import Icon from 'icon'
@ -12,7 +13,6 @@ import { FormattedRelative } from 'react-intl'
import { formatSize } from 'utils'
import Usage, { UsageElement } from 'usage'
import { getObject } from 'selectors'
import { createString, createProperty, toString } from 'complex-matcher'
import {
CpuSparkLines,
MemorySparkLines,
@ -30,7 +30,7 @@ export default ({
}) => {
const pool = getObject(store.getState(), host.$pool)
const vmsFilter = encodeURIComponent(
createProperty('$container', createString(host.id))::toString()
new CM.Property('$container', new CM.String(host.id)).toString()
)
return (

View File

@ -33,6 +33,13 @@
dependencies:
"@babel/types" "7.0.0-beta.31"
"@babel/polyfill@^7.0.0-beta.35":
version "7.0.0-beta.35"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.0.0-beta.35.tgz#49d033c4fdfa54a3a11e8f87239530141650d47a"
dependencies:
core-js "^2.4.0"
regenerator-runtime "^0.11.1"
"@babel/template@7.0.0-beta.31":
version "7.0.0-beta.31"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.31.tgz#577bb29389f6c497c3e7d014617e7d6713f68bda"
@ -1954,6 +1961,13 @@ commander@2.8.x:
dependencies:
graceful-readlink ">= 1.0.0"
complex-matcher@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/complex-matcher/-/complex-matcher-0.1.0.tgz#a26ff7c362e9f67b781374e99ea3a502cbd28191"
dependencies:
"@babel/polyfill" "^7.0.0-beta.35"
lodash "^4.17.4"
component-emitter@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6"
@ -7334,7 +7348,7 @@ regenerate@^1.2.1:
version "1.3.3"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f"
regenerator-runtime@^0.11.0:
regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1:
version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"