« get me outta code hell

Progress - http-music - Command-line music player + utils (not a server!)
about summary refs log tree commit diff
diff options
context:
space:
mode:
authorLiam <towerofnix@gmail.com>2017-05-31 18:58:08 -0300
committerLiam <towerofnix@gmail.com>2017-05-31 18:58:08 -0300
commit26663377fd7ea15a6c3d23a399d1266c8639d42e (patch)
treefa0532caaf5672501bb5797499a515da72a09038
parent42ec01bb91c517067a9eba901272c1248ed52261 (diff)
Progress
-rw-r--r--crawl-links.js33
-rw-r--r--crawl-recursive.js118
-rw-r--r--src/pickers.js6
-rw-r--r--src/play.js7
-rw-r--r--src/playlist-utils.js28
-rw-r--r--src/process-argv.js18
-rw-r--r--src/promisify-process.js3
-rw-r--r--todo.txt4
8 files changed, 123 insertions, 94 deletions
diff --git a/crawl-links.js b/crawl-links.js
deleted file mode 100644
index 8602ce1..0000000
--- a/crawl-links.js
+++ /dev/null
@@ -1,33 +0,0 @@
-'use strict'
-
-const fetch = require('node-fetch')
-const $ = require('cheerio')
-const url = require('url')
-
-const DEFAULT_EXTENSIONS = [
-	'mp3', 'wav'
-]
-
-function getHTMLLinks(text) {
-	// Never parse HTML with a regex!
-
-	return $(text).find('a').get().map(a => {
-		const $a = $(a)
-		return [$a.text(), $a.attr('href')]
-	})
-}
-
-module.exports.getHTMLLinks = getHTMLLinks
-
-if (require.main === module) {
-	const urlString = process.argv[2]
-	const exts = process.argv.length > 3 ? process.argv.slice(3) : DEFAULT_EXTENSIONS
-
-	fetch(urlString)
-		.then(res => res.text())
-		.then(text => getHTMLLinks(text))
-		.then(links => links.filter(l => exts.some(e => l[1].endsWith('.' + e))))
-		.then(links => links.map(l => [l[0], url.resolve(urlString, l[1])]))
-		.then(links => console.log(JSON.stringify(links, null, 2)))
-		.catch(err => console.error(err))
-}
diff --git a/crawl-recursive.js b/crawl-recursive.js
index 8d33ded..d3b0127 100644
--- a/crawl-recursive.js
+++ b/crawl-recursive.js
@@ -3,70 +3,84 @@
 const MAX_DOWNLOAD_ATTEMPTS = 5
 
 const fetch = require('node-fetch')
