« get me outta code hell

write: implement repl as proper build mode - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2024-01-09 11:32:43 -0400
committer(quasar) nebula <qznebula@protonmail.com>2024-01-09 11:32:43 -0400
commit3a205843397b6b4ef86d3e1ab07d93251c90e81f (patch)
tree26af056134294a748d2d08af106eb10e1487dc70
parentd3930822b87e01ae85bbc7e1f3f2598d8f680187 (diff)
write: implement repl as proper build mode
-rw-r--r--README.md2
-rw-r--r--package.json2
-rw-r--r--src/repl.js176
-rwxr-xr-xsrc/upd8.js24
-rw-r--r--src/write/build-modes/index.js1
-rw-r--r--src/write/build-modes/repl.js163
6 files changed, 166 insertions, 202 deletions
diff --git a/README.md b/README.md
index 2c99e3d..0e4ed90 100644
--- a/README.md
+++ b/README.md
@@ -149,11 +149,11 @@ The source code for HSMusic is divided across a number of source files, loosely
   - `write/`: Common utilities and output methods for controlling what hsmusic does to turn data and media into something you actually visit; these each define a variety of command-line arguments and are basically the interchangeable  "second half" of upd8.js
     - `live-dev-server.js`: Gets the site available for viewing as quickly as possible, generating and serving pages as they are requested from a local HTTP server; reacts live to code changes in the `content` directory
     - `static-build.js`: Builds the entire site at once, writing all the output to one self-contained folder which can be uploaded to a static file server
+    - `repl.js`: Provides a convenient REPL to run filters and transformations on data objects right from the Node.js command line
   - `static/`: Purely client-side files are kept here, e.g. site CSS, icon SVGs, and client-side JS
   - `util/`: Common utilities which generally may be accessed from both Node.js or the client (web browser)
   - `upd8.js`: Main entry point which controls and directs site generation from start to finish
   - `gen-thumbs.js`: Standalone utility also called every time HSMusic is run (unless `--skip-thumbs` is provided) which keeps a persistent cache of media MD5s and (re)generates thumbnails for new or updated image files
-  - `repl.js`: Standalone utility for loading all wiki data and providing a convenient REPL to run filters and transformations on data objects right from the Node.js command line
   - `listing-spec.js`: Descriptors for computations and HTML templates used for the Listings part of the site
   - `url-spec.js`: Index of output paths where generated HTML ends up; also controls where `<a>`, `<img>`, etc tags link
   - `file-size-preloader.js`: Simple utility for calculating size of files in media directory
diff --git a/package.json b/package.json
index a1c165b..0374e9c 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
         "#language": "./src/data/language.js",
         "#page-specs": "./src/page/index.js",
         "#node-utils": "./src/util/node-utils.js",
-        "#repl": "./src/repl.js",
+        "#repl": "./src/write/build-modes/repl.js",
         "#replacer": "./src/util/replacer.js",
         "#serialize": "./src/data/serialize.js",
         "#sugar": "./src/util/sugar.js",
