« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/page/index.js33
-rw-r--r--src/page/news.js129
-rwxr-xr-xsrc/upd8.js232
3 files changed, 227 insertions, 167 deletions
diff --git a/src/page/index.js b/src/page/index.js
index d74dad57..384b4193 100644
--- a/src/page/index.js
+++ b/src/page/index.js
@@ -3,7 +3,14 @@
 // homepage.js for that.
 //
 // Each module published in this list should follow a particular format,
-// including the following exports:
+// including any of the following exports:
+//
+// condition({wikiData})
+//     Returns a boolean indicating whether to process targets/writes (true) or
+//     skip this page spec altogether (false). This is usually used for
+//     selectively toggling pages according to site feature flags, though it may
+//     also be used to e.g. skip out if no targets would be found (preventing
+//     writeTargetless from generating an empty index page).
 //
 // targets({wikiData})
 //     Gets the objects which this page's write() function should be called on.
@@ -11,14 +18,21 @@
 //     but it may also apply filter/map/etc if useful.
 //
 // write(thing, {wikiData})
-//     Gets descriptors for any page and data writes associated with the given
-//     thing (which will be a value from the targets() array). This includes
-//     page (HTML) writes, data (JSON) writes, etc. Notably, this function does
-//     not perform any file operations itself; it only describes the operations
-//     which will be processed elsewhere, once for each translation language.
-//     The write function also immediately transforms any data which will be
-//     reused across writes of the same page, so that this data is effectively
-//     cached (rather than recalculated for each language/write).
+//     Provides descriptors for any page and data writes associated with the
+//     given thing (which will be a value from the targets() array). This
+//     includes page (HTML) writes, data (JSON) writes, etc. Notably, this
+//     function does not perform any file operations itself; it only describes
+//     the operations which will be processed elsewhere, once for each
+//     translation language.  The write function also immediately transforms
+//     any data which will be reused across writes of the same page, so that
+//     this data is effectively cached (rather than recalculated for each
+//     language/write).
+//
+// writeTargetless({wikiData})
+//     Provides descriptors for page/data/etc writes which will be used
+//     without concern for targets. This is usually used for writing index pages
+//     which should be generated just once (rather than corresponding to
+//     targets).
 //
 // As these modules are effectively the HTML templates for all site layout,
 // common patterns may also be exported alongside the special exports above.
@@ -29,4 +43,5 @@ export * as album from './album.js';
 export * as artist from './artist.js';
 export * as artistAlias from './artist-alias.js';
 export * as group from './group.js';
+export * as news from './news.js';
 export * as track from './track.js';
diff --git a/src/page/news.js b/src/page/news.js
new file mode 100644
index 00000000..99cbe8d5
--- /dev/null
+++ b/src/page/news.js
@@ -0,0 +1,129 @@
+// News entry & index page specifications.
+
+// Imports
+
+import fixWS from 'fix-whitespace';
+
+// Page exports
+
+export function condition({wikiData}) {
+    return wikiData.wikiInfo.features.news;
+}
+
+export function targets({wikiData}) {
+    return wikiData.newsData;
+}
+
+export function write(entry, {wikiData}) {
+    const page = {
+        type: 'page',
+        path: ['newsEntry', entry.directory],
+        page: ({
+            generatePreviousNextLinks,
+            link,
+            strings,
+            transformMultiline,
+        }) => ({
+            title: strings('newsEntryPage.title', {entry: entry.name}),
+
+            main: {
+                content: fixWS`
+                    <div class="long-content">
+                        <h1>${strings('newsEntryPage.title', {entry: entry.name})}</h1>
+                        <p>${strings('newsEntryPage.published', {date: strings.count.date(entry.date)})}</p>
+                        ${transformMultiline(entry.body)}
+                    </div>
+                `
+            },
+
+            nav: generateNewsEntryNav(entry, {
+                generatePreviousNextLinks,
+                link,
+                strings,
+                wikiData
+            })
+        })
+    };
+
+    return [page];
+}
+
+export function writeTargetless({wikiData}) {
+    const { newsData } = wikiData;
+
+    const page = {
+        type: 'page',
+        path: ['newsIndex'],
+        page: ({
+            link,
+            strings,
+            transformMultiline
+        }) => ({
+            title: strings('newsIndex.title'),
+
+            main: {
+                content: fixWS`
+                    <div class="long-content news-index">
+                        <h1>${strings('newsIndex.title')}</h1>
+                        ${newsData.map(entry => fixWS`
+                            <article id="${entry.directory}">
+                                <h2><time>${strings.count.date(entry.date)}</time> ${link.newsEntry(entry)}</h2>
+                                ${transformMultiline(entry.bodyShort)}
+                                ${entry.bodyShort !== entry.body && `<p>${link.newsEntry(entry, {
+                                    text: strings('newsIndex.entry.viewRest')
+                                })}</p>`}
+                            </article>
+                        `).join('\n')}
+                    </div>
+                `
+            },
+
+            nav: {simple: true}
+        })
+    };
+
+    return [page];
+}
+
+// Utility functions
+
+function generateNewsEntryNav(entry, {
+    generatePreviousNextLinks,
+    link,
+    strings,
+    wikiData
+}) {
+    const { wikiInfo, newsData } = wikiData;
+
+    // The newsData list is sorted reverse chronologically (newest ones first),
+    // so the way we find next/previous entries is flipped from normal.
+    const previousNextLinks = generatePreviousNextLinks(entry, {
+        link, strings,
+        data: newsData.slice().reverse(),
+        linkKey: 'newsEntry'
+    });
+
+    return {
+        links: [
+            {
+                path: ['localized.home'],
+                title: wikiInfo.shortName
+            },
+            {
+                path: ['localized.newsIndex'],
+                title: strings('newsEntryPage.nav.news')
+            },
+            {
+                html: strings('newsEntryPage.nav.entry', {
+                    date: strings.count.date(entry.date),
+                    entry: link.newsEntry(entry, {class: 'current'})
+                })
+            },
+            previousNextLinks &&
+            {
+                divider: false,
+                html: `(${previousNextLinks})`
+            }
+        ]
+    };
+}
diff --git a/src/upd8.js b/src/upd8.js
index 2e164fac..b9fa0274 100755
--- a/src/upd8.js
+++ b/src/upd8.js
@@ -2429,120 +2429,6 @@ function writeMiscellaneousPages({wikiData}) {
     ];
 }
 
