diff --git a/README.md b/README.md index 535cff4..8c83e3f 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,12 @@ var searchQueryObj = searchQuery.parse(query, options); ``` You can configure what keywords and ranges the parser should accept with the options argument. -It accepts 3 values: +It accepts 5 values: * `keywords`, that can be separated by commas (,). Accepts an array of strings. * `ranges`, that can be separated by a hyphen (-). Accepts an array of strings. * `tokenize`, that controls the behaviour of text search terms. If set to `true`, non-keyword text terms are returned as an array of strings where each term in the array is a whitespace-separated word, or a multi-word term surrounded by single- or double-quotes. +* `alwaysArray`, a boolean that controls the behaviour of the returned query. If set to `true`, all matched keywords would always be arrays instead of strings. If set to `false` they will be strings if matched a single value. Defaults to `false`. +* `offsets`, a boolean that controls the behaviour of the returned query. If set to `true`, the query will contain the offsets object. If set to `false`, the query will not contain the offsets object. Defaults to `true`. If no keywords or ranges are specified, or if none are present in the given search query, then `searchQuery.parse` will return a string if `tokenize` is false, or an array of strings under the key `text` if `tokenize` is true. @@ -87,9 +89,34 @@ You can also use exclusion syntax, like `-from:sep@foobar.io name:hello,world`. } ``` +Sometimes checking against whether a keyword holds string or not can be excessive and prone to errors; it's often easier to simply expect everything is an array even if it means doing 1-iteration loops often. + +```javascript +var searchQuery = require('search-query-parser'); + +var query = 'test:helloworld fun:yay,happy'; +var options = {keywords: ['test', 'fun']}; +var parsedQueryWithOptions = searchQuery.parse(query, options); +// parsedQueryWithOptions is now: +// { +// test: 'helloworld', +// fun: ['yay', 'happy'] +// } + +var optionsAlwaysArray = {keywords: ['test', 'fun'], alwaysArray: true}; +var parsedQueryWithOptions = searchQuery.parse(query, options); +// parsedQueryWithOptions is now: +// { +// test: ['helloworld'], //No need to check whether test is a string or not! +// fun: ['yay', 'happy'] +// } +``` + +The offsets object could become pretty huge with long search queries which could be an unnecessary use of space if no functionality depends on it. It can simply be turned off using the option `offsets: false` + ## Testing -The 27 tests are written using the BDD testing framework should.js, and run with mocha. +The 29 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. diff --git a/lib/search-query-parser.js b/lib/search-query-parser.js index c6354ba..bf7d9f0 100644 --- a/lib/search-query-parser.js +++ b/lib/search-query-parser.js @@ -6,9 +6,12 @@ exports.parse = function (string, options) { - // Set an empty options object when none provided + // Set a default options object when none is provided if (!options) { - options = {}; + options = {offsets: true}; + } else { + // If options offsets was't passed, set it to true + options.offsets = (typeof options.offsets === 'undefined' ? true : options.offsets) } if (!string) { @@ -26,7 +29,11 @@ exports.parse = function (string, options) { // Otherwise parse the advanced query syntax else { // Our object to store the query object - var query = {text: [], offsets: []}; + var query = {text: []}; + // When offsets is true, create their array + if (options.offsets) { + query.offsets = []; + } var exclusion = {}; var terms = []; // Get a list of search terms respecting single and double quotes @@ -113,7 +120,10 @@ exports.parse = function (string, options) { if (term.text) { // We add it as pure text query.text.push(term.text); - query.offsets.push(term); + // When offsets is true, push a new offset + if (options.offsets) { + query.offsets.push(term); + } } // We got an advanced search syntax else { @@ -138,12 +148,15 @@ exports.parse = function (string, options) { var isRange = !(-1 === options.ranges.indexOf(key)); // When the key matches a keyword if (isKeyword) { - query.offsets.push({ - keyword: key, - value: term.value, - offsetStart: isExclusion ? term.offsetStart + 1 : term.offsetStart, - offsetEnd: term.offsetEnd - }); + // When offsets is true, push a new offset + if (options.offsets) { + query.offsets.push({ + keyword: key, + value: term.value, + offsetStart: isExclusion ? term.offsetStart + 1 : term.offsetStart, + offsetEnd: term.offsetEnd + }); + } var value = term.value; // When value is a thing @@ -217,8 +230,13 @@ exports.parse = function (string, options) { } // Got only a single value this time else { - // Record its value as a string - query[key] = value; + if (options.alwaysArray) { + // ...but we always return an array if option alwaysArray is true + query[key] = [value]; + } else { + // Record its value as a string + query[key] = value; + } } } } @@ -226,7 +244,10 @@ exports.parse = function (string, options) { } // The key allows a range else if (isRange) { - query.offsets.push(term); + // When offsets is true, push a new offset + if (options.offsets) { + query.offsets.push(term); + } var value = term.value; // Range are separated with a dash @@ -253,11 +274,14 @@ exports.parse = function (string, options) { var text = term.keyword + ':' + term.value; query.text.push(text); - query.offsets.push({ - text: text, - offsetStart: term.offsetStart, - offsetEnd: term.offsetEnd - }); + // When offsets is true, push a new offset + if (options.offsets) { + query.offsets.push({ + text: text, + offsetStart: term.offsetStart, + offsetEnd: term.offsetEnd + }); + } } } } diff --git a/test/test.js b/test/test.js index 674630d..6b374e3 100644 --- a/test/test.js +++ b/test/test.js @@ -347,6 +347,38 @@ describe('Search query syntax parser', function () { }]); }); + it('should always return an array if alwaysArray is set to true', function () { + var searchQuery = 'from:jul@foo.com to:a@b.c ouch!#'; + + var options = {keywords: ['from', 'to'], alwaysArray: true}; + var parsedSearchQuery = searchquery.parse(searchQuery, options); + + parsedSearchQuery.should.be.an.Object; + parsedSearchQuery.should.have.property('text', 'ouch!#'); + parsedSearchQuery.should.have.property('from'); + parsedSearchQuery.should.have.property('to'); + parsedSearchQuery.from.should.be.an.Array; + parsedSearchQuery.to.should.be.an.Array; + parsedSearchQuery.from.length.should.equal(1); + parsedSearchQuery.to.length.should.equal(1); + parsedSearchQuery.from.should.containEql('jul@foo.com'); + parsedSearchQuery.to.should.containEql('a@b.c'); + parsedSearchQuery.should.have.property('offsets', [{ + keyword: 'from', + value: 'jul@foo.com', + offsetStart: 0, + offsetEnd: 16 + }, { + keyword: 'to', + value: 'a@b.c', + offsetStart: 17, + offsetEnd: 25 + }, { + text: 'ouch!#', + offsetStart: 26, + offsetEnd: 32 + }]); + }); it('should parse range with only 1 end and free text', function () { var searchQuery = 'date:12/12/2012 ahaha'; @@ -586,4 +618,24 @@ describe('Search query syntax parser', function () { offsetEnd: 47 }]); }); + + it('should not return offset when offsets option is set to false', function() { + var searchQuery = '-from:jul@foo.com,mar@foo.com to:bar@hey.ya about date:12/12/2012'; + var options = { + keywords: ['from', 'to'], + ranges: ['date'], + offsets: false + }; + 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.to.should.containEql('bar@hey.ya'); + parsedSearchQuery.should.have.property('text', 'about'); + parsedSearchQuery.should.have.property('date'); + parsedSearchQuery.date.from.should.containEql('12/12/2012'); + parsedSearchQuery.should.not.have.property('offsets'); + }); }); \ No newline at end of file