diff --git a/src/repl.js b/src/repl.js
deleted file mode 100644
index d7f4286..0000000
--- a/src/repl.js
+++ /dev/null
@@ -1,176 +0,0 @@
-import * as os from 'node:os';
-import * as path from 'node:path';
-import * as repl from 'node:repl';
-import {fileURLToPath} from 'node:url';
-
-import {logError, logWarn, parseOptions} from '#cli';
-import {debugComposite} from '#composite';
-import {internalDefaultStringsFile, processLanguageFile} from '#language';
-import {isMain} from '#node-utils';
-import {bindOpts, showAggregate} from '#sugar';
-import {generateURLs, urlSpec} from '#urls';
-import {quickLoadAllFromYAML} from '#yaml';
-
-import _find, {bindFind} from '#find';
-import CacheableObject from '#cacheable-object';
-import thingConstructors from '#things';
-import * as serialize from '#serialize';
-import * as sugar from '#sugar';
-import * as wikiDataUtils from '#wiki-data';
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url));
-
-export async function getContextAssignments({
-  dataPath,
-  mediaPath,
-  wikiData,
-}) {
-  let urls;
-  try {
-    urls = generateURLs(urlSpec);
-  } catch (error) {
-    console.error(error);
-    logWarn`Failed to generate URL mappings for built-in urlSpec`;
-    logWarn`\`urls\` variable will be missing`;
-  }
-
-  let find;
-  try {
-    find = bindFind(wikiData);
-  } catch (error) {
-    console.error(error);
-    logWarn`Failed to prepare wikiData-bound find() functions`;
-    logWarn`\`find\` variable will be missing`;
-  }
-
-  let language;
-  try {
-    language = await processLanguageFile(internalDefaultStringsFile);
-  } catch (error) {
-    console.error(error);
-    logWarn`Failed to create Language object`;
-    logWarn`\`language\` variable will be missing`;
-    language = undefined;
-  }
-
-  return {
-    dataPath,
-    mediaPath,
-
-    wikiData,
-    ...wikiData,
-    WD: wikiData,
-
-    ...thingConstructors,
-    CacheableObject,
-    language,
-    debugComposite,
-
-    ...sugar,
-    ...wikiDataUtils,
-
-    serialize,
-    S: serialize,
-
-    urls,
-
-    _find,
-    find,
-    bindFind,
-  };
-}
-
-export default async function bootRepl({
-  dataPath = process.env.HSMUSIC_DATA,
-  mediaPath = process.env.HSMUSIC_MEDIA,
-  disableHistory = false,
-  showTraces = false,
-}) {
-  if (!dataPath) {
-    logError`Expected --data-path option or HSMUSIC_DATA to be set`;
-    return;
-  }
-
-  if (!mediaPath) {
-    logError`Expected --media-path option or HSMUSIC_MEDIA to be set`;
-    return;
-  }
-
-  console.log('HSMusic data REPL');
-
-  const wikiData = await quickLoadAllFromYAML(dataPath, {
-    showAggregate: bindOpts(showAggregate, {
-      showTraces,
-      pathToFileURL: (f) => path.relative(__dirname, fileURLToPath(f)),
-    }),
-  });
-
-  const replServer = repl.start();
-
-  Object.assign(replServer.context, await getContextAssignments({
-    dataPath,
-    mediaPath,
-    wikiData,
-  }));
-
-  if (disableHistory) {
-    console.log(`\rInput history disabled (--no-repl-history provided)`);
-    replServer.displayPrompt(true);
-  } else {
-    const historyFile = path.join(os.homedir(), '.hsmusic_repl_history');
-    replServer.setupHistory(historyFile, (err) => {
-      if (err) {
-        console.error(
-          `\rFailed to begin locally logging input history to ${historyFile} (provide --no-repl-history to disable)`
-        );
-      } else {
-        console.log(
-          `\rLogging input history to ${historyFile} (provide --no-repl-history to disable)`
-        );
-      }
-      replServer.displayPrompt(true);
-    });
-  }
-
-  // Is this called breaking a promise?
-  await new Promise(() => {});
-
-  return true;
-}
-
-async function main() {
-  const miscOptions = await parseOptions(process.argv.slice(2), {
-    'data-path': {
-      type: 'value',
-    },
-
-    'media-path': {
-      type: 'value',
-    },
-
-    'no-repl-history': {
-      type: 'flag',
-    },
-
-    'show-traces': {
-      type: 'flag',
-    },
-  });
-
-  return bootRepl({
-    dataPath: miscOptions['data-path'],
-    mediaPath: miscOptions['media-path'],
-    disableHistory: miscOptions['no-repl-history'],
-    showTraces: miscOptions['show-traces'],
-  });
-}
-
-if (isMain(import.meta.url)) {
-  main().catch((error) => {
-    if (error instanceof AggregateError) {
-      showAggregate(error);
-    } else {
-      console.error(error);
-    }
-  });
-}
diff --git a/src/upd8.js b/src/upd8.js
index 5b6b440..17c4d36 100755
--- a/src/upd8.js
+++ b/src/upd8.js
@@ -43,7 +43,6 @@ import {displayCompositeCacheAnalysis} from '#composite';
 import {processLanguageFile, watchLanguageFile, internalDefaultStringsFile}
   from '#language';
 import {isMain, traverse} from '#node-utils';
-import bootRepl from '#repl';
 import {empty, showAggregate, withEntries} from '#sugar';
 import {generateURLs, urlSpec} from '#urls';
 import {sortByName} from '#wiki-data';
@@ -250,16 +249,6 @@ async function main() {
       type: 'value',
     },
 
