Skip to content

Commit eb5591c

Browse files
committed
setup workflow and initial code
See README.md for more information
1 parent 62d0fb8 commit eb5591c

File tree

7 files changed

+483
-0
lines changed

7 files changed

+483
-0
lines changed

.eslintrc.json

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"env" : {
3+
"es6" : true,
4+
"node": true
5+
},
6+
"extends" : "eslint:recommended",
7+
"parserOptions": {
8+
"sourceType": "module"
9+
},
10+
"globals": {
11+
"window": true,
12+
"describe": true,
13+
"xit": true,
14+
"it": true
15+
},
16+
"rules" : {
17+
"no-var" : "error",
18+
"key-spacing" : [
19+
"error",
20+
{
21+
"align": "colon"
22+
}
23+
],
24+
"indent" : [
25+
"error",
26+
2
27+
],
28+
"linebreak-style": [
29+
"error",
30+
"unix"
31+
],
32+
"quotes" : [
33+
"error",
34+
"single"
35+
],
36+
"semi" : [
37+
"error",
38+
"always"
39+
]
40+
}
41+
}

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist
2+
node_modules

README.md

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Lucene Query String Builder
2+
3+
More easily build your lucene string queries small and pure functions.
4+
5+
See the usage section to see how you can leverage this lib for your purposes.
6+
7+
## Use cases
8+
9+
Imagine having an API that leverages lucene for performing queries on the
10+
(indexed) database. In that case you might want to generate lucene query strings on
11+
the client/front end.
12+
13+
## Installation
14+
15+
```bash
16+
bower install lucene-query-builder --save
17+
18+
# or
19+
20+
npm install lucene-query-builder --save
21+
```
22+
23+
## Usage
24+
25+
Let's see how you can use lucene query string builder to define lucene query
26+
strings with simple JavaScript functions.
27+
28+
Assuming that the lucene global variable contains the lucene functions. This
29+
would be the default when loaded into a browser.
30+
31+
```JavaScript
32+
33+
var findUserLuceneQueryString = lucene.builder(function(data){
34+
35+
// just to make the example more readable;
36+
var _ = lucene;
37+
38+
_.group(_.and(
39+
_.field('eye-color', _.term(data.eye.color)),
40+
_.field('age', _.range(data.age.min, data.age.max))
41+
));
42+
43+
});
44+
45+
var luceneQueryString = findUserLuceneQueryString({
46+
eye: { color: 'brown'},
47+
age: {
48+
min: 10,
49+
max: 20
50+
}
51+
});
52+
53+
luceneQueryString == "( eye-color: "brown" AND age:{ 10 TO 20 } )" // => true
54+
55+
```
56+
The list of all the functions available are. They are based on the
57+
specifications which can be found here:
58+
59+
https://lucene.apache.org/core/2_9_4/queryparsersyntax.html#Terms
60+
61+
- terms
62+
- term
63+
64+
- field
65+
66+
- or
67+
- and
68+
- not
69+
70+
- group
71+
- range;
72+
73+
- fuzzy;
74+
- proximity
75+
- required
76+
77+
- builder
78+
79+
## Contributing
80+
81+
- Make sure your dependencies are installed by running: `npm run-script setup`
82+
83+
- Then start editing the index.js
84+
85+
- You should add and/or edit the tests in test/index.js
86+
87+
- Run your tests and see what happens
88+
89+
When performing pull request make sure to not add the **dist** files. This is left
90+
to the maintainers(s) of the library. They are responsible to version and avoid
91+
code breakages.
92+
93+
You can perform your own build with npm run-script build to make a lucine.js and
94+
a lucine.min.js
95+
96+
**notice**
97+
I am currently not using this repository in any of my projects. Therefore I am looking
98+
for people that are able to make LQSB more useful for them and others.
99+
100+
## Road map
101+
102+
- split all functions into separate files
103+
- escaping lucene query syntax characters in the term(s) function
104+
- tasks for running tests on dist/lucene.js and dist/lucene.min.js
105+
106+
## LICENSE
107+
108+
The MIT License (MIT)