-const { getHTMLLinks } = require('./crawl-links')
 
 function crawl(absURL, attempts = 0) {
-	return fetch(absURL)
-		.then(res => res.text().then(text => playlistifyParse(text, absURL)), err => {
-			console.error('Failed to download: ' + absURL)
+  // Recursively crawls a given URL, following every link to a deeper path and
+  // recording all links in a tree (in the same format playlists use). Makes
+  // multiple attempts to download failed paths.
 
-			if (attempts < MAX_DOWNLOAD_ATTEMPTS) {
-				console.error(
-					'Trying again. Attempt ' + (attempts + 1) +
-					'/' + MAX_DOWNLOAD_ATTEMPTS + '...'
-				)
-				return crawl(absURL, attempts + 1)
-			} else {
-				console.error(
-					'We\'ve hit the download attempt limit (' +
-					MAX_DOWNLOAD_ATTEMPTS + '). Giving up on ' +
-					'this path.'
-				)
-				throw 'FAILED_DOWNLOAD'
-			}
-		})
-		.catch(error => {
-			if (error === 'FAILED_DOWNLOAD') {
-				// Debug logging for this is already handled above.
-				return []
-			} else {
-				throw error
-			}
-		})
-}
+  return fetch(absURL)
+    .then(
+      res => res.text().then(text => {
+        const links = getHTMLLinks(text)
+        const verbose = process.argv.includes('--verbose')
+
+        return Promise.all(links.map(link => {
+          const [ title, href ] = link
+
+          if (href.endsWith('/')) {
+            // It's a directory!
 
-function playlistifyParse(text, absURL) {
-	const links = getHTMLLinks(text)
-	const verbose = process.argv.includes('--verbose')
+            if (verbose) console.log('[Dir] ' + absURL + href)
+            return crawl(absURL + href)
+              .then(res => [title, res])
+          } else {
+            // It's a file!
 
-	return Promise.all(links.map(link => {
-		const [ title, href ] = link
+            if (verbose) console.log('[File] ' + absURL + href)
+            return Promise.resolve([title, absURL + href])
+          }
+        }))
+      }),
 
-		if (href.endsWith('/')) {
-			// It's a directory!
+      err => {
+        console.error('Failed to download: ' + absURL)
+
+        if (attempts < MAX_DOWNLOAD_ATTEMPTS) {
+          console.error(
+            'Trying again. Attempt ' + (attempts + 1) +
+            '/' + MAX_DOWNLOAD_ATTEMPTS + '...'
+          )
+          return crawl(absURL, attempts + 1)
+        } else {
+          console.error(
+            'We\'ve hit the download attempt limit (' +
+            MAX_DOWNLOAD_ATTEMPTS + '). Giving up on ' +
+            'this path.'
+          )
+          throw 'FAILED_DOWNLOAD'
+        }
+      }
+    )
+    .catch(error => {
+      if (error === 'FAILED_DOWNLOAD') {
+        // Debug logging for this is already handled above.
+        return []
+      } else {
+        throw error
+      }
+    })
+}
 
-			if (verbose) console.log('[Dir] ' + absURL + href)
-			return crawl(absURL + href)
-				.then(res => [title, res])
-		} else {
-			// It's a file!
+function getHTMLLinks(text) {
+  // Never parse HTML with a regex!
 
-			if (verbose) console.log('[File] ' + absURL + href)
-			return Promise.resolve([title, absURL + href])
-		}
-	}))
+  return $(text).find('a').get().map(a => {
+    const $a = $(a)
+    return [$a.text(), $a.attr('href')]
+  })
 }
 
 if (process.argv.length === 2) {
-	console.log('Usage: crawl-recursive http://example.com/example/path')
+  console.log('Usage: crawl-recursive http://example.com/example/path')
 } else {
-	let url = process.argv[2]
+  let url = process.argv[2]
 
-	if (!(url.endsWith('/'))) {
-		url = url + '/'
-	}
+  if (!(url.endsWith('/'))) {
+    url = url + '/'
+  }
 
-	crawl(url)
-		.then(res => console.log(JSON.stringify(res, null, 2)))
-		.catch(err => console.error(err))
+  crawl(url)
+    .then(res => console.log(JSON.stringify(res, null, 2)))
+    .catch(err => console.error(err))
 }