-function writeNewsPages({wikiData}) {
-    const { newsData, wikiInfo } = wikiData;
-
-    if (!wikiInfo.features.news) {
-        return;
-    }
-
-    return [
-        writeNewsIndex({wikiData}),
-        ...newsData.map(entry => writeNewsEntryPage(entry, {wikiData}))
-    ];
-}
-
-function writeNewsIndex({wikiData}) {
-    const { newsData } = wikiData;
-
-    const page = {
-        type: 'page',
-        path: ['newsIndex'],
-        page: ({
-            link,
-            strings,
-            transformMultiline
-        }) => ({
-            title: strings('newsIndex.title'),
-
-            main: {
-                content: fixWS`
-                    <div class="long-content news-index">
-                        <h1>${strings('newsIndex.title')}</h1>
-                        ${newsData.map(entry => fixWS`
-                            <article id="${entry.directory}">
-                                <h2><time>${strings.count.date(entry.date)}</time> ${link.newsEntry(entry)}</h2>
-                                ${transformMultiline(entry.bodyShort)}
-                                ${entry.bodyShort !== entry.body && `<p>${link.newsEntry(entry, {
-                                    text: strings('newsIndex.entry.viewRest')
-                                })}</p>`}
-                            </article>
-                        `).join('\n')}
-                    </div>
-                `
-            },
-
-            nav: {simple: true}
-        })
-    };
-
-    return [page];
-}
-
-function writeNewsEntryPage(entry, {wikiData}) {
-    const page = {
-        type: 'page',
-        path: ['newsEntry', entry.directory],
-        page: ({
-            link,
-            strings,
-            transformMultiline,
-        }) => ({
-            title: strings('newsEntryPage.title', {entry: entry.name}),
-
-            main: {
-                content: fixWS`
-                    <div class="long-content">
-                        <h1>${strings('newsEntryPage.title', {entry: entry.name})}</h1>
-                        <p>${strings('newsEntryPage.published', {date: strings.count.date(entry.date)})}</p>
-                        ${transformMultiline(entry.body)}
-                    </div>
-                `
-            },
-
-            nav: generateNewsEntryNav(entry, {link, strings, wikiData})
-        })
-    };
-
-    return [page];
-}
-
-function generateNewsEntryNav(entry, {link, strings, wikiData}) {
-    const { wikiInfo, newsData } = wikiData;
-
-    // The newsData list is sorted reverse chronologically (newest ones first),
-    // so the way we find next/previous entries is flipped from normal.
-    const previousNextLinks = generatePreviousNextLinks(entry, {
-        link, strings,
-        data: newsData.slice().reverse(),
-        linkKey: 'newsEntry'
-    });
-
-    return {
-        links: [
-            {
-                path: ['localized.home'],
-                title: wikiInfo.shortName
-            },
-            {
-                path: ['localized.newsIndex'],
-                title: strings('newsEntryPage.nav.news')
-            },
-            {
-                html: strings('newsEntryPage.nav.entry', {
-                    date: strings.count.date(entry.date),
-                    entry: link.newsEntry(entry, {class: 'current'})
-                })
-            },
-            previousNextLinks &&
-            {
-                divider: false,
-                html: `(${previousNextLinks})`
-            }
-        ]
-    };
-}
-
 function writeStaticPages({wikiData}) {
     return wikiData.staticPageData.map(staticPage => writeStaticPage(staticPage, {wikiData}));
 }