-    'repl': {
-      help: `Boot into the HSMusic REPL for command-line interactive access to data objects`,
-      type: 'flag',
-    },
-
-    'no-repl-history': {
-      help: `Disable locally logging commands entered into the REPL in your home directory`,
-      type: 'flag',
-    },
-
     'skip-reference-validation': {
       help: `Skips checking and reporting reference errors, which speeds up the build but may silently allow erroneous data to pass through`,
       type: 'flag',
@@ -481,9 +470,6 @@ async function main() {
 
   showStepStatusSummary = cliOptions['show-step-summary'] ?? false;
 
-  const replFlag = cliOptions['repl'] ?? false;
-  const disableReplHistory = cliOptions['no-repl-history'] ?? false;
-
   const showAggregateTraces = cliOptions['show-traces'] ?? false;
 
   const precacheMode = cliOptions['precache-mode'] ?? 'common';
@@ -508,16 +494,6 @@ async function main() {
     return false;
   }
 
-  if (replFlag) {
-    return bootRepl({
-      dataPath,
-      mediaPath,
-
-      disableHistory: disableReplHistory,
-      showTraces: showAggregateTraces,
-    });
-  }
-
   if (cliOptions['no-build']) {
     logInfo`Won't generate any site or page files this run (--no-build passed).`;
 
diff --git a/src/write/build-modes/index.js b/src/write/build-modes/index.js
index 91e3900..3ae2cfc 100644
--- a/src/write/build-modes/index.js
+++ b/src/write/build-modes/index.js
@@ -1,2 +1,3 @@
 export * as 'live-dev-server' from './live-dev-server.js';
+export * as 'repl' from './repl.js';
 export * as 'static-build' from './static-build.js';
diff --git a/src/write/build-modes/repl.js b/src/write/build-modes/repl.js
new file mode 100644
index 0000000..1a9fbd3
--- /dev/null
+++ b/src/write/build-modes/repl.js
@@ -0,0 +1,163 @@
+export const description = `Provide command-line interactive access to wiki data objects`;
+
+export const config = {
+  fileSizes: {
+    default: 'skip',
+  },
+
+  languageReloading: {
+    default: true,
+  },
+
+  mediaValidation: {
+    default: 'skip',
+  },
+
+  thumbs: {
+    applicable: false,
+  },
+};
+
+export function getCLIOptions() {
+  return {
+    'no-repl-history': {
+      help: `Disable locally logging commands entered into the REPL in your home directory`,
+      type: 'flag',
+    },
+  };
+}
+
+import * as os from 'node:os';
+import * as path from 'node:path';
+import * as repl from 'node:repl';
+import {fileURLToPath} from 'node:url';
+
+import {logError, logWarn, parseOptions} from '#cli';
+import {debugComposite} from '#composite';
+import {internalDefaultStringsFile, processLanguageFile} from '#language';
+import {isMain} from '#node-utils';
+import {bindOpts, showAggregate} from '#sugar';
+import {generateURLs, urlSpec} from '#urls';
+import {quickLoadAllFromYAML} from '#yaml';
+
+import _find, {bindFind} from '#find';
+import CacheableObject from '#cacheable-object';
+import thingConstructors from '#things';
+import * as serialize from '#serialize';
+import * as sugar from '#sugar';
+import * as wikiDataUtils from '#wiki-data';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+export async function getContextAssignments({
+  dataPath,
+  mediaPath,
+  mediaCachePath,
+
+  defaultLanguage,
+  languages,
+  missingImagePaths,
+  thumbsCache,
+  urls,
+  wikiData,
+
+  getSizeOfAdditionalFile,
+  getSizeOfImagePath,
+  niceShowAggregate,
+}) {
+  let find;
+  try {
+    find = bindFind(wikiData);
+  } catch (error) {
+    console.error(error);
+    logWarn`Failed to prepare wikiData-bound find() functions`;
+    logWarn`\`find\` variable will be missing`;
+  }
+
+  const replContext = {
+    dataPath,
+    mediaPath,
+    mediaCachePath,
+
+    languages,
+    defaultLanguage,
+    language: defaultLanguage,
+
+    missingImagePaths,
+    thumbsCache,
+    urls,
+
+    wikiData,
+    ...wikiData,
+    WD: wikiData,
+
+    ...thingConstructors,
+    CacheableObject,
+    debugComposite,
+
+    ...sugar,
+    ...wikiDataUtils,
+
+    serialize,
+    S: serialize,
+
+    _find,
+    find,
+    bindFind,
+
+    getSizeOfAdditionalFile,
+    getSizeOfImagePath,
+    showAggregate: niceShowAggregate,
+  };
+
+  replContext.replContext = replContext;
+
+  return replContext;
+}
+
+export async function go(buildOptions) {
+  const {
+    cliOptions,
+    closeLanguageWatchers,
+  } = buildOptions;
+
+  const disableHistory = cliOptions['no-repl-history'] ?? false;
+
+  console.log('HSMusic data REPL');
+
+  const replServer = repl.start();
+
+  Object.assign(replServer.context,
+    await getContextAssignments(buildOptions));
+
+  if (disableHistory) {
+    console.log(`\rInput history disabled (--no-repl-history provided)`);
+  } else {
+    const historyFile = path.join(os.homedir(), '.hsmusic_repl_history');
+    await new Promise(resolve => {
+      replServer.setupHistory(historyFile, (err) => {
+        if (err) {
+          console.error(`\rFailed to begin locally logging input history to ${historyFile} (provide --no-repl-history to disable)`);
+        } else {
+          console.log(`\rLogging input history to ${historyFile} (provide --no-repl-history to disable)`);
+        }
+        resolve();
+      });
+    });
+  }
+
+  replServer.displayPrompt(true);
+
+  let resolveDone;
+
+  replServer.on('exit', () => {
+    closeLanguageWatchers();
+    resolveDone();
+  });
+
+  await new Promise(resolve => {
+    resolveDone = resolve;
+  });
+
+  return true;
+}