index.js

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
'use strict';
2+
3+
const values = require('object-values');
4+
const existy = require('existy');
5+
const complement = require('complement');
6+
7+
/**
8+
* Lucene Query Builder
9+
*
10+
* Lucene query syntax docs (specs)
11+
* https://lucene.apache.org/core/2_9_4/queryparsersyntax.html#Terms
12+
*/
13+
14+
const NAMESPACE = 'lucene';
15+
16+
let lucene = {};
17+
18+
lucene.terms = terms;
19+
lucene.term = terms; //alias
20+
21+
lucene.field = field;
22+
23+
lucene.or = surrounded('OR');
24+
lucene.and = surrounded('AND');
25+
lucene.not = surrounded('NOT');
26+
27+
lucene.group = surrounder('(', ')');
28+
lucene.range = range;
29+
30+
lucene.fuzzy = fuzzy;
31+
lucene.proximity = proximity;
32+
lucene.required = required;
33+
34+
lucene.builder = builder;
35+
36+
if (typeof module.exports === 'undefined')
37+
window[NAMESPACE] = lucene;
38+
else
39+
module.exports = lucene;
40+
41+
/**
42+
* Used for defining a query builder
43+
*
44+
* @param {function} fn
45+
* @returns {function}
46+
*
47+
* @example
48+
*
49+
* userLuceneQuery = lucene.builder((data) => {
50+
* return lucene.field(
51+
* lucene.term('user-name', data.user.name)
52+
* );
53+
* });
54+
*
55+
* userLuceneQuery({user: {name: 'Dolly'}}) // => 'user-name: 'Dolly''
56+
*/
57+
function builder(fn) {
58+
return (data) => fn(data);
59+
}
60+
61+
/**
62+
* Basicly add double quotes around the string
63+
*
64+
* @param {string} words
65+
*
66+
* @returns {string}
67+
*
68+
* @todo make sure lucene query characters are escaped with a \
69+
*/
70+
function terms(words) {
71+
return `"${words}"`;
72+
}
73+
74+
/**
75+
* @param {string} field
76+
* @param {string} query
77+
*
78+
* @returns {string} the format is <field>: <term(s)>
79+
*
80+
* @example
81+
* lucene.field('prop-name', lucene.term('value')) // => 'prop-name: "value"'
82+
*/
83+
function field(field, query) {
84+
return `${field}: ${query}`;
85+
}
86+
87+
/**
88+
* The value is between 0 and 1, with a value closer to 1 only terms with a
89+
* higher similarity will be matched.
90+
*
91+
* Fuzzy search can only be applied to a single term
92+
*
93+
* @param {string} str
94+
* @param {number} [similarity=undefined]
95+
*
96+
*/
97+
function fuzzy(termStr, similarity) {
98+
99+
if ((complement(existy)(similarity)))
100+
return `${termStr}~`;
101+
102+
let isValidSimilarity = within(0,1);
103+
104+
if (!isValidSimilarity(similarity))
105+
throw new RangeError('Similarity must be between 0 and 1. It was ' + similarity);
106+
107+
return `${termStr}~${similarity}`;
108+
}
109+
110+
/**
111+
* Lucene supports finding words are a within a specific distance away.
112+
*
113+
* @param {string} first
114+
* @param {string} second
115+
*
116+
* @param {number} distance
117+
*
118+
* @returns {string}
119+
*/
120+
function proximity(first, second, distance) {
121+
return `"${first} ${second}"~${distance}`;
122+
}
123+
124+
/**
125+
* Range Queries allow one to match documents whose field(s) values are
126+
* between the lower and upper bound specified by the Range Query. Range
127+
* Queries can be inclusive or exclusive of the upper and lower bounds.
128+
*
129+
* @param {string}
130+
* @param {string}
131+
* @param {boolean} [includeLeft=true]
132+
* @param {boolean} [includeRight=true]
133+
*
134+
* @returns {string}
135+
*/
136+
function range(from, to, includeLeft, includeRight) {
137+
return surrounder(
138+
includeLeft ? '[' : '{',
139+
includeRight ? ']' : '}'
140+
)(`${from} TO ${to}`);
141+
}
142+
143+
//banana banana banana
144+
function required(termStr) {
145+
return `+${termStr}`;
146+
}
147+
148+
/**
149+
* Higher order function for building strings that always have the same string
150+
* between two other strings
151+
*
152+
* @param {string} patty
153+
*
154+
* @returns {string}
155+
*
156+
* @example
157+
* surrounded('OR')('hello', 'world') // -> "hello OR world"
158+
*/
159+
function surrounded(middle) {
160+
return function(){
161+
return values(arguments).join(` ${middle} `);
162+
};
163+
}
164+
165+
/**
166+
* Higher order function for building strings that are surrounderd on both
167+
* sides of a string
168+
*
169+
* @param {string} open
170+
* @param {string} close
171+
*
172+
* @returns {function}
173+
*/
174+
function surrounder(open, close){
175+
return function(){
176+
let middle = values(arguments).join(' ');
177+
return `${open} ${middle} ${close}`;
178+
};
179+
}
180+
181+
/**
182+
* @param {number} left
183+
* @param {number} right
184+
*
185+
* @returns {function}
186+
*
187+
* @example
188+
*
189+
* within(0,1)(0.5) // => true
190+
*
191+
* within(100, 300)(40) // => false
192+
*/
193+
function within(left, right) {
194+
return function within(value) {
195+
return ((value >= left) && (value <= right));
196+
};
197+
}

package.json

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "LuceneJS",
3+
"version": "0.0.1",
4+
"description": "Build Lucene queries by defining lucene query builders",
5+
"main": "index.js",
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/bas080/lucene-query-string-builder.git"
9+
},
10+
"scripts": {
11+
"setup": "npm install",
12+
"lint": "eslint index.js test/spec.js",
13+
"test": "mocha -b",
14+
"clean": "npm prune && rimraf dist",
15+
"build": "npm run lint && npm run test && npm run setup && npm run clean && mkdir -p dist && npm run buildjs",
16+
"buildjs": "browserify index.js -o dist/lucene.js -t [ babelify --presets [ es2015 ] ] && npm run minify",
17+
"minify": "uglifyjs dist/lucene.js -c -m -o dist/lucene.min.js"
18+
},
19+
"keywords": [
20+
"lucene",
21+
"query",
22+
"builder"
23+
],
24+
"author": "Bas Huis <[email protected]>",
25+
"license": "MIT",
26+
"devDependencies": {
27+
"babel-preset-es2015": "^6.6.0",
28+
"babelify": "^7.3.0",
29+
"browserify": "^13.0.0",
30+
"chai": "~3.5.0",
31+
"eslint": "~2.8.0",
32+
"mocha": "~2.4.5",
33+
"rimraf": "^2.5.2",
34+
"uglifyjs": "^2.4.10"
35+
},
36+
"dependencies": {
37+
"complement": "^1.1.0",
38+
"existy": "^1.0.1",
39+
"object-values": "^1.0.0"
40+
}
41+
}

0 commit comments

Comments
 (0)