parent
e58d56a656
commit
7dfb1635c2
@ -1,7 +1,7 @@
|
||||
import * as CM from './'
|
||||
|
||||
export const pattern =
|
||||
'foo !"\\\\ \\"" name:|(wonderwoman batman) hasCape? age:32'
|
||||
'foo !"\\\\ \\"" name:|(wonderwoman batman) hasCape? age:32 chi*go'
|
||||
|
||||
export const ast = new CM.And([
|
||||
new CM.String('foo'),
|
||||
@ -12,4 +12,5 @@ export const ast = new CM.And([
|
||||
),
|
||||
new CM.TruthyProperty('hasCape'),
|
||||
new CM.Property('age', new CM.Number(32)),
|
||||
new CM.GlobPattern('chi*go'),
|
||||
])
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { isPlainObject, some } from 'lodash'
|
||||
import { escapeRegExp, isPlainObject, some } from 'lodash'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@ -178,6 +178,48 @@ const formatString = value =>
|
||||
: `"${value.replace(/\\|"/g, escapeChar)}"`
|
||||
: `"${value}"`
|
||||
|
||||
export class GlobPattern extends Node {
|
||||
constructor (value) {
|
||||
// fallback to string node if no wildcard
|
||||
if (value.indexOf('*') === -1) {
|
||||
return new StringNode(value)
|
||||
}
|
||||
|
||||
super()
|
||||
|
||||
this.value = value
|
||||
|
||||
// should not be enumerable for the tests
|
||||
Object.defineProperty(this, 'match', {
|
||||
value: this.match.bind(
|
||||
this,
|
||||
new RegExp(
|
||||
value
|
||||
.split('*')
|
||||
.map(escapeRegExp)
|
||||
.join('.*'),
|
||||
'i'
|
||||
)
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
match (re, value) {
|
||||
if (typeof value === 'string') {
|
||||
return re.test(value)
|
||||
}
|
||||
|
||||
if (Array.isArray(value) || isPlainObject(value)) {
|
||||
return some(value, this.match)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
toString () {
|
||||
return this.value
|
||||
}
|
||||
}
|
||||
export class StringNode extends Node {
|
||||
constructor (value) {
|
||||
super()
|
||||
@ -389,6 +431,17 @@ const parser = P.grammar({
|
||||
P.seq(r.ws, r.term.repeat(), P.eof).map(
|
||||
([, terms]) => (terms.length === 0 ? new Null() : new And(terms))
|
||||
),
|
||||
globPattern: new P((input, pos, end) => {
|
||||
let value = ''
|
||||
let c
|
||||
while (pos < end && ((c = input[pos]) === '*' || c in RAW_STRING_CHARS)) {
|
||||
++pos
|
||||
value += c
|
||||
}
|
||||
return value.length === 0
|
||||
? new Failure(pos, 'a raw string')
|
||||
: new Success(pos, value)
|
||||
}),
|
||||
quotedString: new P((input, pos, end) => {
|
||||
if (input[pos] !== '"') {
|
||||
return new Failure(pos, '"')
|
||||
@ -406,6 +459,7 @@ const parser = P.grammar({
|
||||
|
||||
return new Success(pos, value.join(''))
|
||||
}),
|
||||
property: r => P.alt(r.quotedString, r.rawString),
|
||||
rawString: new P((input, pos, end) => {
|
||||
let value = ''
|
||||
let c
|
||||
@ -417,7 +471,6 @@ const parser = P.grammar({
|
||||
? new Failure(pos, 'a raw string')
|
||||
: new Success(pos, value)
|
||||
}),
|
||||
string: r => P.alt(r.quotedString, r.rawString),
|
||||
term: r =>
|
||||
P.alt(
|
||||
P.seq(P.text('('), r.ws, r.term.repeat(1), P.text(')')).map(
|
||||
@ -439,20 +492,22 @@ const parser = P.grammar({
|
||||
}
|
||||
return new Comparison(op, val)
|
||||
}),
|
||||
P.seq(r.string, r.ws, P.text(':'), r.ws, r.term).map(
|
||||
P.seq(r.property, r.ws, P.text(':'), r.ws, r.term).map(
|
||||
_ => new Property(_[0], _[4])
|
||||
),
|
||||
P.seq(r.string, P.text('?')).map(_ => new TruthyProperty(_[0])),
|
||||
P.alt(
|
||||
r.quotedString.map(_ => new StringNode(_)),
|
||||
r.rawString.map(str => {
|
||||
const asNum = +str
|
||||
return Number.isNaN(asNum)
|
||||
? new StringNode(str)
|
||||
: new NumberNode(asNum)
|
||||
})
|
||||
)
|
||||
P.seq(r.property, P.text('?')).map(_ => new TruthyProperty(_[0])),
|
||||
r.value
|
||||
).skip(r.ws),
|
||||
value: r =>
|
||||
P.alt(
|
||||
r.quotedString.map(_ => new StringNode(_)),
|
||||
r.globPattern.map(str => {
|
||||
const asNum = +str
|
||||
return Number.isNaN(asNum)
|
||||
? new GlobPattern(str)
|
||||
: new NumberNode(asNum)
|
||||
})
|
||||
),
|
||||
ws: P.regex(/\s*/),
|
||||
}).default
|
||||
export const parse = parser.parse.bind(parser)
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { ast, pattern } from './index.fixtures'
|
||||
import {
|
||||
getPropertyClausesStrings,
|
||||
GlobPattern,
|
||||
Null,
|
||||
NumberNode,
|
||||
parse,
|
||||
@ -41,6 +42,12 @@ describe('parse', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('GlobPattern', () => {
|
||||
it('matches a glob pattern recursively', () => {
|
||||
expect(new GlobPattern('b*r').match({ foo: 'bar' })).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Number', () => {
|
||||
it('match a number recursively', () => {
|
||||
expect(new NumberNode(3).match([{ foo: 3 }])).toBe(true)
|
||||
@ -82,5 +89,5 @@ describe('setPropertyClause', () => {
|
||||
})
|
||||
|
||||
it('toString', () => {
|
||||
expect(pattern).toBe(ast.toString())
|
||||
expect(ast.toString()).toBe(pattern)
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user