@@ -4913,72 +4799,102 @@ async function main() {
         : (Object.entries(buildDictionary)
             .filter(([ flag ]) => writeFlags[flag])));
 
-    // *NB: While what's 8elow is 8asically still true in principle, the
-    //      format is QUITE DIFFERENT than what's descri8ed here! There
-    //      will 8e actual document8tion on like, what the return format
-    //      looks like soon, once we implement a 8unch of other pages and
-    //      are certain what they actually, uh, will look like, in the end.*
-    //
-    // The writeThingPages functions don't actually immediately do any file
-    // writing themselves; an initial call will only gather the relevant data
-    // which is *then* used for writing. So the return value is a function
-    // (or an array of functions) which expects {strings}, and *that's* what
-    // we call after -- multiple times, once for each language.
     let writes;
     {
         let error = false;
 
-        const targets = buildSteps.flatMap(([ flag, pageSpec ]) => {
+        const buildStepsWithTargets = buildSteps.map(([ flag, pageSpec ]) => {
+            // Condition not met: skip this build step altogether.
+            if (pageSpec.condition && !pageSpec.condition({wikiData})) {
+                return null;
+            }
+
+            // May still call writeTargetless if present.
+            if (!pageSpec.targets) {
+                return {flag, pageSpec, targets: []};
+            }
+
+            if (!pageSpec.write) {
+                logError`${flag + '.targets'} is specified, but ${flag + '.write'} is missing!`;
+                error = true;
+                return null;
+            }
+
             const targets = pageSpec.targets({wikiData});
-            return targets.map(target => ({flag, pageSpec, target}));
-        });
+            return {flag, pageSpec, targets};
+        }).filter(Boolean);
 
-        const writeArrays = await progressPromiseAll(`Processing build data to be shared across langauges.`, queue(
-            targets.map(({ flag, pageSpec, target }) => () => {
-                const writes = pageSpec.write(target, {wikiData}) || [];
+        if (error) {
+            return;
+        }
 
-                // Do a quick valid8tion! If one of the writeThingPages functions go
-                // wrong, this will stall out early and tell us which did.
+        const validateWrites = (writes, fnName) => {
+            // Do a quick valid8tion! If one of the writeThingPages functions go
+            // wrong, this will stall out early and tell us which did.
 
-                if (!Array.isArray(writes)) {
-                    logError`${flag + '.write'} didn't return an array!`;
-                    error = true;
-                    return [];
-                }
+            if (!Array.isArray(writes)) {
+                logError`${fnName} didn't return an array!`;
+                error = true;
+                return false;
+            }
 
-                if (!(
-                    writes.every(obj => typeof obj === 'object') &&
-                    writes.every(obj => {
-                        const result = validateWriteObject(obj);
-                        if (result.error) {
-                            logError`Validating write object failed: ${result.error}`;
-                            return false;
-                        } else {
-                            return true;
-                        }
-                    })
-                )) {
-                    logError`${flag + '.write'} uses updated format, but entries are invalid!`;
-                    error = true;
+            if (!(
+                writes.every(obj => typeof obj === 'object') &&
+                writes.every(obj => {
+                    const result = validateWriteObject(obj);
+                    if (result.error) {
+                        logError`Validating write object failed: ${result.error}`;
+                        return false;
+                    } else {
+                        return true;
+                    }
+                })
+            )) {
+                logError`${fnName} returned invalid entries!`;
+                error = true;
+                return false;
+            }
+
+            return true;
+        };
+
+        writes = buildStepsWithTargets.flatMap(({ flag, pageSpec, targets }) => {
+            const writes = targets.flatMap(target =>
+                pageSpec.write(target, {wikiData}).slice() || []);
+
+            if (!validateWrites(writes, flag + '.write')) {
+                return [];
+            }
+
+            if (pageSpec.writeTargetless) {
+                const writes2 = pageSpec.writeTargetless({wikiData});
+
+                if (!validateWrites(writes2, flag + '.writeTargetless')) {
                     return [];
                 }
 
-                return writes;
-            }),
-            queueSize
-        ));
+                writes.push(...writes2);
+            }
+
+            return writes;
+        });
 
         if (error) {
             return;
         }
-
-        writes = writeArrays.flatMap(writes => writes);
     }
 
     const pageWrites = writes.filter(({ type }) => type === 'page');
     const dataWrites = writes.filter(({ type }) => type === 'data');
     const redirectWrites = writes.filter(({ type }) => type === 'redirect');
 
+    if (writes.length) {
+        logInfo`Total of ${writes.length} writes returned. (${pageWrites.length} page, ${dataWrites.length} data, ${redirectWrites.length} redirect)`;
+    } else {
+        logWarn`No writes returned at all, so exiting early. This is probably a bug!`;
+        return;
+    }
+
     await progressPromiseAll(`Writing data files shared across languages.`, queue(
         dataWrites.map(({path, data}) => () => {
             const bound = {};