diff --git a/README.md b/README.md index 78acc82..631ff69 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ name:/foo/o # search using wildcard name:foo*bar +name:foo?bar # boolean search member:true @@ -202,6 +203,12 @@ Search for `name` field values matching `f*o` wildcard pattern. name:f*o ``` +Search for `name` field values matching `f?o` wildcard pattern. + +```rb +name:f?o +``` + Search for phrase "foo bar" in the `name` field (case sensitive). ```rb @@ -250,12 +257,24 @@ Search for any word that starts with "foo" in the `name` field. name:foo* ``` -Search for any word that starts with "foo" and ends with bar in the `name` field. +Search for any word that starts with "foo" and ends with "bar" in the `name` field. ```rb name:foo*bar ``` +Search for any word that starts with "foo" in the `name` field, followed by a single arbitrary character. + +```rb +name:foo? +``` + +Search for any word that starts with "foo", followed by a single arbitrary character and immediately ends with "bar" in the `name` field. + +```rb +name:foo?bar +``` + ### Boolean operators Search for phrase "foo bar" in the `name` field AND the phrase "quick fox" in the `bio` field. diff --git a/src/convertWildcardToRegex.ts b/src/convertWildcardToRegex.ts index eca30c5..228cc5e 100644 --- a/src/convertWildcardToRegex.ts +++ b/src/convertWildcardToRegex.ts @@ -1,8 +1,10 @@ -const WILDCARD_RULE = /\*+/g; +const WILDCARD_RULE = /(\*+)|(\?)/g; export const convertWildcardToRegex = (pattern: string): RegExp => { return new RegExp( pattern - .replace(WILDCARD_RULE, '(.+?)'), + .replace(WILDCARD_RULE, (_match, p1) => { + return p1 ? '(.+?)' : '(.)'; + }), ); }; diff --git a/src/createStringTest.ts b/src/createStringTest.ts index fd21ebf..a473007 100644 --- a/src/createStringTest.ts +++ b/src/createStringTest.ts @@ -50,8 +50,8 @@ export const createStringTest = (regexCache: RegExpCache, ast: LiqeQuery) => { const value = String(expression.value); - if (value.includes('*') && expression.quoted === false) { - return createRegexTest(regexCache, String(convertWildcardToRegex(value)) + (expression.quoted ? 'u' : 'ui')); + if ((value.includes('*') || value.includes('?')) && expression.quoted === false) { + return createRegexTest(regexCache, String(convertWildcardToRegex(value)) + 'ui'); } else { return createRegexTest(regexCache, '/(' + escapeRegexString(value) + ')/' + (expression.quoted ? 'u' : 'ui')); } diff --git a/test/benchmark.ts b/test/benchmark.ts index 28cf9bf..84d0b93 100644 --- a/test/benchmark.ts +++ b/test/benchmark.ts @@ -10,7 +10,7 @@ import { filter, } from '../src/Liqe'; -const randomInRange = (min, max) => { +const randomInRange = (min: number, max: number) => { return Math.floor( Math.random() * (Math.ceil(max) - Math.floor(min) + 1) + min, ); @@ -71,7 +71,7 @@ void suite( }; }), - add('filters list by the "name" field using wildcard check', () => { + add('filters list by the "name" field using star (*) wildcard check', () => { const query = parse('name:Ga*'); return () => { @@ -79,6 +79,14 @@ void suite( }; }), + add('filters list by the "name" field using question mark (?) wildcard check', () => { + const query = parse('name:Gaju?'); + + return () => { + filter(query, persons); + }; + }), + add('filters list by any field using loose inclusion check', () => { const query = parse('Gajus'); diff --git a/test/liqe/convertWildcardToRegex.ts b/test/liqe/convertWildcardToRegex.ts index 0c0db21..7eda923 100644 --- a/test/liqe/convertWildcardToRegex.ts +++ b/test/liqe/convertWildcardToRegex.ts @@ -8,5 +8,9 @@ const testRule = test.macro((t, regex: RegExp) => { }); test('*', testRule, /(.+?)/); +test('?', testRule, /(.)/); test('foo*bar', testRule, /foo(.+?)bar/); test('foo***bar', testRule, /foo(.+?)bar/); +test('foo*bar*', testRule, /foo(.+?)bar(.+?)/); +test('foo?bar', testRule, /foo(.)bar/); +test('foo???bar', testRule, /foo(.)(.)(.)bar/); diff --git a/test/liqe/highlight.ts b/test/liqe/highlight.ts index 2b3701b..4ee4dc6 100644 --- a/test/liqe/highlight.ts +++ b/test/liqe/highlight.ts @@ -103,7 +103,7 @@ test( ); test( - 'matches wildcard', + 'matches star (*) wildcard', testQuery, 'name:f*o', { @@ -118,7 +118,7 @@ test( ); test( - 'matches wildcard (lazy)', + 'matches star (*) wildcard (lazy)', testQuery, 'name:f*o', { @@ -132,6 +132,21 @@ test( ], ); +test( + 'matches question mark (?) wildcard', + testQuery, + 'name:f?o', + { + name: 'foo bar baz', + }, + [ + { + path: 'name', + query: /(foo)/, + }, + ], +); + test( 'matches regex', testQuery,