From 2b6e7d3d9950aad31536d63a835869502a70af46 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 25 Jan 2021 17:27:05 -0400 Subject: initial staging commit (data/media pruned) --- README.md | 7 +- common/common.js | 28 +- package.json | 21 +- static/site.css | 28 +- upd8-util.js | 8 + upd8.js | 1250 ++++++++++++++++++++++++++++++++++++------------------ 6 files changed, 894 insertions(+), 448 deletions(-) diff --git a/README.md b/README.md index 8d587e2..9bc0d28 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ HSMusic, short for the *Homestuck Music Wiki*, is a revitalization and reimagini * `upd8.js`: "Build" code for the site. Everything specific to generating the structure and HTML content of the website is conatined in this file. As expected, it's pretty massive. * `static`: Static code and supporting files. Everything here is wholly client-side and referenced by the generated HTML files. * `common`: Code which is depended upon by both client- and server-side code. For the most part, this is constants such as directory paths, though there are a few handy algorithms here too. -* `data`: The majority of data files belonging to the wiki are here. If you were to, say, create a fork of hsmusic for some other music archival project, you'd want to change the files here. Data files are all a custom text format designed to be easy to edit, process, and maintain; they should be self-descriptive. - * There are a few HTML files in here as well, for static content in pages like "about", "changelog", etc. -* `media`: Images and other static files referenced by generated and static content across the site. Many of the files here are cover art, and their names match the automatically generated "kebab case" identifiers for tracks and albums (or a manually overridden one). +* In the not quite so far past, we used to have `data` and `media` folders too. Today, for portability and convenience in project structure, those are saved in separate repositories, and you can pass hsmusic paths to them through the `--data` and `--media` options, or the `HSMUSIC_DATA` and `HSMUSIC_MEDIA` environment variables. + * Data directory: The majority of data files belonging to the wiki are here. If you were to, say, create a fork of hsmusic for some other music archival project, you'd want to change the files here. Data files are all a custom text format designed to be easy to edit, process, and maintain; they should be self-descriptive. + * Media directory: Images and other static files referenced by generated and static content across the site. Many of the files here are cover art, and their names match the automatically generated "kebab case" identifiers for tracks and albums (or a manually overridden one). +* Same for the output root: previously it was in a `site` folder; today, use `--out` or `HSMUSIC_OUT`! The code process for upd8.js was politely introduced by 2019!us back when we were beginning the site, and it's essentially the same structure followed today. In summary: diff --git a/common/common.js b/common/common.js index 4ee73a1..8b5b283 100644 --- a/common/common.js +++ b/common/common.js @@ -8,15 +8,17 @@ const C = { // of the whole dang site. Just keep in mind that the gener8ted result will // contain a couple symlinked directories, so if you're uploading, you're // pro8a8ly gonna want to resolve those yourself. - SITE_DIRECTORY: 'site', + // DEFAULT_OUTPUT_DIRECTORY: 'site', // Data files for the site, including flash, artist, and al8um data. // There are also some HTML files here, which are read and em8edded as // content in a few gener8ted pages (e.g. the changelog). - DATA_DIRECTORY: 'data', + // DEFAULT_DATA_DIRECTORY: 'data', - // Su8directory under data for al8um files. - DATA_ALBUM_DIRECTORY: 'album', + // Static media will 8e referenced in the site here! + // The contents are categorized; see MEDIA_DIRECTORY and 8elow. + // (This gets symlinked into SITE_DIRECTORY.) + // DEFAULT_MEDIA_DIRECTORY: 'media', // Code that's common 8etween the 8uild code (i.e. upd8.js) and gener8ted // site code should 8e put here. Which, uh, only really means this one @@ -29,9 +31,11 @@ const C = { // (This gets symlinked into SITE_DIRECTORY.) STATIC_DIRECTORY: 'static', - // Static media will 8e referenced in the site here! - // The contents are categorized 8y the constants 8elow. - // (This gets symlinked into SITE_DIRECTORY.) + // Su8directory under DATA_DIRECTORY for al8um files. + DATA_ALBUM_DIRECTORY: 'album', + + // Media files! This is symlinked from the provided media directory, which + // may be DEfAULT_MEDIA_DIRECTORY. MEDIA_DIRECTORY: 'media', // Contains a folder for each al8um, within which is the al8um cover art @@ -46,6 +50,12 @@ const C = { // a flash is just its page num8er most of the time.) MEDIA_FLASH_ART_DIRECTORY: 'flash-art', + // Again, a single folder, with one image for each artist, matching their + // output directory (which is usually their name in ke8a8-case). Although, + // unlike other art directories, you don't to specify an image for *every* + // artist - and present files will 8e automatically added! + MEDIA_ARTIST_AVATAR_DIRECTORY: 'artist-avatar', + // Miscellaneous stuff! This is pretty much only referenced in commentary // fields. MEDIA_MISC_DIRECOTRY: 'misc', @@ -74,9 +84,7 @@ const C = { ARTIST_AVATAR_DIRECTORY: 'artist-avatar', TAG_DIRECTORY: 'tag', LISTING_DIRECTORY: 'list', - ABOUT_DIRECTORY: 'about', FEEDBACK_DIRECTORY: 'feedback', - CHANGELOG_DIRECTORY: 'changelog', DISCORD_DIRECTORY: 'discord', DONATE_DIRECTORY: 'donate', FLASH_DIRECTORY: 'flash', @@ -131,7 +139,7 @@ const C = { getArtistNumContributions: artist => ( artist.tracks.asAny.length + artist.albums.asCoverArtist.length + - artist.flashes.asContributor.length + (artist.flashes ? artist.flashes.asContributor.length : 0) ), getArtistCommentary: (artist, {justEverythingMan}) => justEverythingMan.filter(thing => thing.commentary && thing.commentary.replace(/<\/?b>/g, '').includes('' + artist.name + ':')) diff --git a/package.json b/package.json index 88b3217..4ae318e 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { - "name": "hs-music-wiki", - "version": "0.0.1", - "description": "fan wiki for homestuck albums", - "main": "upd8.js", - "dependencies": { - "fix-whitespace": "^1.0.3", - "mkdirp": "^0.5.5" - }, - "license": "GPL-3.0" + "name": "hsmusic-wiki", + "version": "0.1.0", + "description": "static wiki software cataloguing collaborative creation", + "main": "upd8.js", + "bin": { + "hsmusic": "./upd8.js" + }, + "dependencies": { + "fix-whitespace": "^1.0.3", + "mkdirp": "^0.5.5" + }, + "license": "GPL-3.0" } diff --git a/static/site.css b/static/site.css index e76054a..7cb1b8b 100644 --- a/static/site.css +++ b/static/site.css @@ -31,7 +31,7 @@ body::before { height: 100%; z-index: -1; - background-image: url("bg.jpg"); + background-image: url("../media/bg.jpg"); background-position: center; background-size: cover; opacity: 0.5; @@ -80,12 +80,19 @@ a:hover { display: flex; } -#header, #skippers { - margin-bottom: 10px; +#header, #skippers, #footer { padding: 5px; font-size: 0.85em; } +#header, #skippers { + margin-bottom: 10px; +} + +#footer { + margin-top: 10px; +} + #header { display: flex; } @@ -128,6 +135,19 @@ a:hover { display: inline-block; } +footer { + text-align: center; + font-style: oblique; +} + +footer > :first-child { + margin-top: 0; +} + +footer > :last-child { + margin-bottom: 0; +} + .nowrap { white-space: nowrap; } @@ -170,7 +190,7 @@ a:hover { font-size: 1em; } -.sidebar, #content, #header, #skippers { +.sidebar, #content, #header, #skippers, #footer { background-color: rgba(var(--bg-shade), var(--bg-shade), var(--bg-shade), 0.6); border: 1px dotted var(--fg-color); border-radius: 3px; diff --git a/upd8-util.js b/upd8-util.js index 28504ea..a266efb 100644 --- a/upd8-util.js +++ b/upd8-util.js @@ -28,6 +28,8 @@ module.exports.splitArray = function*(array, fn) { // This function's name is a joke. Jokes! Hahahahahahahaha. Funny. module.exports.joinNoOxford = function(array, plural = 'and') { + array = array.filter(Boolean); + if (array.length === 0) { // ???????? return ''; @@ -45,6 +47,10 @@ module.exports.joinNoOxford = function(array, plural = 'and') { }; module.exports.progressPromiseAll = function (msg, array) { + if (!array.length) { + return Promise.resolve([]); + } + let done = 0, total = array.length; process.stdout.write(`\r${msg} [0/${total}]`); const start = Date.now(); @@ -296,3 +302,5 @@ module.exports.parseOptions = parseOptions; module.exports.curry = f => x => (...args) => f(x, ...args); module.exports.mapInPlace = (array, fn) => array.splice(0, array.length, ...array.map(fn)); + +module.exports.filterEmptyLines = string => string.split('\n').filter(line => line.trim()).join('\n'); diff --git a/upd8.js b/upd8.js index dac8a13..bcb9533 100644 --- a/upd8.js +++ b/upd8.js @@ -1,3 +1,5 @@ +#!/usr/bin/env node + // HEY N8RDS! // // This is one of the 8ACKEND FILES. It's not used anywhere on the actual site @@ -104,6 +106,7 @@ const { cacheOneArg, curry, decorateTime, + filterEmptyLines, joinNoOxford, mapInPlace, parseOptions, @@ -116,36 +119,16 @@ const { const C = require('./common/common'); -const CACHEBUST = 1; - -const SITE_CANONICAL_BASE = 'https://hsmusic.wiki/'; -const SITE_TITLE = 'Homestuck Music Wiki'; -const SITE_SHORT_TITLE = 'HSMusic'; -const SITE_DESCRIPTION = `Expansive resource for anyone interested in fan-made and official Homestuck music alike; an archive for all things related.`; - -const SITE_DONATE_LINK = 'https://liberapay.com/nebula'; - -function readDataFile(file) { - // fight me bro - return fs.readFileSync(path.join(C.DATA_DIRECTORY, file)).toString().trim(); -} - -const SITE_ABOUT = readDataFile('about.html'); -const SITE_CHANGELOG = readDataFile('changelog.html'); -const SITE_DISCORD = readDataFile('discord.html'); -const SITE_DONATE = readDataFile('donate.html'); -const SITE_FEEDBACK = readDataFile('feedback.html'); -const SITE_JS_DISABLED = readDataFile('js-disabled.html'); - -// Might ena8le this later... we'll see! Eventually. May8e. -const ENABLE_ARTIST_AVATARS = false; -const ARTIST_AVATAR_DIRECTORY = 'artist-avatar'; +const CACHEBUST = 2; +const WIKI_INFO_FILE = 'wiki-info.txt'; +const HOMEPAGE_INFO_FILE = 'homepage.txt'; const ARTIST_DATA_FILE = 'artists.txt'; const FLASH_DATA_FILE = 'flashes.txt'; const NEWS_DATA_FILE = 'news.txt'; const TAG_DATA_FILE = 'tags.txt'; const GROUP_DATA_FILE = 'groups.txt'; +const STATIC_PAGE_DATA_FILE = 'static-pages.txt'; const CSS_FILE = 'site.css'; @@ -155,12 +138,19 @@ const CSS_FILE = 'site.css'; // // Upd8: Okay yeah these aren't actually any different. Still cleaner than // passing around a data object containing all this, though. +let dataPath; +let mediaPath; +let outputPath; + +let wikiInfo; +let homepageInfo; let albumData; let trackData; let flashData; let newsData; let tagData; let groupData; +let staticPageData; let artistNames; let artistData; @@ -180,9 +170,9 @@ let queueSize; // only the track listing, not track data itself), and dealing with errors of // missing track files (or track files which are not linked to al8ums). All a // 8unch of stuff that's a pain to deal with for no apparent 8enefit. -async function findAlbumDataFiles() { - return (await readdir(path.join(C.DATA_DIRECTORY, C.DATA_ALBUM_DIRECTORY))) - .map(albumFile => path.join(C.DATA_DIRECTORY, C.DATA_ALBUM_DIRECTORY, albumFile)); +async function findAlbumDataFiles(albumDirectory) { + return (await readdir(path.join(albumDirectory))) + .map(albumFile => path.join(albumDirectory, albumFile)); } function* getSections(lines) { @@ -194,7 +184,23 @@ function* getSections(lines) { function getBasicField(lines, name) { const line = lines.find(line => line.startsWith(name + ':')); return line && line.slice(name.length + 1).trim(); -}; +} + +function getBooleanField(lines, name) { + // The ?? oper8tor (which is just, hilariously named, lol) can 8e used to + // specify a default! + const value = getBasicField(lines, name); + switch (value) { + case 'yes': + case 'true': + return true; + case 'no': + case 'false': + return false; + default: + return null; + } +} function getListField(lines, name) { let startIndex = lines.findIndex(line => line.startsWith(name + ':')); @@ -268,7 +274,7 @@ function getMultilineField(lines, name) { return null; } startIndex++; - let endIndex = lines.findIndex((line, index) => index >= startIndex && !line.startsWith(' ')); + let endIndex = lines.findIndex((line, index) => index >= startIndex && line.length && !line.startsWith(' ')); if (endIndex === -1) { endIndex = lines.length; } @@ -422,7 +428,7 @@ function parseAttributes(string) { function transformMultiline(text, treatAsDocument=false) { // Heck yes, HTML magics. - text = transformInline(text); + text = transformInline(text.trim()); if (treatAsDocument) { return text; @@ -430,26 +436,138 @@ function transformMultiline(text, treatAsDocument=false) { const outLines = []; - let inList = false; + const indentString = ' '.repeat(4); + + let levelIndents = []; + const openLevel = indent => { + // opening a sublist is a pain: to be semantically *and* visually + // correct, we have to append the
-

Art & Flash Contributors

+

Art${wikiInfo.features.flashesAndGames ? ` & Flash` : ''} Contributors

-

Art & Flash Contributors

+

Art${wikiInfo.features.flashesAndGames ? ` & Flash` : ''} Contributors

`], + wikiInfo.features.groupUI && [['groups', 'by-name'], `Groups - by Name`, groupData .filter(x => x.isGroup) .sort(sortByName) .map(group => fixWS`
  • ${group.name}
  • `)], + wikiInfo.features.groupUI && [['groups', 'by-category'], `Groups - by Category`, fixWS`
    ${groupData.filter(x => x.isCategory).map(category => fixWS` @@ -2488,6 +2790,7 @@ function writeListingPages() { `).join('\n')}
    `], + wikiInfo.features.groupUI && [['groups', 'by-albums'], `Groups - by Albums`, groupData .filter(x => x.isGroup) .map(group => ({group, albums: group.albums.length})) @@ -2495,6 +2798,7 @@ function writeListingPages() { .map(({ group, albums }) => fixWS`
  • ${group.name} (${s(albums, 'album')})
  • `)], + wikiInfo.features.groupUI && [['groups', 'by-tracks'], `Groups - by Tracks`, groupData .filter(x => x.isGroup) .map(group => ({group, tracks: group.albums.reduce((acc, album) => acc + album.tracks.length, 0)})) @@ -2502,6 +2806,7 @@ function writeListingPages() { .map(({ group, tracks }) => fixWS`
  • ${group.name} (${s(tracks, 'track')})
  • `)], + wikiInfo.features.groupUI && [['groups', 'by-duration'], `Groups - by Duration`, groupData .filter(x => x.isGroup) .map(group => ({group, duration: getTotalDuration(group.albums.flatMap(album => album.tracks))})) @@ -2509,6 +2814,7 @@ function writeListingPages() { .map(({ group, duration }) => fixWS`
  • ${group.name} (${getDurationString(duration)})
  • `)], + wikiInfo.features.groupUI && [['groups', 'by-latest'], `Groups - by Latest Album`, C.sortByDate(groupData .filter(x => x.isGroup) .map(group => ({group, date: group.albums[group.albums.length - 1].date})) @@ -2580,9 +2886,11 @@ function writeListingPages() { (${s(track.referencedBy.length, 'time')} referenced) `)], + wikiInfo.features.flashesAndGames && [['tracks', 'in-flashes', 'by-album'], `Tracks - in Flashes & Games (by Album)`, albumChunkedList( C.sortByDate(trackData.slice()).filter(track => track.album.directory !== C.UNRELEASED_TRACKS_DIRECTORY && track.flashes.length > 0), track => `
  • ${track.name}
  • `)], + wikiInfo.features.flashesAndGames && [['tracks', 'in-flashes', 'by-flash'], `Tracks - in Flashes & Games (by Flash)`, fixWS`
    ${C.sortByDate(flashData.filter(flash => !flash.act8r8k)) @@ -2606,15 +2914,17 @@ function writeListingPages() { track => fixWS`
  • ${track.name}
  • `)], + wikiInfo.features.artTagUI && [['tags', 'by-name'], 'Tags - by Name', tagData.slice().sort(sortByName) .filter(tag => !tag.isCW) .map(tag => `
  • ${tag.name}
  • `)], + wikiInfo.features.artTagUI && [['tags', 'by-uses'], 'Tags - by Uses', tagData.slice().sort(sortByName) .filter(tag => !tag.isCW) .map(tag => ({tag, timesUsed: tag.things.length})) .sort((a, b) => b.timesUsed - a.timesUsed) .map(({ tag, timesUsed }) => `
  • ${tag.name} (${s(timesUsed, 'time')})
  • `)] - ]; + ].filter(Boolean); const getWordCount = str => { const wordCount = str.split(' ').length; @@ -2631,20 +2941,20 @@ function writeListingPages() { main: { content: fixWS`

    Listings

    -

    ${SITE_TITLE}: ${releasedTracks.length} tracks across ${releasedAlbums.length} albums, totaling ~${getDurationString(getTotalDuration(releasedTracks))} ${getTotalDuration(releasedTracks) > 3600 ? 'hours' : 'minutes'}.

    +

    ${wikiInfo.name}: ${releasedTracks.length} tracks across ${releasedAlbums.length} albums, totaling ~${getDurationString(getTotalDuration(releasedTracks))} ${getTotalDuration(releasedTracks) > 3600 ? 'hours' : 'minutes'}.


    Feel free to explore any of the listings linked below and in the sidebar!

    ${generateLinkIndexForListings(listingDescriptors)} ` }, - sidebar: { + sidebarLeft: { content: generateSidebarForListings(listingDescriptors) }, nav: { links: [ - ['./', SITE_SHORT_TITLE], + ['./', wikiInfo.shortName], [`${C.LISTINGS_DIRECTORY}/`, 'Listings'] ] } @@ -2684,26 +2994,26 @@ function writeListingPages() {
    ${transformMultiline(album.commentary)}
    - ` || ``} + `} ${tracks.filter(t => t.commentary).map(track => fixWS`

    ${track.name}

    ${transformMultiline(track.commentary)}
    - `).join('\n') || ``} + `).join('\n')} `) .join('\n') } ` }, - sidebar: { + sidebarLeft: { content: generateSidebarForListings(listingDescriptors, 'all-commentary') }, nav: { links: [ - ['./', SITE_SHORT_TITLE], + ['./', wikiInfo.shortName], [`${C.LISTING_DIRECTORY}/`, 'Listings'], [`${C.LISTING_DIRECTORY}/all-commentary`, 'All Commentary'] ] @@ -2742,13 +3052,13 @@ function writeListingPages() { ` }, - sidebar: { + sidebarLeft: { content: generateSidebarForListings(listingDescriptors, 'all-commentary') }, nav: { links: [ - ['./', SITE_SHORT_TITLE], + ['./', wikiInfo.shortName], [`${C.LISTING_DIRECTORY}/`, 'Listings'], [`${C.LISTING_DIRECTORY}/random`, 'Random Pages'] ] @@ -2774,13 +3084,13 @@ function writeListingPage(directoryParts, title, items, listingDescriptors) { ` }, - sidebar: { + sidebarLeft: { content: generateSidebarForListings(listingDescriptors, directoryParts) }, nav: { links: [ - ['./', SITE_SHORT_TITLE], + ['./', wikiInfo.shortName], [`${C.LISTING_DIRECTORY}/`, 'Listings'], [`${C.LISTING_DIRECTORY}/${directoryParts.join('/')}/`, title] ] @@ -2814,6 +3124,10 @@ function generateLinkIndexForListings(listingDescriptors, currentDirectoryParts) } function writeTagPages() { + if (!wikiInfo.features.artTagUI) { + return; + } + return progressPromiseAll(`Writing tag pages.`, queue(tagData .filter(tag => !tag.isCW) .map(curry(writeTagPage)), queueSize)); @@ -2850,8 +3164,8 @@ function writeTagPage(tag) { nav: { links: [ - ['./', SITE_SHORT_TITLE], - [`${C.LISTING_DIRECTORY}/`, 'Listings'], + ['./', wikiInfo.shortName], + wikiInfo.features.listings && [`${C.LISTING_DIRECTORY}/`, 'Listings'], [null, 'Tag:'], [`${C.TAG_DIRECTORY}/${tag.directory}/`, tag.name] ] @@ -2962,7 +3276,7 @@ function getLinkedArtist(ref) { function getLinkedFlash(ref) { if (!ref) return null; ref = ref.replace('flash:', ''); - return flashData.find(flash => flash.directory === ref); + return flashData?.find(flash => flash.directory === ref); } function getLinkedTag(ref) { @@ -3143,7 +3457,7 @@ function chronologyLinks(currentTrack, { return fixWS`
    ${heading} - ${parts.length && `(${parts.join(', ')})` || ``} + ${parts.length && `(${parts.join(', ')})`}
    `; }).filter(Boolean).join('\n'); @@ -3231,6 +3545,10 @@ function generateSidebarForAlbum(album, currentTrack = null) { } function generateSidebarRightForAlbum(album, currentTrack = null) { + if (!wikiInfo.features.groupUI) { + return null; + } + const { groups } = album; if (groups.length) { return { @@ -3254,25 +3572,31 @@ function generateSidebarRightForAlbum(album, currentTrack = null) { } function generateSidebarForGroup(isGallery = false, currentGroup = null) { - return ` -

    Groups

    -
    - ${groupData.filter(x => x.isCategory).map(category => [ - fixWS` -
    - ${category.name} -
    -
      - ${category.groups.map(group => fixWS` -
    • - ${group.name} -
    • - `).join('\n')} -
    - ` - ]).join('\n')} -
    - `; + if (!wikiInfo.features.groupUI) { + return null; + } + + return { + content: fixWS` +

    Groups

    +
    + ${groupData.filter(x => x.isCategory).map(category => [ + fixWS` +
    + ${category.name} +
    +
      + ${category.groups.map(group => fixWS` +
    • + ${group.name} +
    • + `).join('\n')} +
    + ` + ]).join('\n')} +
    + ` + }; } function writeGroupPages() { @@ -3321,13 +3645,11 @@ async function writeGroupPage(group) { ` }, - sidebar: { - content: generateSidebarForGroup(false, group) - }, - nav: { + sidebarLeft: generateSidebarForGroup(false, group), + nav: (wikiInfo.features.groupUI ? { links: [ - ['./', SITE_SHORT_TITLE], - [`${C.LISTING_DIRECTORY}/`, 'Listings'], + ['./', wikiInfo.shortName], + wikiInfo.features.listings && [`${C.LISTING_DIRECTORY}/`, 'Listings'], [null, 'Group:'], [`${C.GROUP_DIRECTORY}/${group.directory}/`, group.name], [null, `(${[ @@ -3335,7 +3657,7 @@ async function writeGroupPage(group) { `Gallery` ].join(', ') + (npInfo.length ? '; ' + npInfo : '')})`] ] - } + } : {simple: true}) }); await writePage([C.GROUP_DIRECTORY, group.directory, 'gallery'], { @@ -3348,7 +3670,7 @@ async function writeGroupPage(group) { content: fixWS`

    ${group.name} - Gallery

    ${releasedTracks.length} track${releasedTracks.length === 1 ? '' : 's'} across ${releasedAlbums.length} album${releasedAlbums.length === 1 ? '' : 's'}, totaling ~${getDurationString(totalDuration)} ${totalDuration > 3600 ? 'hours' : 'minutes'}.

    -

    (Choose another group to filter by!)

    + ${wikiInfo.features.groupUI && wikiInfo.features.listings && `

    (Choose another group to filter by!)

    `}
    ${getGridHTML({ entries: C.sortByDate(group.albums.map(item => ({item}))).reverse(), @@ -3359,13 +3681,11 @@ async function writeGroupPage(group) {
    ` }, - sidebar: { - content: generateSidebarForGroup(true, group) - }, - nav: { + sidebarLeft: generateSidebarForGroup(true, group), + nav: (wikiInfo.features.groupUI ? { links: [ - ['./', SITE_SHORT_TITLE], - [`${C.LISTING_DIRECTORY}/`, 'Listings'], + ['./', wikiInfo.shortName], + wikiInfo.features.listings && [`${C.LISTING_DIRECTORY}/`, 'Listings'], [null, 'Group:'], [`${C.GROUP_DIRECTORY}/${group.directory}/`, group.name], [null, `(${[ @@ -3373,7 +3693,7 @@ async function writeGroupPage(group) { `Gallery` ].join(', ') + (npGallery.length ? '; ' + npGallery : '')})`] ] - } + } : {simple: true}) }); } @@ -3381,10 +3701,9 @@ function getHrefOfAnythingMan(anythingMan) { return ( albumData.includes(anythingMan) ? C.ALBUM_DIRECTORY : trackData.includes(anythingMan) ? C.TRACK_DIRECTORY : - flashData.includes(anythingMan) ? C.FLASH_DIRECTORY : + flashData?.includes(anythingMan) ? C.FLASH_DIRECTORY : 'idk-bud' ) + '/' + ( - flashData.includes(anythingMan) ? getFlashDirectory(anythingMan) : anythingMan.directory ) + '/'; } @@ -3433,7 +3752,7 @@ function rebaseURLs(directory, html) { // no error: it's a full url } catch (error) { // caught an error: it's a component! - url = path.relative(directory, path.join(C.SITE_DIRECTORY, url)); + url = path.relative(directory, path.join(outputPath, url)); } return `${attr}="${url}"`; }); @@ -3446,6 +3765,75 @@ function classes(...args) { } async function main() { + const miscOptions = await parseOptions(process.argv.slice(2), { + 'data': { + type: 'value' + }, + + 'media': { + type: 'value' + }, + + 'out': { + type: 'value' + }, + + 'queue-size': { + type: 'value', + validate(size) { + if (parseInt(size) !== parseFloat(size)) return 'an integer'; + if (parseInt(size) < 0) return 'a counting number or zero'; + return true; + } + }, + queue: {alias: 'queue-size'}, + + [parseOptions.handleUnknown]: () => {} + }); + + dataPath = miscOptions.data || process.env.HSMUSIC_DATA; + mediaPath = miscOptions.media || process.env.HSMUSIC_MEDIA; + outputPath = miscOptions.out || process.env.HSMUSIC_OUT; + + { + let errored = false; + const error = (cond, msg) => { + if (cond) { + console.error(`\x1b[31;1m${msg}\x1b[0m`); + errored = true; + } + }; + error(!dataPath, `Expected --data option or HSMUSIC_DATA to be set`); + error(!mediaPath, `Expected --media option or HSMUSIC_MEDIA to be set`); + error(!outputPath, `Expected --out option or HSMUSIC_OUT to be set`); + if (errored) { + return; + } + } + + wikiInfo = await processWikiInfoFile(path.join(dataPath, WIKI_INFO_FILE)); + if (wikiInfo.error) { + console.log(`\x1b[31;1m${wikiInfo.error}\x1b[0m`); + return; + } + + homepageInfo = await processHomepageInfoFile(path.join(dataPath, HOMEPAGE_INFO_FILE)); + + if (homepageInfo.error) { + console.log(`\x1b[31;1m${homepageInfo.error}\x1b[0m`); + return; + } + + { + const errors = homepageInfo.rows.filter(obj => obj.error); + if (errors.length) { + for (const error of errors) { + console.log(`\x1b[31;1m${error.error}\x1b[0m`); + } + return; + } + } + // 8ut wait, you might say, how do we know which al8um these data files // correspond to???????? You wouldn't dare suggest we parse the actual // paths returned 8y this function, which ought to 8e of effectively @@ -3470,7 +3858,7 @@ async function main() { // avoiding that in our code 8ecause, again, we want to avoid assuming the // format of the returned paths here - they're only meant to 8e used for // reading as-is. - const albumDataFiles = await findAlbumDataFiles(); + const albumDataFiles = await findAlbumDataFiles(path.join(dataPath, C.DATA_ALBUM_DIRECTORY)); // Technically, we could do the data file reading and output writing at the // same time, 8ut that kinda makes the code messy, so I'm not 8othering @@ -3489,7 +3877,7 @@ async function main() { C.sortByDate(albumData); - artistData = await processArtistDataFile(path.join(C.DATA_DIRECTORY, ARTIST_DATA_FILE)); + artistData = await processArtistDataFile(path.join(dataPath, ARTIST_DATA_FILE)); if (artistData.error) { console.log(`\x1b[31;1m${artistData.error}\x1b[0m`); return; @@ -3507,18 +3895,20 @@ async function main() { trackData = C.getAllTracks(albumData); - flashData = await processFlashDataFile(path.join(C.DATA_DIRECTORY, FLASH_DATA_FILE)); - if (flashData.error) { - console.log(`\x1b[31;1m${flashData.error}\x1b[0m`); - return; - } + if (wikiInfo.features.flashesAndGames) { + flashData = await processFlashDataFile(path.join(dataPath, FLASH_DATA_FILE)); + if (flashData.error) { + console.log(`\x1b[31;1m${flashData.error}\x1b[0m`); + return; + } - const flashErrors = flashData.filter(obj => obj.error); - if (flashErrors.length) { - for (const error of flashErrors) { - console.log(`\x1b[31;1m${error.error}\x1b[0m`); + const errors = artistData.filter(obj => obj.error); + if (errors.length) { + for (const error of errors) { + console.log(`\x1b[31;1m${error.error}\x1b[0m`); + } + return; } - return; } artistNames = Array.from(new Set([ @@ -3533,13 +3923,13 @@ async function main() { ...track.contributors || [] ]) ]), - ...flashData.flatMap(flash => [ + ...(flashData?.flatMap(flash => [ ...flash.contributors || [] - ]) + ]) || []) ].map(contribution => contribution.who) ])); - tagData = await processTagDataFile(path.join(C.DATA_DIRECTORY, TAG_DATA_FILE)); + tagData = await processTagDataFile(path.join(dataPath, TAG_DATA_FILE)); if (tagData.error) { console.log(`\x1b[31;1m${tagData.error}\x1b[0m`); return; @@ -3555,7 +3945,7 @@ async function main() { } } - groupData = await processGroupDataFile(path.join(C.DATA_DIRECTORY, GROUP_DATA_FILE)); + groupData = await processGroupDataFile(path.join(dataPath, GROUP_DATA_FILE)); if (groupData.error) { console.log(`\x1b[31;1m${groupData.error}\x1b[0m`); return; @@ -3571,18 +3961,36 @@ async function main() { } } - newsData = await processNewsDataFile(path.join(C.DATA_DIRECTORY, NEWS_DATA_FILE)); - if (newsData.error) { - console.log(`\x1b[31;1m${newsData.error}\x1b[0m`); + staticPageData = await processStaticPageDataFile(path.join(dataPath, STATIC_PAGE_DATA_FILE)); + if (staticPageData.error) { + console.log(`\x1b[31;1m${staticPageData.error}\x1b[0m`); return; } - const newsErrors = newsData.filter(obj => obj.error); - if (newsErrors.length) { - for (const error of newsErrors) { - console.log(`\x1b[31;1m${error.error}\x1b[0m`); + { + const errors = staticPageData.filter(obj => obj.error); + if (errors.length) { + for (const error of errors) { + console.log(`\x1b[31;1m${error.error}\x1b[0m`); + } + return; + } + } + + if (wikiInfo.features.news) { + newsData = await processNewsDataFile(path.join(dataPath, NEWS_DATA_FILE)); + if (newsData.error) { + console.log(`\x1b[31;1m${newsData.error}\x1b[0m`); + return; + } + + const errors = newsData.filter(obj => obj.error); + if (errors.length) { + for (const error of errors) { + console.log(`\x1b[31;1m${error.error}\x1b[0m`); + } + return; } - return; } { @@ -3605,7 +4013,7 @@ async function main() { artistNames.sort((a, b) => a.toLowerCase() < b.toLowerCase() ? -1 : a.toLowerCase() > b.toLowerCase() ? 1 : 0); - justEverythingMan = C.sortByDate(albumData.concat(trackData, flashData.filter(flash => !flash.act8r8k))); + justEverythingMan = C.sortByDate(albumData.concat(trackData, flashData?.filter(flash => !flash.act8r8k) || [])); justEverythingSortedByArtDateMan = C.sortByArtDate(justEverythingMan.slice()); // console.log(JSON.stringify(justEverythingSortedByArtDateMan.map(getHrefOfAnythingMan), null, 2)); @@ -3719,7 +4127,7 @@ async function main() { contributionData = Array.from(new Set([ ...trackData.flatMap(track => [...track.artists || [], ...track.contributors || [], ...track.coverArtists || []]), ...albumData.flatMap(album => [...album.coverArtists || [], ...album.artists || []]), - ...flashData.flatMap(flash => [...flash.contributors || []]) + ...(flashData?.flatMap(flash => [...flash.contributors || []]) || []) ])); // Now that we have all the data, resolve references all 8efore actually @@ -3742,23 +4150,18 @@ async function main() { } }; - const actlessFlashData = flashData.filter(flash => !flash.act8r8k); - trackData.forEach(track => mapInPlace(track.references, getLinkedTrack)); trackData.forEach(track => track.aka = getLinkedTrack(track.aka)); trackData.forEach(track => mapInPlace(track.artTags, getLinkedTag)); albumData.forEach(album => mapInPlace(album.groups, getLinkedGroup)); albumData.forEach(album => mapInPlace(album.artTags, getLinkedTag)); artistData.forEach(artist => artist.alias = getLinkedArtist(artist.alias)); - actlessFlashData.forEach(flash => mapInPlace(flash.tracks, getLinkedTrack)); contributionData.forEach(contrib => contrib.who = getLinkedArtist(contrib.who)); filterNull(trackData, 'references'); filterNull(albumData, 'groups'); - filterNull(actlessFlashData, 'tracks'); trackData.forEach(track1 => track1.referencedBy = trackData.filter(track2 => track2.references.includes(track1))); - trackData.forEach(track => track.flashes = actlessFlashData.filter(flash => flash.tracks.includes(track))); groupData.forEach(group => group.albums = albumData.filter(album => album.groups.includes(group))); tagData.forEach(tag => tag.things = C.sortByArtDate([...albumData, ...trackData]).filter(thing => thing.artTags.includes(tag))); @@ -3767,10 +4170,22 @@ async function main() { ...trackData.filter(({ aka }) => aka === track) ].filter(Boolean)); + if (wikiInfo.features.flashesAndGames) { + const actlessFlashData = flashData.filter(flash => !flash.act8r8k); + + actlessFlashData.forEach(flash => mapInPlace(flash.tracks, getLinkedTrack)); + + filterNull(actlessFlashData, 'tracks'); + + trackData.forEach(track => track.flashes = actlessFlashData.filter(flash => flash.tracks.includes(track))); + } + artistData.forEach(artist => { const filterProp = (array, prop) => array.filter(thing => thing[prop]?.some(({ who }) => who === artist)); + const filterCommentary = array => array.filter(thing => thing.commentary && thing.commentary.replace(/<\/?b>/g, '').includes('' + artist.name + ':')); artist.tracks = { asArtist: filterProp(trackData, 'artists'), + asCommentator: filterCommentary(trackData), asContributor: filterProp(trackData, 'contributors'), asCoverArtist: filterProp(trackData, 'coverArtists'), asAny: trackData.filter(track => ( @@ -3779,11 +4194,14 @@ async function main() { }; artist.albums = { asArtist: filterProp(albumData, 'artists'), + asCommentator: filterCommentary(albumData), asCoverArtist: filterProp(albumData, 'coverArtists') }; - artist.flashes = { - asContributor: filterProp(flashData, 'contributors') - }; + if (wikiInfo.features.flashesAndGames) { + artist.flashes = { + asContributor: filterProp(flashData, 'contributors') + }; + } }); groupData.filter(x => x.isGroup).forEach(group => group.category = groupData.find(x => x.isCategory && x.name === group.category)); @@ -3792,20 +4210,6 @@ async function main() { officialAlbumData = albumData.filter(album => album.groups.some(group => group.directory === C.OFFICIAL_GROUP_DIRECTORY)); fandomAlbumData = albumData.filter(album => album.groups.every(group => group.directory !== C.OFFICIAL_GROUP_DIRECTORY)); - const miscOptions = await parseOptions(process.argv.slice(2), { - 'queue-size': { - type: 'value', - validate(size) { - if (parseInt(size) !== parseFloat(size)) return 'an integer'; - if (parseInt(size) < 0) return 'a counting number or zero'; - return true; - } - }, - queue: {alias: 'queue-size'}, - - [parseOptions.handleUnknown]: () => {} - }); - // Makes writing a little nicer on CPU theoretically, 8ut also costs in // performance right now 'cuz it'll w8 for file writes to 8e completed // 8efore moving on to more data processing. So, defaults to zero, which @@ -3826,6 +4230,7 @@ async function main() { group: {type: 'flag'}, list: {type: 'flag'}, misc: {type: 'flag'}, + static: {type: 'flag'}, tag: {type: 'flag'}, track: {type: 'flag'}, @@ -3836,13 +4241,14 @@ async function main() { await writeSymlinks(); if (buildAll || buildFlags.misc) await writeMiscellaneousPages(); + if (buildAll || buildFlags.static) await writeStaticPages(); if (buildAll || buildFlags.list) await writeListingPages(); if (buildAll || buildFlags.tag) await writeTagPages(); if (buildAll || buildFlags.group) await writeGroupPages(); if (buildAll || buildFlags.album) await writeAlbumPages(); if (buildAll || buildFlags.track) await writeTrackPages(); if (buildAll || buildFlags.artist) await writeArtistPages(); - if (buildAll || buildFlags.flash) await writeFlashPages(); + if (buildAll || buildFlags.flash) if (wikiInfo.features.flashesAndGames) await writeFlashPages(); decorateTime.displayTime(); -- cgit 1.3.0-6-gf8a5