From 31692325a13be15d75d739c34ab47047ac45fde2 Mon Sep 17 00:00:00 2001 From: Florrie Date: Mon, 27 Nov 2017 22:00:11 -0400 Subject: Publish smart-playlist command properly --- src/smart-playlist.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/smart-playlist.js') diff --git a/src/smart-playlist.js b/src/smart-playlist.js index 76cd877..4d20e80 100644 --- a/src/smart-playlist.js +++ b/src/smart-playlist.js @@ -6,7 +6,7 @@ const { getCrawlerByName } = require('./crawlers') const { promisify } = require('util') const readFile = promisify(fs.readFile) -async function processItem(item) { +async function processSmartPlaylist(item) { // Object.assign is used so that we keep original properties, e.g. "name" // or "apply". (It's also used so we return copies of original objects.) @@ -25,15 +25,13 @@ async function processItem(item) { return Object.assign({}, item, await crawl(...args)) } else if ('items' in item) { return Object.assign({}, item, { - items: await Promise.all(item.items.map(processItem)) + items: await Promise.all(item.items.map(processSmartPlaylist)) }) } else { return Object.assign({}, item) } } -module.exports = processItem - async function main(opts) { // TODO: Error when no file is given @@ -41,10 +39,12 @@ async function main(opts) { console.log("Usage: smart-playlist /path/to/playlist") } else { const playlist = JSON.parse(await readFile(opts[0])) - console.log(JSON.stringify(await processItem(playlist), null, 2)) + console.log(JSON.stringify(await processSmartPlaylist(playlist), null, 2)) } } +module.exports = Object.assign(main, {processSmartPlaylist}) + if (require.main === module) { main(process.argv.slice(2)) .catch(err => console.error(err)) -- cgit 1.3.0-6-gf8a5 From b2ac9246886f72bef8b96cf218ed2d803397dafa Mon Sep 17 00:00:00 2001 From: Florrie Date: Sun, 18 Feb 2018 23:44:07 -0400 Subject: Make completely new filter system See the man page for how it works now. --- src/smart-playlist.js | 111 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 10 deletions(-) (limited to 'src/smart-playlist.js') diff --git a/src/smart-playlist.js b/src/smart-playlist.js index 4d20e80..cbe5182 100644 --- a/src/smart-playlist.js +++ b/src/smart-playlist.js @@ -2,6 +2,7 @@ const fs = require('fs') const { getCrawlerByName } = require('./crawlers') +const { isGroup, filterTracks, sourceSymbol } = require('./playlist-utils') const { promisify } = require('util') const readFile = promisify(fs.readFile) @@ -10,26 +11,116 @@ async function processSmartPlaylist(item) { // Object.assign is used so that we keep original properties, e.g. "name" // or "apply". (It's also used so we return copies of original objects.) - if ('source' in item) { + const newItem = Object.assign({}, item) + + if ('source' in newItem) { const [ name, ...args ] = item.source const crawlModule = getCrawlerByName(name) - if (crawlModule === null) { + if (crawlModule) { + const { crawl } = crawlModule + Object.assign(newItem, await crawl(...args)) + } else { console.error(`No crawler by name ${name} - skipped item:`, item) - return Object.assign({}, item, {failed: true}) + newItem.failed = true } - const { crawl } = crawlModule + delete newItem.source + } else if ('items' in newItem) { + newItem.items = await Promise.all(item.items.map(processSmartPlaylist)) + } + + if ('filters' in newItem) filters: { + if (!isGroup(newItem)) { + console.warn('Filter on non-group (no effect):', newItem) + break filters + } - return Object.assign({}, item, await crawl(...args)) - } else if ('items' in item) { - return Object.assign({}, item, { - items: await Promise.all(item.items.map(processSmartPlaylist)) + newItem.filters = newItem.filters.filter(filter => { + if ('tag' in filter === false) { + console.warn('Filter is missing "tag" property (skipping this filter):', filter) + return false + } + + return true }) - } else { - return Object.assign({}, item) + + Object.assign(newItem, filterTracks(newItem, track => { + for (const filter of newItem.filters) { + const { tag } = filter + + let value = track + for (const key of tag.split('.')) { + if (key in Object(value)) { + value = value[key] + } else { + console.warn(`In tag "${tag}", key "${key}" not found.`) + console.warn('...value until now:', value) + console.warn('...track:', track) + console.warn('...filter:', filter) + return false + } + } + + if ('gt' in filter && value <= filter.gt) return false + if ('lt' in filter && value >= filter.lt) return false + if ('gte' in filter && value < filter.gte) return false + if ('lte' in filter && value > filter.lte) return false + if ('least' in filter && value < filter.least) return false + if ('most' in filter && value > filter.most) return false + if ('min' in filter && value < filter.min) return false + if ('max' in filter && value > filter.max) return false + + for (const prop of ['includes', 'contains']) { + if (prop in filter) { + if (Array.isArray(value) || typeof value === 'string') { + if (!value.includes(filter.includes)) return false + } else { + console.warn( + `Value of tag "${tag}" is not an array or string, so passing ` + + `"${prop}" does not make sense.` + ) + console.warn('...value:', value) + console.warn('...track:', track) + console.warn('...filter:', filter) + return false + } + } + } + + if (filter.regex) { + if (typeof value === 'string') { + let re + try { + re = new RegExp(filter.regex) + } catch (error) { + console.warn('Invalid regular expression:', re) + console.warn('...error message:', error.message) + console.warn('...filter:', filter) + return false + } + if (!re.test(value)) return false + } else { + console.warn( + `Value of tag "${tag}" is not a string, so passing "regex" ` + + 'does not make sense.' + ) + console.warn('...value:', value) + console.warn('...track:', track) + console.warn('...filter:', filter) + return false + } + } + } + + return true + })) + + delete newItem.filters } + + return newItem } async function main(opts) { -- cgit 1.3.0-6-gf8a5