Skip to content

Commit

Permalink
Merge pull request #9 from jarvys/master
Browse files Browse the repository at this point in the history
Add exclusion syntax 👍
  • Loading branch information
nepsilon authored Sep 28, 2016
2 parents fc2d1e1 + b0f164f commit a0c94f3
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 31 deletions.
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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:[email protected] name:hello,world` . And it will return :

```javascript
{
name: ['hello', 'world'],
exclusion: {
from: ['[email protected]']
}
}
```

## 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.

Expand Down
105 changes: 77 additions & 28 deletions lib/search-query-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down Expand Up @@ -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));
Expand All @@ -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) {
// ...and got several values this time...
if (values.length > 1) {
// ... concatenate both arrays.
query[key] = query[key].concat(values);
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 {
// ... append the current single value.
query[key].push(value);
// Put both the current value and the new
// value in an array
exclusion[key] = [exclusion[key]];
exclusion[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.
exclusion[key] = values;
}
// Got only a single value this time
else {
// Record its value as a string
exclusion[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;
} 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);
}
}
// Got only a single value this time
// First time we see that keyword
else {
// Record its value as a string
query[key] = 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;
}
}
}
}
}
}
// The key allows a range
else if (isRange) {
Expand Down Expand Up @@ -161,8 +210,8 @@ exports.parse = function (string, options) {
}

// Return forged query object
query.exclude = exclusion;
return query;

}

};
35 changes: 35 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,4 +223,39 @@ describe('Search query syntax parser', function () {
});


it('should parse a single keyword query in exclusion syntax', function() {
var searchQuery = '-from:[email protected]';
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', '[email protected]');
parsedSearchQuery.should.not.have.property('text');
});

it('should concatenate a keyword multiple values in exclusion syntax', function() {
var searchQuery = '-from:[email protected],[email protected]';
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('[email protected]');
parsedSearchQuery.exclude.from.should.containEql('[email protected]');
parsedSearchQuery.should.not.have.property('text');
});

it('should support keywords which appear multiple times with exclusion syntax', function() {
var searchQuery = '-from:[email protected],[email protected] -from:[email protected]';
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('[email protected]');
parsedSearchQuery.exclude.from.should.containEql('[email protected]');
parsedSearchQuery.exclude.from.should.containEql('[email protected]');
parsedSearchQuery.should.not.have.property('text');
});
});

0 comments on commit a0c94f3

Please sign in to comment.