« get me outta code hell

Add --collapse-groups option - http-music - Command-line music player + utils (not a server!)
about summary refs log tree commit diff
path: root/src/playlist-utils.js
diff options
context:
space:
mode:
authorFlorrie <towerofnix@gmail.com>2017-09-26 20:59:59 -0300
committerFlorrie <towerofnix@gmail.com>2017-09-26 20:59:59 -0300
commit78dc49edf83ad4eec8fc66330543eb601c2b2689 (patch)
treeb66c9185df786439055a5ccf4727c680f54916d8 /src/playlist-utils.js
parent8d99a28e2466c43ec554904ef90d09109f2c1004 (diff)
Add --collapse-groups option
Diffstat (limited to 'src/playlist-utils.js')
-rw-r--r--src/playlist-utils.js126
1 files changed, 116 insertions, 10 deletions
diff --git a/src/playlist-utils.js b/src/playlist-utils.js
index 5c8f206..6452559 100644
--- a/src/playlist-utils.js
+++ b/src/playlist-utils.js
@@ -58,9 +58,10 @@ function updateGroupFormat(group) {
   groupObj = Object.assign(defaultGroup, groupObj)
 
   groupObj.items = groupObj.items.map(item => {
-    // Theoretically this wouldn't work on downloader-args where the value
-    // isn't a string..
-    if (typeof item[1] === 'string' || item.downloaderArg) {
+    // Check if it's a group; if not, it's probably a track.
+    if (typeof item[1] === 'array' || item.items) {
+      item = updateGroupFormat(item)
+    } else {
       item = updateTrackFormat(item)
 
       // TODO: Should this also apply to groups? Is recursion good? Probably
@@ -72,8 +73,6 @@ function updateGroupFormat(group) {
       if (groupObj.apply) {
         Object.assign(item, groupObj.apply)
       }
-    } else {
-      item = updateGroupFormat(item)
     }
 
     item[parentSymbol] = groupObj
@@ -131,9 +130,7 @@ function flattenGrouplike(grouplike) {
   return {
     items: grouplike.items.map(item => {
       if (isGroup(item)) {
-        const flat = flattenGrouplike(item).items
-
-        return flat
+        return flattenGrouplike(item).items
       } else {
         return [item]
       }
@@ -141,6 +138,53 @@ function flattenGrouplike(grouplike) {
   }
 }
 
+function partiallyFlattenGrouplike(grouplike, resultDepth) {
+  // Flattens a grouplike so that it is never more than a given number of
+  // groups deep, INCLUDING the "top" group -- e.g. a resultDepth of 2
+  // means that there can be one level of groups remaining in the resulting
+  // grouplike, plus the top group.
+
+  if (resultDepth <= 1) {
+    return flattenGrouplike(grouplike)
+  }
+
+  const items = grouplike.items.map(item => {
+    if (isGroup(item)) {
+      return {items: partiallyFlattenGrouplike(item, resultDepth - 1).items}
+    } else {
+      return item
+    }
+  })
+
+  return {items}
+}
+
+function collapseGrouplike(grouplike) {
+  // Similar to partiallyFlattenGrouplike, but doesn't discard the individual
+  // ordering of tracks; rather, it just collapses them all to one level.
+
+  // Gather the groups. The result is an array of groups.
+  // Collapsing [Kar/Baz/Foo, Kar/Baz/Lar] results in [Foo, Lar].
+  // Aha! Just collect the top levels.
+  // Only trouble is what to do with groups that contain both groups and
+  // tracks. Maybe give them their own separate group (e.g. Baz).
+
+  const subgroups = grouplike.items.filter(x => isGroup(x))
+  const nonGroups = grouplike.items.filter(x => !isGroup(x))
+
+  // Get each group's own collapsed groups, and store them all in one big
+  // array.
+  const ret = subgroups.map(group => {
+    return collapseGrouplike(group).items
+  }).reduce((a, b) => a.concat(b), [])
+
+  if (nonGroups.length) {
+    ret.unshift({name: grouplike.name, items: nonGroups})
+  }
+
+  return {items: ret}
+}
+
 function filterPlaylistByPathString(playlist, pathString) {
   // Calls filterGroupContentsByPath, taking an unparsed path string.
 
@@ -308,13 +352,13 @@ function parsePathString(pathString) {
 }
 
 function isGroup(obj) {
-  return obj && obj.items
+  return !!(obj && obj.items)
 
   // return Array.isArray(array[1])
 }
 
 function isTrack(obj) {
-  return obj && obj.downloaderArg
+  return !!(obj && obj.downloaderArg)
 
   // return typeof array[1] === 'string'
 }
@@ -358,6 +402,7 @@ module.exports = {
   parentSymbol,
   updatePlaylistFormat, updateTrackFormat,
   flattenGrouplike,
+  partiallyFlattenGrouplike, collapseGrouplike,
   filterPlaylistByPathString, filterGrouplikeByPath,
   removeGroupByPathString, removeGroupByPath,
   getPlaylistTreeString,
@@ -503,4 +548,65 @@ if (require.main === module) {
     console.log('- (//foo/////bar//) should return [foo, bar]')
     assertArray(parsePathString('//foo/////bar//'), ['foo', 'bar'])
   }
+
+  console.log('partiallyFlattenGrouplike')
+
+  test: {
+    console.log('- ([[a1, [aa1]], out], 2) should return [[a1, aa1], out]')
+
+    const playlist = updatePlaylistFormat({name: 'top', items: [
+      {name: 'a', items: [
+        {name: 'a1'},
+        {name: 'aa', items: [
+          {name: 'aa1'}
+        ]}
+      ]},
+      {name: 'out'}
+    ]})
+
+    const result = partiallyFlattenGrouplike(playlist, 2)
+
+    console.log('  -> ' + JSON.stringify(result, null))
+
+    // TODO: A nicer way to compare playlists, haha.
+    assert(result.items.length, 2)
+    assert(result.items[0].items.length, 2)
+    assert(result.items[0].items[0].name, 'a1')
+    assert(result.items[0].items[1].name, 'aa1')
+    assert(result.items[1].name, 'out')
+  }
+
+  console.log('collapseGrouplike')
+
+  test: {
+    console.log('- (top: [a: [a1, aa: [aa1], a2], out])')
+
+    const playlist = updatePlaylistFormat({name: 'top', items: [
+      {name: 'a', items: [
+        {name: 'a1'},
+        {name: 'aa', items: [
+          {name: 'aa1'}
+        ]},
+        {name: 'a2'}
+      ]},
+      {name: 'out'}
+    ]})
+
+    const result = collapseGrouplike(playlist)
+
+    console.log('  -> ' + JSON.stringify(result, null))
+
+    // output should be [top: [out], a: [a1, a2], aa: [aa1]]
+    assert(result.items.length, 3)
+    assert(result.items[0].name, 'top')
+    assert(result.items[0].items.length, 1)
+    assert(result.items[0].items[0].name, 'out')
+    assert(result.items[1].name, 'a')
+    assert(result.items[1].items.length, 2)
+    assert(result.items[1].items[0].name, 'a1')
+    assert(result.items[1].items[1].name, 'a2')
+    assert(result.items[2].name, 'aa')
+    assert(result.items[2].items.length, 1)
+    assert(result.items[2].items[0].name, 'aa1')
+  }
 }