1
0

Merge pull request #9 from jarvys/master

Add exclusion syntax 👍
This commit is contained in:
Julien Buty
2016-09-28 11:28:50 +08:00
committed by GitHub
3 changed files with 126 additions and 31 deletions

View File

@@ -48,8 +48,8 @@ var searchQueryObj = searchQuery.parse(query, options);
You can configure what keywords and ranges the parser should accept with the options argument.
It accepts 2 values:
* `keywords`, that can be separated by commas (,)
* `ranges`, that can be separated by a hyphen (-)
* `keywords`, that can be separated by commas (,)
* `ranges`, that can be separated by a hyphen (-)
Both values take an array of strings, as in the example just above.
@@ -67,9 +67,20 @@ var parsedQueryWithOptions = searchQuery.parse(query, options);
// parsedQueryWithOptions is now 'a query with just text'
```
You can also use exclusion syntax, like `-from:sep@foobar.io name:hello,world` . And it will return :
```javascript
{
name: ['hello', 'world'],
exclusion: {
from: ['sep@foobar.io']
}
}
```
## Testing
The 17 tests are written using the BDD testing framework should.js, and run with mocha.
The 20 tests are written using the BDD testing framework should.js, and run with mocha.
Run `npm install should` and `npm install -g mocha` to install them both.

View File

@@ -31,6 +31,7 @@ exports.parse = function (string, options) {
else {
// Our object to store the query object
var query = {text: []};
var exclusion = {};
// Get a list of search terms respecting single and double quotes
var terms = string.match(/(\S+:'(?:[^'\\]|\\.)*')|(\S+:"(?:[^"\\]|\\.)*")|\S+|\S+:\S+/g);
for (var i = 0; i < terms.length; i++) {
@@ -74,7 +75,18 @@ exports.parse = function (string, options) {
var key = term.slice(0, sepIdx);
// Check if the key is a registered keyword
options.keywords = options.keywords || [];
var isKeyword = !(-1 === options.keywords.indexOf(key));
var isKeyword = false;
var isExclusion = false;
if (!/^-/.test(key)) {
isKeyword = !(-1 === options.keywords.indexOf(key));
} else if (key[0] === '-') {
var _key = key.slice(1);
isKeyword = !(-1 === options.keywords.indexOf(_key))
if (isKeyword) {
key = _key;
isExclusion = true;
}
}
// Check if the key is a registered range
options.ranges = options.ranges || [];
var isRange = !(-1 === options.ranges.indexOf(key));
@@ -85,42 +97,79 @@ exports.parse = function (string, options) {
if (value.length) {
// Get an array of values when several are there
var values = value.split(',');
// If we already have seen that keyword...
if (query[key]) {
// ...many times...
if (query[key] instanceof Array) {
if (isExclusion) {
if (exclusion[key]) {
// ...many times...
if (exclusion[key] instanceof Array) {
// ...and got several values this time...
if (values.length > 1) {
// ... concatenate both arrays.
exclusion[key] = exclusion[key].concat(values);
}
else {
// ... append the current single value.
exclusion[key].push(value);
}
}
// We saw that keyword only once before
else {
// Put both the current value and the new
// value in an array
exclusion[key] = [exclusion[key]];
exclusion[key].push(value);
}
}
// First time we see that keyword
else {
// ...and got several values this time...
if (values.length > 1) {
// ... concatenate both arrays.
query[key] = query[key].concat(values);
// ...add all values seen.
exclusion[key] = values;
}
// Got only a single value this time
else {
// ... append the current single value.
// Record its value as a string
exclusion[key] = value;
}
}
} else {
// If we already have seen that keyword...
if (query[key]) {
// ...many times...
if (query[key] instanceof Array) {
// ...and got several values this time...
if (values.length > 1) {
// ... concatenate both arrays.
query[key] = query[key].concat(values);
}
else {
// ... append the current single value.
query[key].push(value);
}
}
// We saw that keyword only once before
else {
// Put both the current value and the new
// value in an array
query[key] = [query[key]];
query[key].push(value);
}
}
// We saw that keyword only once before
// First time we see that keyword
else {
// Put both the current value and the new
// value in an array
query[key] = [query[key]];
query[key].push(value);
// ...and got several values this time...
if (values.length > 1) {
// ...add all values seen.
query[key] = values;
}
// Got only a single value this time
else {
// Record its value as a string
query[key] = value;
}
}
}
// First time we see that keyword
else {
// ...and got several values this time...
if (values.length > 1) {
// ...add all values seen.
query[key] = values;
}
// Got only a single value this time
else {
// Record its value as a string
query[key] = value;
}
}
}
}
}
// The key allows a range
else if (isRange) {
@@ -161,8 +210,8 @@ exports.parse = function (string, options) {
}
// Return forged query object
query.exclude = exclusion;
return query;
}
};

View File

@@ -223,4 +223,39 @@ describe('Search query syntax parser', function () {
});
it('should parse a single keyword query in exclusion syntax', function() {
var searchQuery = '-from:jul@foo.com';
var options = {keywords: ['from']};
var parsedSearchQuery = searchquery.parse(searchQuery, options);
parsedSearchQuery.should.be.an.Object;
parsedSearchQuery.exclude.should.be.an.Object;
parsedSearchQuery.exclude.should.have.property('from', 'jul@foo.com');
parsedSearchQuery.should.not.have.property('text');
});
it('should concatenate a keyword multiple values in exclusion syntax', function() {
var searchQuery = '-from:jul@foo.com,mar@foo.com';
var options = {keywords: ['from']};
var parsedSearchQuery = searchquery.parse(searchQuery, options);
parsedSearchQuery.should.be.an.Object;
parsedSearchQuery.exclude.should.be.an.Object;
parsedSearchQuery.exclude.from.should.containEql('jul@foo.com');
parsedSearchQuery.exclude.from.should.containEql('mar@foo.com');
parsedSearchQuery.should.not.have.property('text');
});
it('should support keywords which appear multiple times with exclusion syntax', function() {
var searchQuery = '-from:jul@foo.com,mar@foo.com -from:jan@foo.com';
var options = {keywords: ['from']};
var parsedSearchQuery = searchquery.parse(searchQuery, options);
parsedSearchQuery.should.be.an.Object;
parsedSearchQuery.exclude.should.be.an.Object;
parsedSearchQuery.exclude.from.should.containEql('jul@foo.com');
parsedSearchQuery.exclude.from.should.containEql('mar@foo.com');
parsedSearchQuery.exclude.from.should.containEql('jan@foo.com');
parsedSearchQuery.should.not.have.property('text');
});
});