diff --git a/ReadMe.md b/ReadMe.md index ba22d78..e5a26e8 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -11,8 +11,8 @@ tracks from episodes 12-149 and 226-230. ## music.json An object of objects, each track indexed by a normalized form of its name, which -is all lowercase alphanumeric characters only. Note that these are not enforced -or formalized by the library, but convention I have adopted. +is all lowercase alphanumeric characters only. Note that only `names` is +enforced by the library. - `names`: A list of names equivalent to this track (some tracks have duplicate names due to formatting differences) @@ -37,9 +37,20 @@ A simple interface library to use in a Lua REPL. - `add_file(file_name)` adds new tracks from the specified file (file must have one track per line, ignores empty lines) - `find(str)` finds possible track matches by normalizing the input string, - returns them in a list + returns them as a list of normalized names - `set(match, info)` match can be a list (as is returned by find) or a track name (either will be normalized), info must be a table of key-value pairs, these will be set on the matched tracks, overwriting existing values if a key is already in use - `normalize(str)` returns a normalized form of the input string + +`music.random(count, match, include, exclude)` is a little more complicated. +- `count` is the maximum number of returned names, and defaults to 1 +- `match` can be nil, a name, or a list of names (names and lists will have + their values normalized) +- `include` and `exclude` are tables of key-value pairs that must exist or not + exist, `true` values mean non-false keys, but other values must match exactly + +Example: `music.random(5, nil, {url = true}, {downloaded = true})` will return +5 random tracks from the whole database that have a url, but do not have +`downloaded` set. diff --git a/music.lua b/music.lua index 3b9808c..f170abc 100644 --- a/music.lua +++ b/music.lua @@ -43,6 +43,80 @@ function music.find(str) return matches end +-- count is maximum number of returned names, defaults to 1 +-- match can be nil, a name, or a list of names +-- include is a table of key-value pairs that must exist +-- exclude is a table of key-value pairs that must not exist +-- 'true' values mean non-false keys, any other value must match exactly +-- example: music.random(5, nil, {url = true}, {downloaded = true}) will return +-- 5 random tracks that have a url, but have not been downloaded +function music.random(count, match, include, exclude) + local matches, results = {}, {} + if not music.seed then music.seed = os.time() math.randomseed(music.seed) end + count = math.floor(tonumber(count) or 1) + local function filter(match, include, exclude) + local matches = {} + for _, name in ipairs(match) do + local valid = true + local compare = music.data[name] + for k,v in pairs(include) do + if v == true then + if not compare[k] then + valid = false + break + end + else + if compare[k] ~= v then + valid = false + break + end + end + end + for k,v in pairs(exclude) do + if v == true then + if compare[k] then + valid = false + break + end + else + if compare[k] == v then + valid = false + break + end + end + end + if valid then + table.insert(matches, name) + end + end + return matches + end + if type(match) == "table" then + for _, v in ipairs(match) do + table.insert(matches, music.normalize(v)) + end + elseif type(match) == "string" then + matches[1] = music.normalize(match) + else + for k in pairs(music.data) do + table.insert(matches, k) + end + end + matches = filter(matches, include or {}, exclude or {}) + while count > 0 and #matches > 0 do + for i = #matches, 1, -1 do + if math.random() > 0.5 then + table.insert(results, table.remove(matches, i)) + count = count - 1 + end + if count < 1 then + break + end + end + end + return results +end + function music.add(name) local normalized = music.normalize(name) local entry = music.data[normalized] @@ -75,7 +149,7 @@ function music.add_file(file_name) return true end --- match is a normalized name or a list of names, info is a table of key-value pairs to be set +-- match is a name or a list of names, info is a table of key-value pairs to be set function music.set(match, info) if type(match) == "table" then for _, value in ipairs(match) do