« get me outta code hell

mtui - Music Text User Interface - user-friendly command line music player
about summary refs log tree commit diff
path: root/playlist-utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'playlist-utils.js')
-rw-r--r--playlist-utils.js80
1 files changed, 57 insertions, 23 deletions
diff --git a/playlist-utils.js b/playlist-utils.js
index 452b705..68cba56 100644
--- a/playlist-utils.js
+++ b/playlist-utils.js
@@ -520,36 +520,70 @@ function getTrackIndexInParent(track) {
 
 const nameWithoutTrackNumberSymbol = Symbol('Cached name without track number')
 function getNameWithoutTrackNumber(track) {
-  // Be lazy and reuse an old value if possible! Don't do this if the track's
-  // name has changed.
-  const [oldName, cachedValue] = track[nameWithoutTrackNumberSymbol] || []
-  if (cachedValue && track.name === oldName) {
-    return cachedValue
+  // A "part" is a series of numeric digits, separated from other parts by
+  // whitespace and dashes, always preceding either the first non-numeric/
+  // separator character or (if there are no such characters) the first word
+  // (i.e. last whitespace).
+  const getNumberOfParts = ({ name }) => {
+    const match = name.match(/[^0-9\-\s]/)
+    if (match) {
+      name = name.slice(0, match.index)
+    } else if (name.includes(' ')) {
+      name = name.slice(0, name.lastIndexOf(' '))
+    } else {
+      return 0
+    }
+    name = name.replace(/[\-\s]+$/, '')
+    return name.split(/[\-\s]+/g).length
+  }
+
+  const removeParts = (name, numParts) => {
+    const regex = new RegExp(`([0-9]+[\\-\\s]+){${numParts},${numParts}}`)
+    return track.name.replace(regex, '')
   }
 
-  // This is the expression that matches a track number at the start of
-  // a track's title.
-  const regex = /^[0-9\-\s]+/
+  // Despite this function returning a single string for one track, that value
+  // depends on the names of all other tracks under the same parent. We still
+  // store individual track -> name data on the track object, but the parent
+  // gets an additional cache for the names of its children tracks as well as
+  // the number of "parts" (the value directly based upon those names, and
+  // useful in computing the name data for other children tracks).
 
-  // First we need to determine whether every track in the group even starts
-  // with a track number.
   const parent = track[parentSymbol]
   if (parent) {
-    const names = parent.items.filter(isTrack).map(t => t.name)
-    if (names.some(name => !regex.test(name))) {
-      // If any of the names don't match the track number regex, just return
-      // the track name unmodified.
-      return track.name
+    const [trackNames, cachedNumParts] = parent[nameWithoutTrackNumberSymbol] || []
+    const tracks = parent.items.filter(isTrack)
+    if (trackNames && tracks.length === trackNames.length && tracks.every((t, i) => t.name === trackNames[i])) {
+      const [, oldName, oldNumParts, cachedValue] = track[nameWithoutTrackNumberSymbol] || []
+      if (cachedValue && track.name === oldName && cachedNumParts === oldNumParts) {
+        return cachedValue
+      } else {
+        // Individual track cache outdated.
+        const value = removeParts(track.name, cachedNumParts)
+        track[nameWithoutTrackNumberSymbol] = [true, track.name, cachedNumParts, value]
+        return value
+      }
+    } else {
+      // Group (parent) cache outdated.
+      const numParts = Math.min(...tracks.map(getNumberOfParts))
+      parent[nameWithoutTrackNumberSymbol] = [tracks.map(t => t.name), numParts]
+      // Parent changed so track cache changed is outdated too.
+      const value = removeParts(track.name, numParts)
+      track[nameWithoutTrackNumberSymbol] = [true, track.name, numParts, value]
+      return value
+    }
+  } else {
+    const [oldHadParent, oldName, , cachedValue] = track[nameWithoutTrackNumberSymbol] || []
+    if (cachedValue && !oldHadParent && track.name === oldName) {
+      return cachedValue
+    } else {
+      // Track cache outdated.
+      const numParts = getNumberOfParts(track)
+      const value = removeParts(track.name, numParts)
+      track[nameWithoutTrackNumberSymbol] = [false, track.name, numParts, value]
+      return value
     }
   }
-
-  // Now actually perform the replacement to get rid of the track number!
-  const value = track.name.replace(regex, '')
-
-  // Cache the value, so we don't need to do this whole process again.
-  track[nameWithoutTrackNumberSymbol] = [track.name, value]
-
-  return value
 }
 
 function isGroup(obj) {