music-library-index
Advanced tools
Comparing version 1.4.0 to 2.0.0
432
index.js
@@ -18,4 +18,2 @@ var removeDiacritics = require('diacritics').remove; | ||
]; | ||
MusicLibraryIndex.parseQuery = parseQuery; | ||
MusicLibraryIndex.tokenizeQuery = tokenizeQuery; | ||
@@ -32,3 +30,5 @@ function MusicLibraryIndex(options) { | ||
this.trackComparator = this.trackComparator.bind(this); | ||
this.clear(); | ||
this.labelComparator = this.labelComparator.bind(this); | ||
this.clearTracks(); | ||
this.clearLabels(); | ||
} | ||
@@ -95,2 +95,6 @@ | ||
MusicLibraryIndex.prototype.labelComparator = function(a, b) { | ||
return this.titleCompare(a.name, b.name); | ||
} | ||
MusicLibraryIndex.prototype.getAlbumKey = function(track) { | ||
@@ -106,3 +110,3 @@ var artistName = track.albumArtistName || | ||
MusicLibraryIndex.prototype.clear = function() { | ||
MusicLibraryIndex.prototype.clearTracks = function() { | ||
this.trackTable = {}; | ||
@@ -113,9 +117,12 @@ this.artistTable = {}; | ||
this.albumList = []; | ||
this.dirty = false; | ||
this.dirtyAlbumTable = false; | ||
this.dirtyTracks = false; | ||
}; | ||
MusicLibraryIndex.prototype.clearLabels = function() { | ||
this.labelTable = {}; | ||
this.labelList = []; | ||
this.dirtyLabels = false; | ||
}; | ||
MusicLibraryIndex.prototype.rebuildAlbumTable = function() { | ||
if (!this.dirtyAlbumTable) return; | ||
// builds everything from trackTable | ||
@@ -153,4 +160,2 @@ this.artistTable = {}; | ||
this.dirtyAlbumTable = false; | ||
function createAlbum() { | ||
@@ -168,4 +173,4 @@ var album = { | ||
MusicLibraryIndex.prototype.rebuild = function() { | ||
if (!this.dirty) return; | ||
MusicLibraryIndex.prototype.rebuildTracks = function() { | ||
if (!this.dirtyTracks) return; | ||
this.rebuildAlbumTable(); | ||
@@ -233,3 +238,3 @@ this.albumList.sort(this.albumComparator); | ||
this.dirty = false; | ||
this.dirtyTracks = false; | ||
@@ -245,14 +250,22 @@ function createArtist() { | ||
MusicLibraryIndex.prototype.rebuildLabels = function() { | ||
if (!this.dirtyLabels) return; | ||
this.labelList = []; | ||
for (var id in this.labelTable) { | ||
var label = this.labelTable[id]; | ||
this.labelList.push(label); | ||
} | ||
this.labelList.sort(this.labelComparator); | ||
this.labelList.forEach(function(label, index) { | ||
label.index = index; | ||
}); | ||
this.dirtyLabels = false; | ||
} | ||
MusicLibraryIndex.prototype.addTrack = function(track) { | ||
var oldTrack = this.trackTable[track.key]; | ||
this.dirty = this.dirty || | ||
oldTrack == null || | ||
oldTrack.artistName !== track.artistName || | ||
oldTrack.albumArtistName !== track.albumArtistName || | ||
oldTrack.albumName !== track.albumName || | ||
oldTrack.track !== track.track || | ||
oldTrack.disc !== track.disc || | ||
oldTrack.year !== track.year; | ||
this.dirtyAlbumTable = this.dirty; | ||
this.trackTable[track.key] = track; | ||
this.dirtyTracks = true; | ||
} | ||
@@ -262,6 +275,15 @@ | ||
delete this.trackTable[key]; | ||
this.dirty = true; | ||
this.dirtyAlbumTable = true; | ||
this.dirtyTracks = true; | ||
} | ||
MusicLibraryIndex.prototype.addLabel = function(label) { | ||
this.labelTable[label.id] = label; | ||
this.dirtyLabels = true; | ||
} | ||
MusicLibraryIndex.prototype.removeLabel = function(id) { | ||
delete this.labelTable[id]; | ||
this.dirtyLabels = true; | ||
} | ||
MusicLibraryIndex.prototype.search = function(query) { | ||
@@ -275,3 +297,3 @@ var searchResults = new MusicLibraryIndex({ | ||
var matcher = parseQuery(query); | ||
var matcher = this.parseQuery(query); | ||
@@ -285,5 +307,4 @@ var track; | ||
} | ||
searchResults.dirty = true; | ||
searchResults.dirtyAlbumTable = true; | ||
searchResults.rebuild(); | ||
searchResults.dirtyTracks = true; | ||
searchResults.rebuildTracks(); | ||
@@ -294,45 +315,2 @@ return searchResults; | ||
function makeFuzzyTextMatcher(term) { | ||
// make this publicly modifiable | ||
fuzzyTextMatcher.fuzzyTerm = formatSearchable(term);; | ||
fuzzyTextMatcher.toString = function() { | ||
return "(fuzzy " + JSON.stringify(fuzzyTextMatcher.fuzzyTerm) + ")" | ||
}; | ||
return fuzzyTextMatcher; | ||
function fuzzyTextMatcher(track) { | ||
return track.fuzzySearchTags.indexOf(fuzzyTextMatcher.fuzzyTerm) !== -1; | ||
} | ||
} | ||
function makeExactTextMatcher(term) { | ||
exactTextMatcher.toString = function() { | ||
return "(exact " + JSON.stringify(term) + ")" | ||
}; | ||
return exactTextMatcher; | ||
function exactTextMatcher(track) { | ||
return track.exactSearchTags.indexOf(term) !== -1; | ||
} | ||
} | ||
function makeAndMatcher(children) { | ||
if (children.length === 1) return children[0]; | ||
andMatcher.toString = function() { | ||
return "(" + children.join(" AND ") + ")"; | ||
}; | ||
return andMatcher; | ||
function andMatcher(track) { | ||
for (var i = 0; i < children.length; i++) { | ||
if (!children[i](track)) return false; | ||
} | ||
return true; | ||
} | ||
} | ||
function makeNotMatcher(subMatcher) { | ||
notMatcher.toString = function() { | ||
return "(not " + subMatcher.toString() + ")"; | ||
}; | ||
return notMatcher; | ||
function notMatcher(track) { | ||
return !subMatcher(track); | ||
} | ||
} | ||
var tokenizerRegex = new RegExp( | ||
@@ -343,4 +321,5 @@ '( +)' +'|'+ // 1: whitespace between terms (not in quotes) | ||
'(not:)' +'|'+ // 4: not: prefix | ||
'("(?:[^"\\\\]|\\\\.)*"\\)*)' +'|'+ // 5: quoted thing. can end with parentheses | ||
'([^ ]+)', // 6: normal word. can end with parentheses | ||
'(label:)' +'|'+ // 5: label: prefix | ||
'("(?:[^"\\\\]|\\\\.)*"\\)*)' +'|'+ // 6: quoted thing. can end with parentheses | ||
'([^ ]+)', // 7: normal word. can end with parentheses | ||
"g"); | ||
@@ -351,119 +330,228 @@ var WHITESPACE = 1; | ||
var NOT = 4; | ||
var QUOTED_THING = 5; | ||
var NORMAL_WORD = 6; | ||
function tokenizeQuery(query) { | ||
tokenizerRegex.lastIndex = 0; | ||
var tokens = []; | ||
while (true) { | ||
var match = tokenizerRegex.exec(query); | ||
if (match == null) break; | ||
var term = match[0]; | ||
var type; | ||
for (var i = 1; i < match.length; i++) { | ||
if (match[i] != null) { | ||
type = i; | ||
break; | ||
var LABEL = 5; | ||
var QUOTED_THING = 6; | ||
var NORMAL_WORD = 7; | ||
MusicLibraryIndex.prototype.parseQuery = function(query) { | ||
var self = this; | ||
return parse(query); | ||
function parse(query) { | ||
var tokens = tokenizeQuery(query); | ||
var tokenIndex = 0; | ||
return parseAnd(null); | ||
function parseAnd(waitForTokenType) { | ||
var matchers = []; | ||
var justSawWhitespace = true; | ||
while (tokenIndex < tokens.length) { | ||
var token = tokens[tokenIndex++]; | ||
switch (token.type) { | ||
case OPEN_PARENTHESIS: | ||
var subMatcher = parseAnd(CLOSE_PARENTHESIS); | ||
matchers.push(subMatcher); | ||
break; | ||
case CLOSE_PARENTHESIS: | ||
if (waitForTokenType === CLOSE_PARENTHESIS) return makeAndMatcher(matchers); | ||
// misplaced ) | ||
var previousMatcher = matchers[matchers.length - 1]; | ||
if (!justSawWhitespace && previousMatcher != null && previousMatcher.fuzzyTerm != null) { | ||
// slap it on the back of the last guy | ||
previousMatcher.fuzzyTerm += token.text; | ||
} else { | ||
// it's its own term | ||
matchers.push(makeFuzzyTextMatcher(token.text)); | ||
} | ||
break; | ||
case NOT: | ||
matchers.push(parseNot()); | ||
break; | ||
case LABEL: | ||
matchers.push(parseLabel()); | ||
break; | ||
case QUOTED_THING: | ||
if (token.text.length !== 0) { | ||
matchers.push(makeExactTextMatcher(token.text)); | ||
} | ||
break; | ||
case NORMAL_WORD: | ||
matchers.push(makeFuzzyTextMatcher(token.text)); | ||
break; | ||
} | ||
var justSawWhitespace = token.type === WHITESPACE; | ||
} | ||
return makeAndMatcher(matchers); | ||
} | ||
switch (type) { | ||
case WHITESPACE: | ||
case OPEN_PARENTHESIS: | ||
case CLOSE_PARENTHESIS: | ||
case NOT: | ||
tokens.push({type: type, text: term}); | ||
break; | ||
case QUOTED_THING: | ||
case NORMAL_WORD: | ||
var endParensCount = /\)*$/.exec(term)[0].length; | ||
term = term.substr(0, term.length - endParensCount); | ||
if (type === QUOTED_THING) { | ||
// strip quotes | ||
term = /^"(.*)"$/.exec(term)[1]; | ||
// handle escapes | ||
term = term.replace(/\\(.)/g, "$1"); | ||
} | ||
tokens.push({type: type, text: term}); | ||
for (var i = 0; i < endParensCount; i++) { | ||
tokens.push({type: CLOSE_PARENTHESIS, text: ")"}); | ||
} | ||
break; | ||
function parseNot() { | ||
if (tokenIndex >= tokens.length) { | ||
// "not:" then EOF. treat it as a fuzzy matcher for "not:" | ||
return makeFuzzyTextMatcher(tokens[tokenIndex - 1].text); | ||
} | ||
var token = tokens[tokenIndex++]; | ||
switch (token.type) { | ||
case WHITESPACE: | ||
case CLOSE_PARENTHESIS: | ||
// "not: " or "not:)" | ||
// Treat the "not:" as a fuzzy matcher, | ||
// and let the parent deal with this token | ||
tokenIndex--; | ||
return makeFuzzyTextMatcher(tokens[tokenIndex - 1].text); | ||
case OPEN_PARENTHESIS: | ||
// "not:(" | ||
return makeNotMatcher(parseAnd(CLOSE_PARENTHESIS)); | ||
case NOT: | ||
// double negative all the way. | ||
return makeNotMatcher(parseNot()); | ||
case LABEL: | ||
return makeNotMatcher(parseLabel()); | ||
case QUOTED_THING: | ||
return makeNotMatcher(makeExactTextMatcher(token.text)); | ||
case NORMAL_WORD: | ||
return makeNotMatcher(makeFuzzyTextMatcher(token.text)); | ||
} | ||
throw new Error("unreachable"); | ||
} | ||
} | ||
return tokens; | ||
} | ||
function parseQuery(query) { | ||
var tokens = tokenizeQuery(query); | ||
var tokenIndex = 0; | ||
return parseAnd(null); | ||
function parseAnd(waitForTokenType) { | ||
var matchers = []; | ||
var justSawWhitespace = true; | ||
while (tokenIndex < tokens.length) { | ||
function parseLabel() { | ||
if (tokenIndex >= tokens.length) { | ||
// "label:" then EOF. treat it as a fuzzy matcher for "label:" | ||
return makeFuzzyTextMatcher(tokens[tokenIndex - 1].text); | ||
} | ||
var token = tokens[tokenIndex++]; | ||
switch (token.type) { | ||
case WHITESPACE: | ||
case CLOSE_PARENTHESIS: | ||
// "label: " or "label:)" | ||
// Treat the "label:" as a fuzzy matcher, | ||
// and let the parent deal with this token | ||
tokenIndex--; | ||
return makeFuzzyTextMatcher(tokens[tokenIndex - 1].text); | ||
case OPEN_PARENTHESIS: | ||
var subMatcher = parseAnd(CLOSE_PARENTHESIS); | ||
matchers.push(subMatcher); | ||
case NOT: | ||
case LABEL: | ||
case QUOTED_THING: | ||
case NORMAL_WORD: | ||
// "label:(" or "label:not:" or "label:label:" or 'label:"Asdf"' or "label:Asdf" | ||
return makeLabelMatcher(token.text); | ||
} | ||
throw new Error("unreachable"); | ||
} | ||
} | ||
function makeFuzzyTextMatcher(term) { | ||
// make this publicly modifiable | ||
fuzzyTextMatcher.fuzzyTerm = formatSearchable(term);; | ||
fuzzyTextMatcher.toString = function() { | ||
return "(fuzzy " + JSON.stringify(fuzzyTextMatcher.fuzzyTerm) + ")" | ||
}; | ||
return fuzzyTextMatcher; | ||
function fuzzyTextMatcher(track) { | ||
return track.fuzzySearchTags.indexOf(fuzzyTextMatcher.fuzzyTerm) !== -1; | ||
} | ||
} | ||
function makeExactTextMatcher(term) { | ||
exactTextMatcher.toString = function() { | ||
return "(exact " + JSON.stringify(term) + ")" | ||
}; | ||
return exactTextMatcher; | ||
function exactTextMatcher(track) { | ||
return track.exactSearchTags.indexOf(term) !== -1; | ||
} | ||
} | ||
function makeAndMatcher(children) { | ||
if (children.length === 1) return children[0]; | ||
andMatcher.toString = function() { | ||
return "(" + children.join(" AND ") + ")"; | ||
}; | ||
return andMatcher; | ||
function andMatcher(track) { | ||
for (var i = 0; i < children.length; i++) { | ||
if (!children[i](track)) return false; | ||
} | ||
return true; | ||
} | ||
} | ||
function makeNotMatcher(subMatcher) { | ||
notMatcher.toString = function() { | ||
return "(not " + subMatcher.toString() + ")"; | ||
}; | ||
return notMatcher; | ||
function notMatcher(track) { | ||
return !subMatcher(track); | ||
} | ||
} | ||
function makeLabelMatcher(text) { | ||
var id = (function() { | ||
for (var id in self.labelTable) { | ||
if (self.labelTable[id].name === text) { | ||
return id; | ||
} | ||
} | ||
return null; | ||
})(); | ||
if (id != null) { | ||
labelMatcher.toString = function() { | ||
return "(label " + JSON.stringify(id) + ")"; | ||
}; | ||
return labelMatcher; | ||
} else { | ||
// not even a real label | ||
alwaysFail.toString = function() { | ||
return "(label <none>)"; | ||
}; | ||
return alwaysFail; | ||
} | ||
function labelMatcher(track) { | ||
return track.labels != null && track.labels[id]; | ||
} | ||
function alwaysFail() { | ||
return false; | ||
} | ||
} | ||
function tokenizeQuery(query) { | ||
tokenizerRegex.lastIndex = 0; | ||
var tokens = []; | ||
while (true) { | ||
var match = tokenizerRegex.exec(query); | ||
if (match == null) break; | ||
var term = match[0]; | ||
var type; | ||
for (var i = 1; i < match.length; i++) { | ||
if (match[i] != null) { | ||
type = i; | ||
break; | ||
} | ||
} | ||
switch (type) { | ||
case WHITESPACE: | ||
case OPEN_PARENTHESIS: | ||
case CLOSE_PARENTHESIS: | ||
if (waitForTokenType === CLOSE_PARENTHESIS) return makeAndMatcher(matchers); | ||
// misplaced ) | ||
var previousMatcher = matchers[matchers.length - 1]; | ||
if (!justSawWhitespace && previousMatcher != null && previousMatcher.fuzzyTerm != null) { | ||
// slap it on the back of the last guy | ||
previousMatcher.fuzzyTerm += token.text; | ||
} else { | ||
// it's its own term | ||
matchers.push(makeFuzzyTextMatcher(token.text)); | ||
} | ||
break; | ||
case NOT: | ||
matchers.push(parseNot()); | ||
case LABEL: | ||
tokens.push({type: type, text: term}); | ||
break; | ||
case QUOTED_THING: | ||
if (token.text.length !== 0) { | ||
matchers.push(makeExactTextMatcher(token.text)); | ||
case NORMAL_WORD: | ||
var endParensCount = /\)*$/.exec(term)[0].length; | ||
term = term.substr(0, term.length - endParensCount); | ||
if (type === QUOTED_THING) { | ||
// strip quotes | ||
term = /^"(.*)"$/.exec(term)[1]; | ||
// handle escapes | ||
term = term.replace(/\\(.)/g, "$1"); | ||
} | ||
tokens.push({type: type, text: term}); | ||
for (var i = 0; i < endParensCount; i++) { | ||
tokens.push({type: CLOSE_PARENTHESIS, text: ")"}); | ||
} | ||
break; | ||
case NORMAL_WORD: | ||
matchers.push(makeFuzzyTextMatcher(token.text)); | ||
break; | ||
} | ||
var justSawWhitespace = token.type === WHITESPACE; | ||
} | ||
return makeAndMatcher(matchers); | ||
return tokens; | ||
} | ||
function parseNot() { | ||
if (tokenIndex >= tokens.length) { | ||
// "not:" then EOF. treat it as a fuzzy matcher for "not:" | ||
return makeFuzzyTextMatcher(tokens[tokenIndex - 1].text); | ||
} | ||
var token = tokens[tokenIndex++]; | ||
switch (token.type) { | ||
case WHITESPACE: | ||
case CLOSE_PARENTHESIS: | ||
// "not: " or "not:)" | ||
// Treat the "not:" as a fuzzy matcher, | ||
// and let the parent deal with this token | ||
tokenIndex--; | ||
return makeFuzzyTextMatcher(tokens[tokenIndex - 1].text); | ||
case OPEN_PARENTHESIS: | ||
// "not:(" | ||
return makeNotMatcher(parseAnd(CLOSE_PARENTHESIS)); | ||
case NOT: | ||
// double negative all the way. | ||
return makeNotMatcher(parseNot()); | ||
case QUOTED_THING: | ||
return makeNotMatcher(makeExactTextMatcher(token.text)); | ||
case NORMAL_WORD: | ||
return makeNotMatcher(makeFuzzyTextMatcher(token.text)); | ||
} | ||
throw new Error("unreachable"); | ||
} | ||
}; | ||
} | ||
function getOrCreate(key, table, initObjFunc) { | ||
@@ -470,0 +558,0 @@ var result = table[key]; |
{ | ||
"name": "music-library-index", | ||
"version": "1.4.0", | ||
"version": "2.0.0", | ||
"description": "build a searchable javascript object model given track metadata objects", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -58,6 +58,14 @@ # Music Library Index | ||
albumArtistName: "Anberlin", | ||
label: {"favorites_id": 1}, | ||
}); | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
library.addLabel({ | ||
id: "favorites_id", | ||
name: "favorites", | ||
}); | ||
library.rebuildLabels(); | ||
console.log(library.artistList[0]); | ||
@@ -64,0 +72,0 @@ console.log(library.trackTable); |
@@ -15,4 +15,4 @@ // usage: node benchmark.js path/to/index.json ["search query" ...] | ||
} | ||
timed("rebuild()", function() { | ||
library.rebuild(); | ||
timed("rebuildTracks()", function() { | ||
library.rebuildTracks(); | ||
}); | ||
@@ -19,0 +19,0 @@ for (var i = 3; i < process.argv.length; i++) { |
144
test/test.js
@@ -29,3 +29,3 @@ var assert = require('assert'); | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -132,3 +132,3 @@ it("trackTable", function() { | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -182,3 +182,3 @@ it("filed in various artists", function() { | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -215,3 +215,3 @@ it("still knows they're in the same album", function() { | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -249,3 +249,3 @@ it("detects that they are different", function() { | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -279,3 +279,3 @@ it("only creates one album", function() { | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -342,3 +342,3 @@ it("should be filed under the artist", function() { | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -372,3 +372,3 @@ it("sorts by disc before track", function() { | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -399,3 +399,3 @@ it("shouldn't be various artists", function() { | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
var results = library; | ||
@@ -450,3 +450,3 @@ | ||
}); | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -460,26 +460,86 @@ it("shouldn't be various artists", function() { | ||
describe("label management", function() { | ||
var library = new MusicLibraryIndex(); | ||
library.addLabel({ | ||
id: "wrong_id", | ||
name: "wrong", | ||
}); | ||
library.rebuildLabels(); | ||
library.clearLabels(); | ||
library.addLabel({ | ||
id: "techno_id", | ||
name: "techno", | ||
}); | ||
library.addLabel({ | ||
id: "jazz_id", | ||
name: "jazz", | ||
}); | ||
library.rebuildLabels(); | ||
it("clearLabels, addLabel", function() { | ||
assert.strictEqual(library.labelList[0].name, "jazz"); | ||
assert.strictEqual(library.labelList[1].name, "techno"); | ||
}); | ||
}); | ||
describe("parseQuery", function() { | ||
var library = new MusicLibraryIndex(); | ||
it("works", function() { | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('').toString(), '()'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('a').toString(), '(fuzzy "a")'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery(' ab cd ').toString(), '((fuzzy "ab") AND (fuzzy "cd"))'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('"a b"').toString(), '(exact "a b")'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('"a b\\" c"').toString(), '(exact "a b\\\" c")'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('\\"a b"').toString(), '((fuzzy "\\\\\\"a") AND (fuzzy "b\\""))'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('"').toString(), '(fuzzy "\\\"")'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('\\').toString(), '(fuzzy "\\\\")'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('""').toString(), '()'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('a" b"c').toString(), '((fuzzy "a\\\"") AND (fuzzy "b\\\"c"))'); | ||
assert.strictEqual(library.parseQuery('').toString(), '()'); | ||
assert.strictEqual(library.parseQuery('a').toString(), '(fuzzy "a")'); | ||
assert.strictEqual(library.parseQuery(' ab cd ').toString(), '((fuzzy "ab") AND (fuzzy "cd"))'); | ||
assert.strictEqual(library.parseQuery('"a b"').toString(), '(exact "a b")'); | ||
assert.strictEqual(library.parseQuery('"a b\\" c"').toString(), '(exact "a b\\\" c")'); | ||
assert.strictEqual(library.parseQuery('\\"a b"').toString(), '((fuzzy "\\\\\\"a") AND (fuzzy "b\\""))'); | ||
assert.strictEqual(library.parseQuery('"').toString(), '(fuzzy "\\\"")'); | ||
assert.strictEqual(library.parseQuery('\\').toString(), '(fuzzy "\\\\")'); | ||
assert.strictEqual(library.parseQuery('""').toString(), '()'); | ||
assert.strictEqual(library.parseQuery('a" b"c').toString(), '((fuzzy "a\\\"") AND (fuzzy "b\\\"c"))'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('not:A').toString(), '(not (fuzzy "a"))'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('not:"A"').toString(), '(not (exact "A"))'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('not:(a b)').toString(), '(not ((fuzzy "a") AND (fuzzy "b")))'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('not:(a)').toString(), '(not (fuzzy "a"))'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('not:not:a').toString(), '(not (not (fuzzy "a")))'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('not:').toString(), '(fuzzy "not:")'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('not: a').toString(), '((fuzzy "not:") AND (fuzzy "a"))'); | ||
assert.strictEqual(MusicLibraryIndex.parseQuery('not:)a').toString(), '((fuzzy "not:)") AND (fuzzy "a"))'); | ||
assert.strictEqual(library.parseQuery('not:A').toString(), '(not (fuzzy "a"))'); | ||
assert.strictEqual(library.parseQuery('not:"A"').toString(), '(not (exact "A"))'); | ||
assert.strictEqual(library.parseQuery('not:(a b)').toString(), '(not ((fuzzy "a") AND (fuzzy "b")))'); | ||
assert.strictEqual(library.parseQuery('not:(a)').toString(), '(not (fuzzy "a"))'); | ||
assert.strictEqual(library.parseQuery('not:not:a').toString(), '(not (not (fuzzy "a")))'); | ||
assert.strictEqual(library.parseQuery('not:').toString(), '(fuzzy "not:")'); | ||
assert.strictEqual(library.parseQuery('not: a').toString(), '((fuzzy "not:") AND (fuzzy "a"))'); | ||
assert.strictEqual(library.parseQuery('not:)a').toString(), '((fuzzy "not:)") AND (fuzzy "a"))'); | ||
}); | ||
}); | ||
describe("parseQuery with labels", function() { | ||
var library = new MusicLibraryIndex(); | ||
library.addLabel({ | ||
id: "techno_id", | ||
name: "techno", | ||
}); | ||
library.addLabel({ | ||
id: "jazz_id", | ||
name: "jazz music", | ||
}); | ||
library.addLabel({ | ||
id: "not_id", | ||
name: "not:", | ||
}); | ||
library.rebuildLabels(); | ||
it("works", function() { | ||
assert.strictEqual(library.parseQuery('label:techno').toString(), '(label "techno_id")'); | ||
assert.strictEqual(library.parseQuery('not:label:techno').toString(), '(not (label "techno_id"))'); | ||
assert.strictEqual(library.parseQuery('label:asdf').toString(), '(label <none>)'); | ||
assert.strictEqual(library.parseQuery('label:Techno').toString(), '(label <none>)'); | ||
assert.strictEqual(library.parseQuery('label:"jazz music"').toString(), '(label "jazz_id")'); | ||
assert.strictEqual(library.parseQuery('label:jazz music').toString(), '((label <none>) AND (fuzzy "music"))'); | ||
assert.strictEqual(library.parseQuery('label:""').toString(), '(label <none>)'); | ||
assert.strictEqual(library.parseQuery('label:').toString(), '(fuzzy "label:")'); | ||
assert.strictEqual(library.parseQuery('label: ').toString(), '(fuzzy "label:")'); | ||
assert.strictEqual(library.parseQuery('label:not:').toString(), '(label "not_id")'); | ||
assert.strictEqual(library.parseQuery('not:(this label:)').toString(), '(not ((fuzzy "this") AND (fuzzy "label:")))'); | ||
}); | ||
}); | ||
describe("searching with quoted seach terms", function() { | ||
@@ -531,3 +591,3 @@ var library = new MusicLibraryIndex(); | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -572,2 +632,12 @@ it("single search term returns both", function() { | ||
var library = new MusicLibraryIndex(); | ||
library.addLabel({ | ||
id: "techno_id", | ||
name: "techno", | ||
}); | ||
library.addLabel({ | ||
id: "jazz_id", | ||
name: "jazz", | ||
}); | ||
library.rebuildLabels(); | ||
library.addTrack({ | ||
@@ -578,2 +648,3 @@ key: "fUPmxjMc", | ||
albumName: "Varieté", | ||
labels: {"jazz_id": 1}, | ||
}); | ||
@@ -585,2 +656,3 @@ library.addTrack({ | ||
albumName: "Varieté", | ||
labels: {"techno_id": 1, "jazz_id": 1}, | ||
}); | ||
@@ -593,3 +665,3 @@ library.addTrack({ | ||
}); | ||
library.rebuild(); | ||
library.rebuildTracks(); | ||
@@ -604,2 +676,14 @@ it("'not:'", function() { | ||
}); | ||
it("'label:'", function () { | ||
assert.strictEqual(library.search('label:techno').artistList.length, 1); | ||
assert.strictEqual(library.search('label:jazz').artistList.length, 2); | ||
assert.strictEqual(library.search('not:label:techno').artistList.length, 2); | ||
assert.strictEqual(library.search('"label:techno"').artistList.length, 0); | ||
assert.strictEqual(library.search('not:"label:techno"').artistList.length, 3); | ||
assert.strictEqual(library.search('label:wrong').artistList.length, 0); | ||
assert.strictEqual(library.search('not:label:wrong').artistList.length, 3); | ||
assert.strictEqual(library.search('not:(label:jazz not:label:techno)').artistList.length, 2); | ||
}); | ||
}); |
45951
1126
111