« 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.js137
1 files changed, 44 insertions, 93 deletions
diff --git a/playlist-utils.js b/playlist-utils.js
index 227c985..0fe26df 100644
--- a/playlist-utils.js
+++ b/playlist-utils.js
@@ -1,16 +1,12 @@
 'use strict'
 
-const path = require('path')
-const fs = require('fs')
+import path from 'node:path'
 
-const { promisify } = require('util')
-const unlink = promisify(fs.unlink)
+import {shuffleArray} from './general-util.js'
 
-const { shuffleArray } = require('./general-util')
+export const parentSymbol = Symbol('Parent group')
 
-const parentSymbol = Symbol('Parent group')
-
-function updatePlaylistFormat(playlist) {
+export function updatePlaylistFormat(playlist) {
   const defaultPlaylist = {
     options: [],
     items: []
@@ -43,7 +39,7 @@ function updatePlaylistFormat(playlist) {
   return updateGroupFormat(fullPlaylistObj)
 }
 
-function updateGroupFormat(group) {
+export function updateGroupFormat(group) {
   const defaultGroup = {
     name: '',
     items: []
@@ -61,7 +57,7 @@ function updateGroupFormat(group) {
 
   groupObj.items = groupObj.items.map(item => {
     // Check if it's a group; if not, it's probably a track.
-    if (typeof item[1] === 'array' || item.items) {
+    if (Array.isArray(item[1]) || item.items) {
       item = updateGroupFormat(item)
     } else {
       item = updateTrackFormat(item)
@@ -85,7 +81,7 @@ function updateGroupFormat(group) {
   return groupObj
 }
 
-function updateTrackFormat(track) {
+export function updateTrackFormat(track) {
   const defaultTrack = {
     name: '',
     downloaderArg: ''
@@ -106,7 +102,7 @@ function updateTrackFormat(track) {
   return Object.assign(defaultTrack, trackObj)
 }
 
-function cloneGrouplike(grouplike) {
+export function cloneGrouplike(grouplike) {
   const newGrouplike = {
     name: grouplike.name,
     items: grouplike.items.map(item => {
@@ -128,7 +124,7 @@ function cloneGrouplike(grouplike) {
   return newGrouplike
 }
 
-function filterTracks(grouplike, handleTrack) {
+export function filterTracks(grouplike, handleTrack) {
   // Recursively filters every track in the passed grouplike. The track-handler
   // function passed should either return true (to keep a track) or false (to
   // remove the track). After tracks are filtered, groups which contain no
@@ -161,7 +157,7 @@ function filterTracks(grouplike, handleTrack) {
   })
 }
 
-function flattenGrouplike(grouplike) {
+export function flattenGrouplike(grouplike) {
   // Flattens a group-like, taking all of the non-group items (tracks) at all
   // levels in the group tree and returns them as a new group containing those
   // tracks.
@@ -177,7 +173,7 @@ function flattenGrouplike(grouplike) {
   }
 }
 
-function countTotalTracks(item) {
+export function countTotalTracks(item) {
   // Returns the total number of tracks in a grouplike, including tracks in any
   // descendant groups. Basically the same as flattenGrouplike().items.length.
 
@@ -191,7 +187,7 @@ function countTotalTracks(item) {
   }
 }
 
-function shuffleOrderOfGroups(grouplike) {
+export function shuffleOrderOfGroups(grouplike) {
   // OK, this is opinionated on how it should work, but I think it Makes Sense.
   // Also sorry functional-programming friends, I'm sure this is a horror.
   // (FYI, this is the same as how http-music used to work with shuffle-groups,
@@ -209,12 +205,12 @@ function shuffleOrderOfGroups(grouplike) {
   return {items: shuffleArray(items)}
 }
 
-function reverseOrderOfGroups(grouplike) {
+export function reverseOrderOfGroups(grouplike) {
   const { items } = collapseGrouplike(grouplike)
   return {items: items.reverse()}
 }
 
-function collectGrouplikeChildren(grouplike, filter = null) {
+export function collectGrouplikeChildren(grouplike, filter = null) {
   // Collects all descendants of a grouplike into a single flat array.
   // Can be passed a filter function, which will decide whether or not to add
   // an item to the return array. However, note that all descendants will be
@@ -237,7 +233,7 @@ function collectGrouplikeChildren(grouplike, filter = null) {
   return items
 }
 
-function partiallyFlattenGrouplike(grouplike, resultDepth) {
+export 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
@@ -258,7 +254,7 @@ function partiallyFlattenGrouplike(grouplike, resultDepth) {
   return {items}
 }
 
-function collapseGrouplike(grouplike) {
+export function collapseGrouplike(grouplike) {
   // Similar to partiallyFlattenGrouplike, but doesn't discard the individual
   // ordering of tracks; rather, it just collapses them all to one level.
 
@@ -284,7 +280,7 @@ function collapseGrouplike(grouplike) {
   return {items: ret}
 }
 
-function filterGrouplikeByProperty(grouplike, property, value) {
+export function filterGrouplikeByProperty(grouplike, property, value) {
   // Returns a copy of the original grouplike, only keeping tracks with the
   // given property-value pair. (If the track's value for the given property
   // is an array, this will check if that array includes the given value.)
@@ -314,13 +310,13 @@ function filterGrouplikeByProperty(grouplike, property, value) {
   })
 }
 
-function filterPlaylistByPathString(playlist, pathString) {
+export function filterPlaylistByPathString(playlist, pathString) {
   // Calls filterGroupContentsByPath, taking an unparsed path string.
 
   return filterGrouplikeByPath(playlist, parsePathString(pathString))
 }
 
-function filterGrouplikeByPath(grouplike, pathParts) {
+export function filterGrouplikeByPath(grouplike, pathParts) {
   // Finds a group by following the given group path and returns it. If the
   // function encounters an item in the group path that is not found, it logs
   // a warning message and returns the group found up to that point. If the
@@ -371,13 +367,13 @@ function filterGrouplikeByPath(grouplike, pathParts) {
   }
 }
 
-function removeGroupByPathString(playlist, pathString) {
+export function removeGroupByPathString(playlist, pathString) {
   // Calls removeGroupByPath, taking a path string, rather than a parsed path.
 
   return removeGroupByPath(playlist, parsePathString(pathString))
 }
 
-function removeGroupByPath(playlist, pathParts) {
+export function removeGroupByPath(playlist, pathParts) {
   // Removes the group at the given path from the given playlist.
 
   const groupToRemove = filterGrouplikeByPath(playlist, pathParts)
@@ -418,7 +414,7 @@ function removeGroupByPath(playlist, pathParts) {
   }
 }
 
-function getPlaylistTreeString(playlist, showTracks = false) {
+export function getPlaylistTreeString(playlist, showTracks = false) {
   function recursive(group) {
     const groups = group.items.filter(x => isGroup(x))
     const nonGroups = group.items.filter(x => !isGroup(x))
@@ -454,7 +450,7 @@ function getPlaylistTreeString(playlist, showTracks = false) {
   return recursive(playlist)
 }
 
-function getItemPath(item) {
+export function getItemPath(item) {
   if (item[parentSymbol]) {
     return [...getItemPath(item[parentSymbol]), item]
   } else {
@@ -462,7 +458,7 @@ function getItemPath(item) {
   }
 }
 
-function getItemPathString(item) {
+export function getItemPathString(item) {
   // Gets the playlist path of an item by following its parent chain.
   //
   // Returns a string in format Foo/Bar/Baz, where Foo and Bar are group
@@ -489,12 +485,12 @@ function getItemPathString(item) {
   }
 }
 
-function parsePathString(pathString) {
+export function parsePathString(pathString) {
   const pathParts = pathString.split('/').filter(item => item.length)
   return pathParts
 }
 
-function getTrackIndexInParent(track) {
+export function getTrackIndexInParent(track) {
   if (parentSymbol in track === false) {
     throw new Error(
       'getTrackIndexInParent called with a track that has no parent!'
@@ -505,6 +501,11 @@ function getTrackIndexInParent(track) {
 
   let i = 0, foundTrack = false;
   for (; i < parent.items.length; i++) {
+    // TODO: Port isSameTrack from http-music, if it makes sense - doing
+    // so involves porting the [oldSymbol] property on all tracks and groups,
+    // so may or may not be the right call. This function isn't used anywhere
+    // in mtui so it'll take a little extra investigation.
+    /* eslint-disable-next-line no-undef */
     if (isSameTrack(track, parent.items[i])) {
       foundTrack = true
       break
@@ -519,14 +520,14 @@ function getTrackIndexInParent(track) {
 }
 
 const nameWithoutTrackNumberSymbol = Symbol('Cached name without track number')
-function getNameWithoutTrackNumber(track) {
+export function getNameWithoutTrackNumber(track) {
   // A "part" is a series of numeric digits, separated from other parts by
   // whitespace, dashes, and dots, 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 }) => {
-    name = name.replace(/^[\-\s.]+$/, '')
-    const match = name.match(/[^0-9\-\s.]/)
+    name = name.replace(/^[-\s.]+$/, '')
+    const match = name.match(/[^0-9-\s.]/)
     if (match) {
       if (match.index === 0) {
         return 0
@@ -538,12 +539,12 @@ function getNameWithoutTrackNumber(track) {
     } else {
       return 0
     }
-    name = name.replace(/[\-\s.]+$/, '')
-    return name.split(/[\-\s.]+/g).length
+    name = name.replace(/[-\s.]+$/, '')
+    return name.split(/[-\s.]+/g).length
   }
 
   const removeParts = (name, numParts) => {
-    const regex = new RegExp(String.raw`[\-\s.]{0,}([0-9]+[\-\s.]+){${numParts},${numParts}}`)
+    const regex = new RegExp(String.raw`[-\s.]{0,}([0-9]+[-\s.]+){${numParts},${numParts}}`)
     return track.name.replace(regex, '')
   }
 
@@ -591,24 +592,24 @@ function getNameWithoutTrackNumber(track) {
   }
 }
 
-function isGroup(obj) {
+export function isGroup(obj) {
   return !!(obj && obj.items)
 }
 
-function isTrack(obj) {
+export function isTrack(obj) {
   return !!(obj && obj.downloaderArg)
 }
 
-function isPlayable(obj) {
+export function isPlayable(obj) {
   return isGroup(obj) || isTrack(obj)
 }
 
-function isOpenable(obj) {
+export function isOpenable(obj) {
   return !!(obj && obj.url)
 }
 
 
-function searchForItem(grouplike, value, preferredStartIndex = -1) {
+export function searchForItem(grouplike, value, preferredStartIndex = -1) {
   if (value.length) {
     // We prioritize searching past the index that the user opened the jump
     // element from (oldFocusedIndex). This is so that it's more practical
@@ -648,7 +649,7 @@ function searchForItem(grouplike, value, preferredStartIndex = -1) {
   return null
 }
 
-function getCorrespondingFileForItem(item, extension) {
+export function getCorrespondingFileForItem(item, extension) {
   if (!(item && item.url)) {
     return null
   }
@@ -673,7 +674,7 @@ function getCorrespondingFileForItem(item, extension) {
   return null
 }
 
-function getCorrespondingPlayableForFile(item) {
+export function getCorrespondingPlayableForFile(item) {
   if (!(item && item.url)) {
     return null
   }
@@ -691,53 +692,3 @@ function getCorrespondingPlayableForFile(item) {
   const basename = path.basename(item.url, path.extname(item.url))
   return parent.items.find(item => isPlayable(item) && path.basename(item.url, path.extname(item.url)) === basename)
 }
-
-module.exports = {
-  parentSymbol,
-  updatePlaylistFormat, updateGroupFormat, updateTrackFormat,
-  cloneGrouplike,
-  filterTracks,
-  flattenGrouplike, countTotalTracks,
-  shuffleOrderOfGroups,
-  reverseOrderOfGroups,
-  partiallyFlattenGrouplike, collapseGrouplike,
-  filterGrouplikeByProperty,
-  filterPlaylistByPathString, filterGrouplikeByPath,
-  removeGroupByPathString, removeGroupByPath,
-  getPlaylistTreeString,
-  getItemPath, getItemPathString,
-  parsePathString,
-  getTrackIndexInParent,
-  getNameWithoutTrackNumber,
-  searchForItem,
-  getCorrespondingFileForItem,
-  getCorrespondingPlayableForFile,
-  isGroup, isTrack,
-  isOpenable, isPlayable
-}
-
-if (require.main === module) {
-  {
-    const group = updateGroupFormat({items: [
-      {name: '- 1.01 Hello World 425', downloaderArg: 'x'},
-      {name: '1.02 Aww Yeah 371', downloaderArg: 'x'},
-      {name: ' 1.03 Here Goes 472', downloaderArg: 'x'}
-    ]})
-
-    for (let i = 0; i < group.items.length; i++) {
-      console.log(group.items[i].name, '->', getNameWithoutTrackNumber(group.items[i]))
-    }
-  }
-
-  {
-    const group = updateGroupFormat({items: [
-      {name: 'BAM #1', downloaderArg: 'x'},
-      {name: 'BAM #2', downloaderArg: 'x'},
-      {name: 'BAM #3.1 - no', downloaderArg: 'x'}
-    ]})
-
-    for (let i = 0; i < group.items.length; i++) {
-      console.log(group.items[i].name, '->', getNameWithoutTrackNumber(group.items[i]))
-    }
-  }
-}