diff --git a/src/pickers.js b/src/pickers.js
index 236f9ea..92a9641 100644
--- a/src/pickers.js
+++ b/src/pickers.js
@@ -3,6 +3,9 @@
 const { flattenPlaylist } = require('./playlist-utils')
 
 function makeOrderedPlaylistPicker(playlist) {
+  // Ordered playlist picker - this plays all the tracks in a playlist in
+  // order, after flattening it.
+
   const allSongs = flattenPlaylist(playlist)
   let index = 0
 
@@ -18,6 +21,9 @@ function makeOrderedPlaylistPicker(playlist) {
 }
 
 function makeShufflePlaylistPicker(playlist) {
+  // Shuffle playlist picker - this selects a random track at any index in
+  // the playlist, after flattening it.
+
   const allSongs = flattenPlaylist(playlist)
 
   return function() {
diff --git a/src/play.js b/src/play.js
index b0014f5..9b9a5cf 100644
--- a/src/play.js
+++ b/src/play.js
@@ -7,6 +7,10 @@ const loopPlay = require('./loop-play')
 const processArgv = require('./process-argv')
 const pickers = require('./pickers')
 
+const {
+  filterPlaylistByPathString, ignoreGroupByPathString, getPlaylistTreeString
+} = require('./playlist-utils')
+
 const readFile = promisify(fs.readFile)
 
 readFile('./playlist.json', 'utf-8')
@@ -29,7 +33,8 @@ readFile('./playlist.json', 'utf-8')
         // Opens a separate playlist file.
         // This sets the source playlist.
 
-        const openedPlaylist = JSON.parse(await readFile(util.nextArg(), 'utf-8'))
+        const playlistText = await readFile(util.nextArg(), 'utf-8')
+        const openedPlaylist = JSON.parse(playlistText)
         sourcePlaylist = openedPlaylist
         curPlaylist = openedPlaylist
       },
diff --git a/src/playlist-utils.js b/src/playlist-utils.js
index d853456..5266f1a 100644
--- a/src/playlist-utils.js
+++ b/src/playlist-utils.js
@@ -1,6 +1,10 @@
 'use strict'
 
 function flattenPlaylist(playlist) {
+  // Flattens a playlist, taking all of the non-group items (tracks) at all
+  // levels in the playlist tree and returns them as a single-level array of
+  // tracks.
+
   const groups = playlist.filter(x => Array.isArray(x[1]))
   const nonGroups = playlist.filter(x => x[1] && !(Array.isArray(x[1])))
   return groups.map(g => flattenPlaylist(g[1]))
@@ -8,12 +12,16 @@ function flattenPlaylist(playlist) {
 }
 
 function filterPlaylistByPathString(playlist, pathString) {
+  // Calls filterPlaylistByPath, taking a path string, rather than a parsed
+  // path.
+
   return filterPlaylistByPath(playlist, parsePathString(pathString))
 }
 
 function filterPlaylistByPath(playlist, pathParts) {
-  // Note this can be used as a utility function, rather than just as
-  // a function for use by the argv-handler!
+  // 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.
 
   let cur = pathParts[0]
 
@@ -33,13 +41,14 @@ function filterPlaylistByPath(playlist, pathParts) {
   }
 }
 
-function ignoreGroupByPathString(playlist, pathString) {
-  const pathParts = parsePathString(pathString)
-  return ignoreGroupByPath(playlist, pathParts)
+function removeGroupByPathString(playlist, pathString) {
+  // Calls removeGroupByPath, taking a path string, rather than a parsed path.
+
+  return removeGroupByPath(playlist, parsePathString(pathString))
 }
 
-function ignoreGroupByPath(playlist, pathParts) {
-  // TODO: Ideally this wouldn't mutate the given playlist.
+function removeGroupByPath(playlist, pathParts) {
+  // Removes the group at the given path from the given playlist.
 
   const groupToRemove = filterPlaylistByPath(playlist, pathParts)
 
@@ -80,7 +89,10 @@ function getPlaylistTreeString(playlist, showTracks = false) {
       }
     }).join('\n')
 
-    const tracksString = (showTracks ? nonGroups.map(g => g[0]).join('\n') : '')
+    let trackString = ''
+    if (showTracks) {
+      trackString = nonGroups.map(g => g[0]).join('\n')
+    }
 
     if (tracksString && childrenString) {
       return tracksString + '\n' + childrenString
diff --git a/src/process-argv.js b/src/process-argv.js
index 3193d98..d5f86f9 100644
--- a/src/process-argv.js
+++ b/src/process-argv.js
@@ -1,17 +1,35 @@
 'use strict'
 
 module.exports = async function processArgv(argv, handlers) {
+  // Basic command line argument list processor. Takes a list of arguments and
+  // an object, which is used as a mapping of option strings to behavior
+  // functions.
+
   let i = 0
 
   async function handleOpt(opt) {
+    // Handles a single option. May be recursive, depending on the user-defined
+    // handler given to processArgv. If there is no such handler for the given
+    // option, a warning message is displayed and the option is ignored.
+
     if (opt in handlers) {
       await handlers[opt]({
+        // Util object; stores useful information and methods that the handler
+        // can access.
+
         argv, index: i,
+
         nextArg: function() {
+          // Returns the next argument in the argument list, and increments
+          // the parse index by one.
+
           i++
           return argv[i]
         },
+
         alias: function(optionToRun) {
+          // Runs the given option's handler.
+
           handleOpt(optionToRun)
         }
       })
diff --git a/src/promisify-process.js b/src/promisify-process.js
index 877cb8d..ca49b31 100644
--- a/src/promisify-process.js
+++ b/src/promisify-process.js
@@ -1,6 +1,9 @@
 'use strict'
 
 module.exports = function promisifyProcess(proc, showLogging = true) {
+  // Takes a process (from child_process) and returns a promise that resolves
+  // when the process exits.
+
   return new Promise((resolve, reject) => {
     if (showLogging) {
       proc.stdout.pipe(process.stdout)
diff --git a/todo.txt b/todo.txt
index 324df83..cf984fc 100644
--- a/todo.txt
+++ b/todo.txt
@@ -63,3 +63,7 @@ TODO: Make crawl-itunes.js a bit more general, more command-line
 
 TODO: Play-in-order track picker.
       (Done!)
+
+TODO: Volume controls. Who knows how to do this? It might have to be an
+      argument passed to `play`. Being able to change the volume while it's
+      playing would be nice, but I'm not sure if that's really possible.