@sdeverywhere/plugin-config 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # @sdeverywhere/plugin-config
2
2
 
3
- This package provides a plugin that reads CSV files used to configure a library or app
4
- around an SDEverywhere-generated system dynamics model.
3
+ This package provides a plugin that reads CSV files used to configure a library or app around a System Dynamics model generated by [SDEverywhere](https://github.com/climateinteractive/SDEverywhere).
4
+
5
+ ## Quick Start
6
+
7
+ The best way to get started with SDEverywhere is to follow the [Quick Start](https://github.com/climateinteractive/SDEverywhere#quick-start) instructions.
8
+ If you follow those instructions, the `@sdeverywhere/plugin-config` package will be added to your project automatically, in which case you can skip the next section and jump straight to the ["Usage"](#usage) section below.
5
9
 
6
10
  ## Install
7
11
 
@@ -18,6 +22,9 @@ yarn add -D @sdeverywhere/plugin-config
18
22
 
19
23
  ## Usage
20
24
 
25
+ _Note:_ If you followed the "Quick Start" instructions above, the `create` script will have already performed the following steps for you.
26
+ Reading these instructions can still be helpful if you are setting up a project manually or want to understand how `plugin-config` can be integrated into your project.
27
+
21
28
  To get started:
22
29
 
23
30
  1. Copy the included template config files to your local project:
@@ -38,12 +45,12 @@ import { configProcessor } from '@sdeverywhere/plugin-config'
38
45
  export async function config() {
39
46
  return {
40
47
  // Specify the Vensim model to read
41
- modelFiles: ['sample.mdl'],
48
+ modelFiles: ['example.mdl'],
42
49
 
43
- // Read csv files from `config` directory and write to the `generated` directory
50
+ // Read csv files from `config` directory and write to the recommended output
51
+ // directory structure. See `ConfigProcessorOptions` for more details.
44
52
  modelSpec: configProcessor({
45
- config: configDir,
46
- out: genDir
53
+ config: 'config'
47
54
  }),
48
55
 
49
56
  plugins: [
@@ -55,6 +62,10 @@ export async function config() {
55
62
 
56
63
  4. Run `sde bundle` or `sde dev`; your config files will be used to drive the build process.
57
64
 
65
+ ## Documentation
66
+
67
+ API documentation (for plugin configuration options) is available in the [`docs`](./docs/index.md) directory.
68
+
58
69
  ## License
59
70
 
60
71
  SDEverywhere is distributed under the MIT license. See `LICENSE` for more details.
package/dist/index.cjs CHANGED
@@ -17,6 +17,10 @@ var __copyProps = (to, from, except, desc) => {
17
17
  return to;
18
18
  };
19
19
  var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
20
24
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
21
25
  mod
22
26
  ));
@@ -29,6 +33,10 @@ __export(src_exports, {
29
33
  });
30
34
  module.exports = __toCommonJS(src_exports);
31
35
 
36
+ // ../../node_modules/.pnpm/tsup@7.2.0_typescript@5.2.2/node_modules/tsup/assets/cjs_shims.js
37
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL("file:" + __filename).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
38
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
39
+
32
40
  // src/processor.ts
33
41
  var import_fs3 = require("fs");
34
42
  var import_path3 = require("path");
@@ -96,8 +104,20 @@ var Strings = class {
96
104
  });
97
105
  return key;
98
106
  }
107
+ /**
108
+ * Write a `<lang>.js` file containing translated strings for each supported language.
109
+ *
110
+ * @param context The build context.
111
+ * @param dstDir The `strings` directory in the core package.
112
+ * @param xlatLangs The set of languages that are configured for translation.
113
+ */
99
114
  writeJsFiles(context, dstDir) {
100
- writeLangJsFiles(context, dstDir, this.records);
115
+ writeLangJsFiles(
116
+ context,
117
+ dstDir,
118
+ this.records
119
+ /*, xlatLangs*/
120
+ );
101
121
  }
102
122
  };
103
123
  function getSortedRecords(records) {
@@ -224,21 +244,49 @@ var ConfigContext = class {
224
244
  this.outputVarNames = /* @__PURE__ */ new Map();
225
245
  this.staticVarNames = /* @__PURE__ */ new Map();
226
246
  }
247
+ /**
248
+ * Read a CSV file of the given name from the config directory.
249
+ *
250
+ * @param name The base name of the CSV file.
251
+ */
227
252
  readConfigCsvFile(name) {
228
253
  return readConfigCsvFile(this.configDir, name);
229
254
  }
255
+ /**
256
+ * Log a message to the console and/or the in-browser overlay panel.
257
+ *
258
+ * @param level The log level (verbose, info, error).
259
+ * @param msg The message.
260
+ */
230
261
  log(level, msg) {
231
262
  this.buildContext.log(level, msg);
232
263
  }
264
+ /**
265
+ * Write a file to the staged directory.
266
+ *
267
+ * This file will be copied (along with other staged files) into the destination
268
+ * directory only after the build process has completed. Copying all staged files
269
+ * at once helps improve the local development experience by making it so that
270
+ * live reloading tools only need to refresh once instead of every time a build
271
+ * file is written.
272
+ *
273
+ * @param srcDir The directory underneath the configured `staged` directory where
274
+ * the file will be written (this must be a relative path).
275
+ * @param dstDir The absolute path to the destination directory where the staged
276
+ * file will be copied when the build has completed.
277
+ * @param filename The name of the file.
278
+ * @param content The file content.
279
+ */
233
280
  writeStagedFile(srcDir, dstDir, filename, content) {
234
281
  this.buildContext.writeStagedFile(srcDir, dstDir, filename, content);
235
282
  }
236
- addInputVariable(inputVarName, defaultValue, minValue, maxValue) {
283
+ addInputVariable(inputId, inputVarName, defaultValue, minValue, maxValue) {
237
284
  const varId = sdeNameForVensimVarName(inputVarName);
238
285
  if (this.inputSpecs.get(varId)) {
239
286
  console.error(`ERROR: Input variable ${inputVarName} was already added`);
240
287
  }
241
288
  this.inputSpecs.set(varId, {
289
+ inputId,
242
290
  varName: inputVarName,
243
291
  defaultValue,
244
292
  minValue,
@@ -275,7 +323,11 @@ var ConfigContext = class {
275
323
  });
276
324
  }
277
325
  writeStringsFiles(dstDir) {
278
- this.strings.writeJsFiles(this.buildContext, dstDir);
326
+ this.strings.writeJsFiles(
327
+ this.buildContext,
328
+ dstDir
329
+ /*, xlatLangs*/
330
+ );
279
331
  }
280
332
  };
281
333
  function createConfigContext(buildContext, configDir) {
@@ -468,7 +520,7 @@ function graphSpecFromCsv(g, context) {
468
520
  return;
469
521
  }
470
522
  const varId = sdeNameForVensimVarName(varName);
471
- const externalSourceName = (overrides == null ? void 0 : overrides.sourceName) || optionalString(g[plotKey("source")]);
523
+ const externalSourceName = overrides?.sourceName || optionalString(g[plotKey("source")]);
472
524
  const datasetLabel = optionalString(g[plotKey("label")]);
473
525
  let labelKey;
474
526
  if (datasetLabel) {
@@ -479,7 +531,7 @@ function graphSpecFromCsv(g, context) {
479
531
  "Graph Dataset Label"
480
532
  );
481
533
  }
482
- const colorId = (overrides == null ? void 0 : overrides.colorId) || requiredString(plotKey("color"));
534
+ const colorId = overrides?.colorId || requiredString(plotKey("color"));
483
535
  const hexColor = context.getHexColorForId(colorId);
484
536
  if (!hexColor) {
485
537
  throw new Error(`Graph ${graphId} references an unknown color ${colorId}`);
@@ -512,10 +564,7 @@ function graphSpecFromCsv(g, context) {
512
564
  for (let i = 1; i <= 11; i++) {
513
565
  addDataset(i);
514
566
  }
515
- const legendItems = datasets.filter((dataset) => {
516
- var _a;
517
- return ((_a = dataset.labelKey) == null ? void 0 : _a.length) > 0;
518
- }).map((dataset) => {
567
+ const legendItems = datasets.filter((dataset) => dataset.labelKey?.length > 0).map((dataset) => {
519
568
  return {
520
569
  color: dataset.color,
521
570
  labelKey: dataset.labelKey
@@ -636,7 +685,7 @@ function inputSpecFromCsv(r, context) {
636
685
  e += `default=${defaultValue} min=${minValue} max=${maxValue}`;
637
686
  throw new Error(e);
638
687
  }
639
- context.addInputVariable(varName, defaultValue, minValue, maxValue);
688
+ context.addInputVariable(inputId, varName, defaultValue, minValue, maxValue);
640
689
  const format = optionalString(r["format"]) || ".0f";
641
690
  const units = optionalString(r["units"]);
642
691
  let unitsKey;
@@ -678,7 +727,7 @@ function inputSpecFromCsv(r, context) {
678
727
  }
679
728
  const minValue = Math.min(offValue, onValue);
680
729
  const maxValue = Math.max(offValue, onValue);
681
- context.addInputVariable(varName, defaultValue, minValue, maxValue);
730
+ context.addInputVariable(inputId, varName, defaultValue, minValue, maxValue);
682
731
  const controlledInputIds = requiredString("controlled input ids");
683
732
  const controlledParts = controlledInputIds.split("|");
684
733
  const rowsActiveWhenOff = controlledParts[0].split(";").filter((id) => id.trim().length > 0);
@@ -748,8 +797,7 @@ function getSliderRangeInfo(r, maxValue, context) {
748
797
  }
749
798
 
750
799
  // src/gen-config-specs.ts
751
- var import_meta = {};
752
- var __dirname = (0, import_path2.dirname)((0, import_url.fileURLToPath)(import_meta.url));
800
+ var __dirname = (0, import_path2.dirname)((0, import_url.fileURLToPath)(importMetaUrl));
753
801
  function generateConfigSpecs(context) {
754
802
  context.log("verbose", " Reading graph specs");
755
803
  const graphSpecs = generateGraphSpecs(context);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/processor.ts","../src/context.ts","../src/strings.ts","../src/var-names.ts","../src/gen-model-spec.ts","../src/gen-config-specs.ts","../src/read-config.ts","../src/gen-graphs.ts","../src/gen-inputs.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport { configProcessor } from './processor'\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport type { BuildContext, ModelSpec } from '@sdeverywhere/build'\n\nimport { createConfigContext } from './context'\nimport { writeModelSpec } from './gen-model-spec'\nimport { generateConfigSpecs, writeConfigSpecs, writeSpecTypes } from './gen-config-specs'\n\nexport interface ConfigOutputPaths {\n /** The absolute path to the directory where model spec files will be written. */\n modelSpecsDir?: string\n\n /** The absolute path to the directory where config spec files will be written. */\n configSpecsDir?: string\n\n /** The absolute path to the directory where translated strings will be written. */\n stringsDir?: string\n}\n\nexport interface ConfigOptions {\n /**\n * The absolute path to the directory containing the CSV config files.\n */\n config: string\n\n /**\n * Either a single path to a base output directory (in which case, the recommended\n * directory structure will be used) or a `ConfigOutputPaths` containing specific paths.\n * If a single string is provided, the following subdirectories will be used:\n * <out-dir>/\n * src/\n * config/\n * generated/\n * model/\n * generated/\n * strings/\n */\n out?: string | ConfigOutputPaths\n}\n\n/**\n * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere\n * `UserConfig`. The returned function:\n * - reads CSV files from a `config` directory\n * - writes JS files to the configured output directories\n * - returns a `ModelSpec` that guides the rest of the `sde` build process\n */\nexport function configProcessor(options: ConfigOptions): (buildContext: BuildContext) => Promise<ModelSpec> {\n return buildContext => {\n return processModelConfig(buildContext, options)\n }\n}\n\nasync function processModelConfig(buildContext: BuildContext, options: ConfigOptions): Promise<ModelSpec> {\n const t0 = performance.now()\n\n // Resolve source (config) directory\n if (!existsSync(options.config)) {\n throw new Error(`The provided config dir '${options.config}' does not exist`)\n }\n\n // Resolve output directories\n let outModelSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outModelSpecsDir = joinPath(options.out, 'src', 'model', 'generated')\n } else {\n outModelSpecsDir = options.out.modelSpecsDir\n }\n }\n\n let outConfigSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outConfigSpecsDir = joinPath(options.out, 'src', 'config', 'generated')\n } else {\n outConfigSpecsDir = options.out.configSpecsDir\n }\n }\n\n let outStringsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outStringsDir = joinPath(options.out, 'strings')\n } else {\n outStringsDir = options.out.stringsDir\n }\n }\n\n // Create a container for strings, variables, etc\n const context = createConfigContext(buildContext, options.config)\n\n // Write the generated files\n context.log('info', 'Generating files...')\n\n const configSpecs = generateConfigSpecs(context)\n if (outConfigSpecsDir) {\n context.log('verbose', ' Writing config specs')\n writeConfigSpecs(context, configSpecs, outConfigSpecsDir)\n writeSpecTypes(context, outConfigSpecsDir)\n }\n\n if (outModelSpecsDir) {\n context.log('verbose', ' Writing model specs')\n writeModelSpec(context, outModelSpecsDir)\n }\n\n if (outStringsDir) {\n context.log('verbose', ' Writing strings')\n context.writeStringsFiles(outStringsDir)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `Done generating files (${elapsed}s)`)\n\n return {\n inputs: context.getOrderedInputs(),\n outputs: context.getOrderedOutputs(),\n datFiles: context.datFiles\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { join as joinPath, relative } from 'path'\n\nimport { parse as parseCsv } from 'csv-parse/sync'\n\nimport type { BuildContext, InputSpec, LogLevel, OutputSpec } from '@sdeverywhere/build'\n\nimport type { HexColor } from './spec-types'\nimport { Strings } from './strings'\nimport type { InputVarId, OutputVarId } from './var-names'\nimport { sdeNameForVensimVarName } from './var-names'\n\nexport type CsvRow = { [key: string]: string }\nexport type ColorId = string\n\nexport class ConfigContext {\n private readonly inputSpecs: Map<InputVarId, InputSpec> = new Map()\n private readonly outputVarNames: Map<OutputVarId, string> = new Map()\n private readonly staticVarNames: Map<string, Set<string>> = new Map()\n\n constructor(\n private readonly buildContext: BuildContext,\n private readonly configDir: string,\n public readonly strings: Strings,\n private readonly colorMap: Map<ColorId, HexColor>,\n public readonly graphDefaultMinTime: number,\n public readonly graphDefaultMaxTime: number,\n public readonly datFiles: string[]\n ) {}\n\n /**\n * Read a CSV file of the given name from the config directory.\n *\n * @param name The base name of the CSV file.\n */\n readConfigCsvFile(name: string): CsvRow[] {\n return readConfigCsvFile(this.configDir, name)\n }\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n this.buildContext.log(level, msg)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.buildContext.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n addInputVariable(inputVarName: string, defaultValue: number, minValue: number, maxValue: number): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(inputVarName)\n if (this.inputSpecs.get(varId)) {\n // Fail if the variable was already added (there should only be one spec\n // per input variable)\n console.error(`ERROR: Input variable ${inputVarName} was already added`)\n }\n this.inputSpecs.set(varId, {\n varName: inputVarName,\n defaultValue,\n minValue,\n maxValue\n })\n }\n\n addOutputVariable(outputVarName: string): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(outputVarName)\n this.outputVarNames.set(varId, outputVarName)\n }\n\n addStaticVariable(sourceName: string, varName: string): void {\n const sourceVarNames = this.staticVarNames.get(sourceName)\n if (sourceVarNames) {\n sourceVarNames.add(varName)\n } else {\n const varNames: Set<string> = new Set()\n varNames.add(varName)\n this.staticVarNames.set(sourceName, varNames)\n }\n }\n\n getHexColorForId(colorId: ColorId): HexColor {\n return this.colorMap.get(colorId)\n }\n\n getOrderedInputs(): InputSpec[] {\n // TODO: It would be nice to alphabetize the inputs, but currently we have\n // code that assumes that the InputSpecs in the map have the same order\n // as the variables in the spec file and model config, so preserve the\n // existing order here for now\n return Array.from(this.inputSpecs.values())\n }\n\n getOrderedOutputs(): OutputSpec[] {\n // Sort the output variable names alphabetically\n const alphabetical = (a: string, b: string) => (a > b ? 1 : b > a ? -1 : 0)\n const varNames = Array.from(this.outputVarNames.values()).sort(alphabetical)\n return varNames.map(varName => {\n return {\n varName\n }\n })\n }\n\n writeStringsFiles(dstDir: string): void {\n this.strings.writeJsFiles(this.buildContext, dstDir /*, xlatLangs*/)\n }\n}\n\nexport function createConfigContext(buildContext: BuildContext, configDir: string): ConfigContext {\n // Read basic model configuration from `model.csv`\n const modelCsv = readConfigCsvFile(configDir, 'model')[0]\n const graphDefaultMinTime = Number(modelCsv['graph default min time'])\n const graphDefaultMaxTime = Number(modelCsv['graph default max time'])\n const datFilesString = modelCsv['model dat files']\n const origDatFiles = datFilesString.length > 0 ? datFilesString.split(';') : []\n\n // The dat file paths in the config file are assumed to be relative to\n // the project directory (i.e., the directory where the `sde.config.js`\n // file resides), so we need to convert to paths that are relative to\n // the `sde-prep` directory (since that is the \"model\" directory from\n // the perspective of the compile package).\n const prepDir = buildContext.config.prepDir\n const projDir = buildContext.config.rootDir\n const datFiles = origDatFiles.map(f => joinPath(relative(prepDir, projDir), f))\n\n // Read the static strings from `strings.csv`\n const strings = readStringsCsv(configDir)\n\n // Read color configuration from `colors.csv`\n const colorsCsv = readConfigCsvFile(configDir, 'colors')\n const colors = new Map()\n for (const row of colorsCsv) {\n const colorId = row['id']\n const hexColor = row['hex code']\n colors.set(colorId, hexColor)\n }\n\n return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles)\n}\n\nfunction configFilePath(configDir: string, name: string, ext: string): string {\n return joinPath(configDir, `${name}.${ext}`)\n}\n\nfunction readCsvFile(path: string): CsvRow[] {\n const data = readFileSync(path, 'utf8')\n return parseCsv(data, {\n columns: true,\n trim: true,\n skip_empty_lines: true,\n skip_records_with_empty_values: true\n })\n}\n\nfunction readConfigCsvFile(configDir: string, name: string): CsvRow[] {\n return readCsvFile(configFilePath(configDir, name, 'csv'))\n}\n\n/**\n * Initialize a `Strings` instance with the core strings from `strings.csv`.\n */\nfunction readStringsCsv(configDir: string): Strings {\n const strings = new Strings()\n\n // TODO: For now we use the same \"layout\" and \"context\" for all core strings\n const layout = 'default'\n const context = 'Core'\n\n const rows = readConfigCsvFile(configDir, 'strings')\n for (const row of rows) {\n const key = row['id']\n let str = row['string']\n str = str ? str.trim() : ''\n if (str) {\n strings.add(key, str, layout, context)\n }\n }\n\n return strings\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport sanitizeHtml from 'sanitize-html'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type { StringKey } from './spec-types'\n\ninterface StringRecord {\n key: StringKey\n str: string\n layout: string\n context: string\n grouping: string\n appendedStringKeys?: string[]\n}\n\ntype LangCode = string\ntype StringMap = Map<StringKey, string>\ntype XlatMap = Map<LangCode, StringMap>\n\nexport class Strings {\n private readonly records: Map<StringKey, StringRecord> = new Map()\n\n add(\n key: StringKey,\n str: string,\n layout: string,\n context: string,\n grouping?: string,\n appendedStringKeys?: string[]\n ): StringKey {\n checkInvisibleCharacters(str || '')\n\n if (!key) {\n throw new Error(`Must provide a key for the string: ${str}`)\n }\n\n const validKey = /^[0-9a-z_]+$/.test(key)\n if (!validKey) {\n throw new Error(`String key contains undesirable characters: ${key}`)\n }\n\n if (!layout) {\n throw new Error(`Must provide a layout (e.g., 'layout1' or 'not-translated')`)\n }\n\n if (!context) {\n throw new Error(`Must provide a context string: key=${key}, string=${str}`)\n }\n\n if (context === 'Core') {\n // For core strings from the `strings.csv` file, leave the context empty for now\n // (indicating this is a \"general\" string with no particular context)\n context = undefined\n }\n\n if (!grouping) {\n // Use 'primary' if grouping is not specified\n grouping = 'primary'\n }\n\n // Convert some HTML tags and entities to UTF-8\n if (str) {\n str = str.trim()\n str = htmlToUtf8(str)\n }\n\n // If the trimmed string is empty, do not create a new key, and return an empty string\n if (!str) {\n return ''\n }\n\n if (this.records.has(key)) {\n // TODO: For now, allow certain keys to appear more than once; we only add the string once\n const prefix = key.substring(0, key.indexOf('__'))\n switch (prefix) {\n case 'graph_dataset_label':\n case 'graph_xaxis_label':\n case 'graph_yaxis_label':\n case 'input_group_title':\n case 'input_range':\n case 'input_units':\n break\n default:\n throw new Error(`More than one string with key=${key}`)\n }\n }\n\n // Add the string record\n this.records.set(key, {\n key,\n str,\n layout,\n context,\n grouping,\n appendedStringKeys\n })\n\n return key\n }\n\n /**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param xlatLangs The set of languages that are configured for translation.\n */\n writeJsFiles(context: BuildContext, dstDir: string /*, xlatLangs: Map<LangCode, LangSpec>*/): void {\n writeLangJsFiles(context, dstDir, this.records /*, xlatLangs*/)\n }\n}\n\nfunction getSortedRecords(records: Map<StringKey, StringRecord>): StringRecord[] {\n // Sort records by string key\n return Array.from(records.values()).sort((a, b) => {\n return a.key > b.key ? 1 : b.key > a.key ? -1 : 0\n })\n}\n\nfunction checkInvisibleCharacters(s: string): void {\n if (s.includes('\\u00a0')) {\n const e = s.replace(/\\u00a0/g, 'HERE')\n throw new Error(\n `String contains one or more non-breaking space characters (to fix, replace \"HERE\" with a normal space):\\n ${e}`\n )\n }\n}\n\nfunction utf8SubscriptToHtml(key: StringKey, s: string): string {\n if (key.includes('graph_yaxis_label')) {\n // Chart.js doesn't support using HTML tags like subscripts or superscripts\n // in axis labels. For now, we will convert subscript literals in axis labels\n // to a simple number. (We could preserve the subscript literal, but it doesn't\n // render all that well.)\n s = s.replace(/₂/gi, '2')\n s = s.replace(/₃/gi, '3')\n s = s.replace(/₄/gi, '4')\n s = s.replace(/₆/gi, '6')\n return s\n }\n\n // Unicode subscript literals render differently in some browsers (very low\n // in Safari for example), so we will replace them with HTML `sub` tags\n s = s.replace(/₂/gi, '<sub>2</sub>')\n s = s.replace(/₃/gi, '<sub>3</sub>')\n s = s.replace(/₄/gi, '<sub>4</sub>')\n s = s.replace(/₆/gi, '<sub>6</sub>')\n\n // If the subscript tag is followed by a space, that space needs to be\n // replaced with a non-breaking space, otherwise the whitespace will be lost\n s = s.replace(/<\\/sub> /gi, '</sub>&nbsp;')\n\n return s\n}\n\nfunction htmlSubscriptAndSuperscriptToUtf8(s: string): string {\n // Subscripts have a straight mapping in Unicode (U+208x)\n s = s.replace(/<sub>(\\d)<\\/sub>/gi, (_match, p1) => String.fromCharCode(0x2080 + Number(p1)))\n\n // Superscripts don't have a straight mapping, so it's easier to just\n // replace the ones we care about. There are some others (12, 18, -5)\n // that don't render well when replaced with their Unicode superscript\n // equivalents, so we will leave those with HTML `sup` tags.\n s = s.replace(/<sup>6<\\/sup>/gi, '\\u2076')\n s = s.replace(/<sup>9<\\/sup>/gi, '\\u2079')\n\n return s\n}\n\nexport function htmlToUtf8(orig: string): string {\n // Replace common HTML tags and entities with UTF-8 characters\n let s = orig\n\n // Convert `sub` and `sup` tags\n s = htmlSubscriptAndSuperscriptToUtf8(s)\n\n let clean = sanitizeHtml(s, {\n allowedTags: ['a', 'b', 'br', 'i', 'em', 'li', 'p', 'strong', 'sub', 'sup', 'ul'],\n allowedAttributes: {\n a: ['href', 'target', 'rel']\n }\n })\n\n // XXX: The `sanitize-html` package converts `&nbsp;` to the Unicode\n // equivalent (`U+00A0`); we will convert it back to `&nbsp;` to make\n // it more obvious and easier to view in a translation tool\n clean = clean.replace(/\\u00a0/gi, '&nbsp;')\n\n // if (clean !== s) {\n // console.log(`IN: ${orig}`)\n // console.log(`O1: ${s}`)\n // console.log(`O2: ${clean}\\n`)\n // }\n\n return clean\n}\n\n/**\n * Generate a string key for the given string by replacing special characters with\n * underscores and converting other characters to lowercase.\n */\nexport function genStringKey(prefix: string, s: string): string {\n checkInvisibleCharacters(s)\n\n let key = s.toLowerCase()\n key = key.replace(/ – /g, '_') // e.g. 'Data – Satellite' (with emdash) -> 'data_satellite'\n key = key.replace(/ \\/ /g, '_per_') // e.g. 'CO2 / TJ' -> 'co2_per_tj'\n key = key.replace(/ /g, '_')\n key = key.replace('₂', '2') // e.g. 'CO₂' -> 'co2'\n key = key.replace('<sub>2</sub>', '2') // e.g. 'CO<sub>2</sub>' -> 'co2'\n key = key.replace(/\\$\\//g, 'dollars_per_') // e.g. '$/year' -> 'dollars_per_year'\n key = key.replace(/%\\//g, 'pct_per_') // e.g. '%/year' -> 'pct_per_year'\n key = key.replace(/\\//g, '_per_') // e.g. 'CO2/TJ' -> 'co2_per_tj'\n key = key.replace(/º/g, 'degrees_') // e.g. 'ºC' -> 'degrees_c'\n key = key.replace(/\\*/g, '_') // e.g. 'CO2*year' -> 'co2_year'\n key = key.replace(/%/g, 'pct') // e.g. '%' -> 'pct'\n key = key.replace(/\\$/g, 'dollars') // e.g. '$' -> 'dollars'\n key = key.replace(/&/g, 'and') // e.g. 'Actions & Outcomes' -> 'actions_and_outcomes'\n key = key.replace(/\\//g, 'per') // e.g. 'Gigatons CO2/year' -> 'gigatons_co2_per_year'\n key = key.replace(/:/g, '') // e.g. 'Net:' -> 'net'\n key = key.replace(/\\./g, '') // e.g. 'U.S. Units' -> 'us_units'\n key = key.replace(/-/g, '_') // e.g. 'some-thing' -> 'some_thing'\n key = key.replace(/—/g, '_') // endash to underscore\n key = key.replace(/–/g, '_') // emdash to underscore\n key = key.replace(/,/g, '')\n key = key.replace(/\\(/g, '')\n key = key.replace(/\\)/g, '')\n key = key.replace(/\\\\n/g, '')\n key = key.replace(/<br>/g, '_')\n return `${prefix}__${key}`\n}\n\n/**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * These files are currently saved as plain JS (ES6) files. The only difference compared\n * to JSON files is these JS files start with `export default`, so converting to JSON is\n * as trivial as stripping those two words, if needed.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param records The string records.\n * //@param xlatLangs The set of languages that are configured for translation.\n */\nfunction writeLangJsFiles(\n context: BuildContext,\n dstDir: string,\n records: Map<StringKey, StringRecord>\n // xlatLangs: Map<LangCode, LangSpec>\n): void {\n const xlatMap: XlatMap = new Map()\n const sortedRecords = getSortedRecords(records)\n\n // const baseStringForKey = (key: StringKey) => {\n // const record = records.get(key)\n // if (!record) {\n // throw new Error(`No base string found for key=${key}`)\n // }\n // return record.str\n // }\n\n // Add base (e.g., English) strings that were gathered from the config files\n const enStrings: StringMap = new Map()\n for (const record of sortedRecords) {\n const s = record.str\n enStrings.set(record.key, utf8SubscriptToHtml(record.key, s))\n }\n // TODO: Don't assume English, make the base language configurable\n xlatMap.set('en', enStrings)\n\n // TODO: Enable support for translation files (for now, we only write base strings)\n // const hasSecondary =\n // existsSync(projectFilePath('localization', 'graph-descriptions')) &&\n // existsSync(projectFilePath('localization', 'input-descriptions'))\n // for (const lang of xlatLangs.keys()) {\n // const langStrings: StringMap = new Map()\n\n // let poMsgs: Map<string, string>\n // const primaryMsgs = readXlatPoFile('primary', lang)\n // if (hasSecondary) {\n // const graphMsgs = readXlatPoFile('graph-descriptions', lang)\n // const inputMsgs = readXlatPoFile('input-descriptions', lang)\n // poMsgs = new Map([...primaryMsgs, ...graphMsgs, ...inputMsgs])\n // } else {\n // poMsgs = primaryMsgs\n // }\n\n // const xlatStringForKey = (key: StringKey) => {\n // return poMsgs.get(key)\n // }\n\n // // Add the translation of each English string.\n // for (const record of sortedRecords) {\n // if (record.grouping !== 'primary') {\n // // Only include secondary strings (graph and input descriptions) if they are\n // // explicitly requested for this language; if not included, the English\n // // descriptions will be used as a fallback\n // if (xlatLangs.get(lang).includeSecondary !== true) {\n // continue\n // }\n // }\n\n // const xlatStr = xlatStringForKey(record.key)\n // if (xlatStr) {\n // // Add the translated string\n // const s = xlatStr\n // langStrings.set(record.key, utf8SubscriptToHtml(record.key, s))\n // } else {\n // // No translation for this string. We don't add the English string to\n // // map as a fallback. Instead, we configure the i18n library to use\n // // English strings as a fallback at runtime.\n // // console.warn(`WARNING: No translated string for lang=${lang} id=${stringObj.id}`)\n // }\n // }\n\n // xlatMap.set(lang, langStrings)\n // }\n\n // Write a JS file for each language for use in the core package\n for (const lang of xlatMap.keys()) {\n const stringsForLang = xlatMap.get(lang)\n const stringsObj = Object.fromEntries(stringsForLang)\n const json = JSON.stringify(stringsObj, null, 2)\n context.writeStagedFile('strings', dstDir, `${lang}.js`, `export default ${json}`)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport type InputVarId = string\nexport type OutputVarId = string\n\n/**\n * Helper function that converts a Vensim variable or subscript name\n * into a valid C identifier as used by SDE.\n * TODO: Import helper function from `sdeverywhere` package instead\n */\nfunction sdeNameForVensimName(name: string): string {\n return (\n '_' +\n name\n .trim()\n .replace(/\"/g, '_')\n .replace(/\\s+!$/g, '!')\n .replace(/\\s/g, '_')\n .replace(/,/g, '_')\n .replace(/-/g, '_')\n .replace(/\\./g, '_')\n .replace(/\\$/g, '_')\n .replace(/'/g, '_')\n .replace(/&/g, '_')\n .replace(/%/g, '_')\n .replace(/\\//g, '_')\n .replace(/\\|/g, '_')\n .toLowerCase()\n )\n}\n\n/**\n * Helper function that converts a Vensim variable name (possibly containing\n * subscripts) into a valid C identifier as used by SDE.\n * TODO: Import helper function from `sdeverywhere` package instead\n */\nexport function sdeNameForVensimVarName(varName: string): string {\n const m = varName.match(/([^[]+)(?:\\[([^\\]]+)\\])?/)\n if (!m) {\n throw new Error(`Invalid Vensim name: ${varName}`)\n }\n let id = sdeNameForVensimName(m[1])\n if (m[2]) {\n const subscripts = m[2].split(',').map(x => sdeNameForVensimName(x))\n id += `[${subscripts.join('][')}]`\n }\n\n return id\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext } from './context'\nimport { sdeNameForVensimVarName } from './var-names'\n\n/**\n * Write the `model-spec.ts` file used by the core package to initialize the model.\n */\nexport function writeModelSpec(context: ConfigContext, dstDir: string): void {\n // Create ordered arrays of inputs and outputs\n const inputVarIds = context.getOrderedInputs().map(i => sdeNameForVensimVarName(i.varName))\n const outputVarIds = context.getOrderedOutputs().map(o => sdeNameForVensimVarName(o.varName))\n\n // Generate the `model-spec.ts` file\n let tsContent = ''\n function emit(s: string): void {\n tsContent += s + '\\n'\n }\n\n emit('// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!')\n emit(`export const inputVarIds: string[] = ${JSON.stringify(inputVarIds, null, 2)}`)\n emit(`export const outputVarIds: string[] = ${JSON.stringify(outputVarIds, null, 2)}`)\n\n // Write the `model-spec.ts` file to the staged directory\n context.writeStagedFile('model', dstDir, 'model-spec.ts', tsContent)\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { dirname, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { ConfigContext } from './context'\nimport { generateGraphSpecs } from './gen-graphs'\nimport { generateInputsConfig } from './gen-inputs'\nimport type { GraphId, GraphSpec, InputId, InputSpec } from './spec-types'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport interface ConfigSpecs {\n graphSpecs: Map<GraphId, GraphSpec>\n inputSpecs: Map<InputId, InputSpec>\n}\n\n/**\n * Convert the CSV files in the `config` directory to config specs that can be\n * used in the core package.\n */\nexport function generateConfigSpecs(context: ConfigContext): ConfigSpecs {\n // Convert `graphs.csv` to graph specs\n context.log('verbose', ' Reading graph specs')\n const graphSpecs = generateGraphSpecs(context)\n\n // Convert `inputs.csv` to input specs\n context.log('verbose', ' Reading input specs')\n const inputSpecs = generateInputsConfig(context)\n\n // Include extra output variables that should be included in the generated\n // model even though they are not referenced in any graph specs\n context.log('verbose', ' Reading extra output variables')\n const extraOutputsCsv = context.readConfigCsvFile('outputs')\n for (const row of extraOutputsCsv) {\n const varName = row['variable name']\n if (varName) {\n context.addOutputVariable(varName)\n }\n }\n\n return {\n graphSpecs,\n inputSpecs\n }\n}\n\n/**\n * Write the `config-specs.ts` file to the given destination directory.\n */\nexport function writeConfigSpecs(context: ConfigContext, config: ConfigSpecs, dstDir: string): void {\n // Generate one big string containing the TypeScript source that will be\n // loaded by `config.ts` at runtime\n let tsContent = ''\n function emit(s: string): void {\n tsContent += s + '\\n'\n }\n\n emit('// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!')\n emit('')\n emit(`import type { GraphSpec, InputSpec } from './spec-types'`)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function emitArray(type: string, values: Iterable<any>): void {\n const varName = type.charAt(0).toLowerCase() + type.slice(1) + 's'\n const array = Array.from(values)\n const json = JSON.stringify(array, null, 2)\n emit('')\n emit(`export const ${varName}: ${type}[] = ${json}`)\n }\n\n emitArray('GraphSpec', config.graphSpecs.values())\n emitArray('InputSpec', config.inputSpecs.values())\n\n // Write the `config-specs.ts` file\n context.writeStagedFile('config', dstDir, 'config-specs.ts', tsContent)\n}\n\n/**\n * Write the `spec-types.ts` file to the given destination directory.\n */\nexport function writeSpecTypes(context: ConfigContext, dstDir: string): void {\n // Copy the `spec-types.ts` file. Currently we keep the full source of the file\n // in the `dist` directory for this package so that we can access it here.\n const tsFile = 'spec-types.ts'\n const tsPath = resolvePath(__dirname, tsFile)\n const tsContent = readFileSync(tsPath, 'utf8')\n context.writeStagedFile('config', dstDir, tsFile, tsContent)\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport function optionalString(stringValue?: string): string | undefined {\n if (stringValue !== undefined && stringValue.length > 0) {\n return stringValue\n } else {\n return undefined\n }\n}\n\nexport function optionalNumber(stringValue?: string): number | undefined {\n if (stringValue !== undefined && stringValue.length > 0) {\n return Number(stringValue)\n } else {\n return undefined\n }\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext, CsvRow } from './context'\nimport { optionalNumber, optionalString } from './read-config'\nimport type {\n GraphAlternateSpec,\n GraphDatasetSpec,\n GraphId,\n GraphKind,\n GraphLegendItemSpec,\n GraphSide,\n GraphSpec,\n LineStyle,\n LineStyleModifier,\n StringKey,\n UnitSystem\n} from './spec-types'\nimport { genStringKey, htmlToUtf8 } from './strings'\nimport { sdeNameForVensimVarName } from './var-names'\n\n/**\n * Convert the `config/graphs.csv` file to config specs that can be used in\n * the core package.\n */\nexport function generateGraphSpecs(context: ConfigContext): Map<GraphId, GraphSpec> {\n // TODO: Optionally read the graph descriptions from `graphs.md`\n // let descriptions: Map<GraphId, Description>\n // if (useDescriptions) {\n // descriptions = readGraphDescriptions(context)\n // } else {\n // descriptions = undefined\n // }\n\n // Convert `graphs.csv` to graph specs\n const graphsCsv = context.readConfigCsvFile('graphs')\n const graphSpecs: Map<GraphId, GraphSpec> = new Map()\n for (const row of graphsCsv) {\n const spec = graphSpecFromCsv(row, context)\n if (spec) {\n graphSpecs.set(spec.id, spec)\n }\n }\n\n return graphSpecs\n}\n\nfunction graphSpecFromCsv(g: CsvRow, context: ConfigContext): GraphSpec | undefined {\n const strings = context.strings\n\n // TODO: For now, all strings use the same \"layout\" specifier; this could be customized\n // to provide a \"maximum length\" hint for a group of strings to the translation tool\n const layout = 'default'\n\n function requiredString(key: string): string {\n const value = g[key]\n if (value === undefined || typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`Must specify '${key}' for graph ${g.id}`)\n }\n return value\n }\n\n // Extract required fields\n const graphIdParts = requiredString('id').split(';')\n const graphId = graphIdParts[0]\n const graphIdBaseParts = graphId.split('-')\n const graphBaseId = graphIdBaseParts[0]\n const title = requiredString('graph title')\n\n // Extract optional fields\n const menuTitle = optionalString(g['menu title'])\n const miniTitle = optionalString(g['mini title'])\n const parentMenu = optionalString(g['parent menu'])\n const description = optionalString(g['description'])\n const kindString = optionalString(g['kind'])\n\n // Skip rows that have an empty `parent menu` value; this can be used to omit graphs\n // from the product until they've been fully reviewed and approved\n if (!parentMenu) {\n context.log('info', `Skipping graph ${graphId} (${title})`)\n return undefined\n }\n\n // TODO: Check for a description\n // let desc: Description\n // if (descriptions) {\n // desc = descriptions.get(graphBaseId)\n // if (!desc) {\n // throw new Error(`Graph description for ${graphBaseId} not found in graphs.md`)\n // }\n // }\n\n // Helper that creates a string key prefix\n const key = (kind: string) => `graph_${graphBaseId.padStart(3, '0')}_${kind}`\n\n // Helper that creates a string context\n const strCtxt = (kind: string) => {\n const parent = htmlToUtf8(parentMenu).replace('&amp;', '&')\n const displayTitle = htmlToUtf8(menuTitle || title).replace('&amp;', '&')\n return `Graph ${kind}: ${parent} > ${displayTitle}`\n }\n\n const titleKey = strings.add(key('title'), title, layout, strCtxt('Title'))\n let menuTitleKey: StringKey\n if (menuTitle) {\n menuTitleKey = strings.add(key('menu_title'), menuTitle, layout, strCtxt('Menu Item'))\n }\n\n let miniTitleKey: StringKey\n if (miniTitle) {\n miniTitleKey = strings.add(key('mini_title'), miniTitle, layout, strCtxt('Title (for Mini View)'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'graph-descriptions')\n }\n\n // TODO: Validate kind?\n const kind: GraphKind = kindString\n\n // TODO: Validate graph side?\n const sideString = optionalString(g['side'])\n const side: GraphSide = sideString\n\n // Determine if this graph is associated with a particular unit system and\n // has an alternate version\n const unitsString = optionalString(g['units'])\n const altIdString = optionalString(g['alternate'])\n let unitSystem: UnitSystem\n let alternates: GraphAlternateSpec[]\n if (unitsString && altIdString) {\n if (unitsString === 'metric') {\n // This graph is metric with a U.S. alternate\n unitSystem = 'metric'\n alternates = [\n {\n id: altIdString,\n unitSystem: 'us'\n }\n ]\n } else if (unitsString === 'us') {\n // This graph is U.S. with a metric alternate\n unitSystem = 'us'\n alternates = [\n {\n id: altIdString,\n unitSystem: 'metric'\n }\n ]\n }\n }\n\n const xMin = optionalNumber(g['x axis min']) || context.graphDefaultMinTime\n const xMax = optionalNumber(g['x axis max']) || context.graphDefaultMaxTime\n const xAxisLabel = optionalString(g['x axis label'])\n let xAxisLabelKey: StringKey\n if (xAxisLabel) {\n xAxisLabelKey = strings.add(genStringKey('graph_xaxis_label', xAxisLabel), xAxisLabel, layout, 'Graph X-Axis Label')\n }\n\n const yMin = optionalNumber(g['y axis min']) || 0\n const yMax = optionalNumber(g['y axis max'])\n const ySoftMax = optionalNumber(g['y axis soft max'])\n const yFormat = optionalString(g['y axis format']) || '.0f'\n const yAxisLabel = optionalString(g['y axis label'])\n let yAxisLabelKey: StringKey\n if (yAxisLabel) {\n yAxisLabelKey = strings.add(genStringKey('graph_yaxis_label', yAxisLabel), yAxisLabel, layout, 'Graph Y-Axis Label')\n }\n\n const datasets: GraphDatasetSpec[] = []\n interface Overrides {\n sourceName?: string\n colorId?: string\n }\n function addDataset(index: number, overrides?: Overrides): void {\n const plotKey = (name: string) => `plot ${index} ${name}`\n const varName = g[plotKey('variable')]\n if (!varName) {\n return\n }\n\n const varId = sdeNameForVensimVarName(varName)\n const externalSourceName = overrides?.sourceName || optionalString(g[plotKey('source')])\n const datasetLabel = optionalString(g[plotKey('label')])\n let labelKey: StringKey\n if (datasetLabel) {\n labelKey = strings.add(\n genStringKey('graph_dataset_label', datasetLabel),\n datasetLabel,\n layout,\n 'Graph Dataset Label'\n )\n }\n\n const colorId = overrides?.colorId || requiredString(plotKey('color'))\n const hexColor = context.getHexColorForId(colorId)\n if (!hexColor) {\n throw new Error(`Graph ${graphId} references an unknown color ${colorId}`)\n }\n\n const lineStyleAndModString = optionalString(g[plotKey('style')]) || 'line'\n const lineStyleParts = lineStyleAndModString.split(';')\n const lineStyleString = lineStyleParts[0]\n const lineStyleModifierString = lineStyleParts.length > 1 ? lineStyleParts[1] : undefined\n\n // TODO: Validate line style and modifiers?\n const lineStyle: LineStyle = lineStyleString\n let lineStyleModifiers: ReadonlyArray<LineStyleModifier>\n if (lineStyleModifierString) {\n // TODO: For now, we assume at most one modifier; should change this to allow > 1\n lineStyleModifiers = [lineStyleModifierString]\n }\n\n if (externalSourceName && externalSourceName !== 'Ref') {\n // Add the variable to the set of vars to be included in the static data file\n context.addStaticVariable(externalSourceName, varName)\n } else {\n // Add the variable if this is a normal model output (in which case the source\n // name is undefined) or if it will be captured as \"Ref\" (baseline) values\n context.addOutputVariable(varName)\n }\n\n const datasetSpec: GraphDatasetSpec = {\n varId,\n varName,\n externalSourceName,\n labelKey,\n color: hexColor,\n lineStyle,\n lineStyleModifiers\n }\n datasets.push(datasetSpec)\n }\n\n // Add each dataset configured in graphs.csv\n for (let i = 1; i <= 11; i++) {\n addDataset(i)\n }\n\n // Only show legend items for datasets that have a label (i.e., ignore\n // some special ones, like the ones used to show dotted reference lines)\n const legendItems: GraphLegendItemSpec[] = datasets\n .filter(dataset => dataset.labelKey?.length > 0)\n .map(dataset => {\n return {\n color: dataset.color,\n labelKey: dataset.labelKey\n }\n })\n\n const graphSpec: GraphSpec = {\n id: graphId,\n kind,\n titleKey,\n miniTitleKey,\n menuTitleKey,\n descriptionKey,\n side,\n unitSystem,\n alternates,\n xMin,\n xMax,\n xAxisLabelKey,\n yMin,\n yMax,\n ySoftMax,\n yAxisLabelKey,\n yFormat,\n datasets,\n legendItems\n }\n\n // Add the graph to the menu\n // context.addGraphMenuItem(graphSpec, parentMenu)\n\n return graphSpec\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext, CsvRow } from './context'\nimport { optionalNumber, optionalString } from './read-config'\nimport type { InputId, InputSpec, SliderSpec, StringKey, SwitchSpec } from './spec-types'\nimport { genStringKey, htmlToUtf8 } from './strings'\nimport { sdeNameForVensimVarName } from './var-names'\n\n// TODO: For now, all strings use the same \"layout\" specifier; this could be customized\n// to provide a \"maximum length\" hint for a group of strings to the translation tool\nconst layout = 'default'\n\n/**\n * Convert the `config/inputs.csv` file to config specs that can be used in\n * the core package.\n */\nexport function generateInputsConfig(context: ConfigContext): Map<InputId, InputSpec> {\n // Convert `inputs.csv` to input specs\n const inputsCsv = context.readConfigCsvFile('inputs')\n const inputSpecs: Map<InputId, InputSpec> = new Map()\n for (const row of inputsCsv) {\n const spec = inputSpecFromCsv(row, context)\n if (spec) {\n inputSpecs.set(spec.id, spec)\n }\n }\n\n return inputSpecs\n}\n\nfunction inputSpecFromCsv(r: CsvRow, context: ConfigContext): InputSpec | undefined {\n const strings = context.strings\n\n function requiredString(key: string): string {\n const value = r[key]\n if (value === undefined || typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`Must specify '${key}' for input ${r.id}`)\n }\n return value\n }\n\n function requiredNumber(key: string): number {\n const stringValue = requiredString(key)\n const numValue = Number(stringValue)\n if (numValue === undefined) {\n throw new Error(`Must specify numeric '${key}' for input ${r.id}`)\n }\n return numValue\n }\n\n // Extract required fields\n const inputIdParts = requiredString('id').split(';')\n const inputId = inputIdParts[0]\n const viewId = optionalString(r['viewid'])\n const label = optionalString(r['label']) || ''\n const inputType = requiredString('input type')\n\n // Skip rows that have an empty `viewid` value; this can be used to omit inputs\n // from the product until they've been fully reviewed and approved\n if (!viewId) {\n context.log('info', `Skipping input ${inputId} (${label})`)\n return undefined\n }\n\n // Extract optional fields\n const description = optionalString(r['description'])\n\n // Helper that creates a string key prefix\n const key = (kind: string) => `input_${inputId.padStart(3, '0')}_${kind}`\n\n // For now, use the group name defined in `inputs.csv`\n const groupTitle = optionalString(r['group name'])\n if (!groupTitle) {\n throw new Error(`Must specify 'group name' for input ${inputId}`)\n }\n const groupTitleKey = genStringKey('input_group_title', groupTitle)\n strings.add(groupTitleKey, groupTitle, layout, 'Input Group Title')\n\n let typeLabel: string\n switch (inputType) {\n case 'slider':\n typeLabel = 'Slider'\n break\n case 'switch':\n typeLabel = 'Switch'\n break\n case 'checkbox':\n typeLabel = 'Checkbox'\n break\n case 'checkbox group':\n typeLabel = 'Checkbox Group'\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n // Helper that creates a string context\n const strCtxt = (kind: string) => {\n const labelText = htmlToUtf8(label).replace('&amp;', '&')\n return `${typeLabel} ${kind}: ${groupTitle} > ${labelText}`\n }\n\n const labelKey = strings.add(key('label'), label, layout, strCtxt('Label'))\n\n const listingLabel = optionalString(r['listing label'])\n let listingLabelKey: StringKey\n if (listingLabel) {\n listingLabelKey = strings.add(key('action_label'), listingLabel, layout, strCtxt('Action Label'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'input-descriptions')\n }\n\n // Converts a slider row in `inputs.csv` to a `SliderSpec`\n function sliderSpecFromCsv(): SliderSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const defaultValue = requiredNumber('slider/switch default')\n const minValue = requiredNumber('slider min')\n const maxValue = requiredNumber('slider max')\n const step = requiredNumber('slider step')\n const reversed = optionalString(r['reversed']) === 'yes'\n\n if (defaultValue < minValue || defaultValue > maxValue) {\n let e = `Default value for slider ${inputId} is out of range: `\n e += `default=${defaultValue} min=${minValue} max=${maxValue}`\n throw new Error(e)\n }\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n const format = optionalString(r['format']) || '.0f'\n\n const units = optionalString(r['units'])\n let unitsKey: StringKey\n if (units) {\n unitsKey = strings.add(genStringKey('input_units', units), units, layout, 'Slider Units')\n }\n\n const rangeInfo = getSliderRangeInfo(r, maxValue, context)\n const rangeLabelKeys = rangeInfo.labelKeys\n const rangeDividers = rangeInfo.dividers\n\n return {\n kind: 'slider',\n id: inputId,\n varId,\n varName,\n defaultValue,\n minValue,\n maxValue,\n step,\n reversed,\n labelKey,\n listingLabelKey,\n descriptionKey,\n unitsKey,\n rangeLabelKeys,\n rangeDividers,\n format\n }\n }\n\n // Converts a switch row in `inputs.csv` to a `SwitchSpec`\n function switchSpecFromCsv(): SwitchSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const onValue = requiredNumber('enabled value')\n const offValue = requiredNumber('disabled value')\n const defaultValue = requiredNumber('slider/switch default')\n if (defaultValue !== onValue && defaultValue !== offValue) {\n throw new Error(\n `Invalid default value for switch ${inputId}: off=${offValue} on=${onValue} default=${defaultValue}`\n )\n }\n\n const minValue = Math.min(offValue, onValue)\n const maxValue = Math.max(offValue, onValue)\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n // The `controlled input ids` field dictates which rows are active\n // when this switch is on or off. Examples of the format of this field:\n // 1;2;3|4;5;6\n // 1|2;3;4;5\n // |1\n // On the left side of the '|' are the rows that are active when the\n // switch is in an off position, and on the right side are the rows\n // that are active when the switch is in an on position. Usually the\n // \"active when off\" rows are above the switch in the UI, and the\n // \"active when on\" rows are below the switch.\n const controlledInputIds = requiredString('controlled input ids')\n const controlledParts = controlledInputIds.split('|')\n const rowsActiveWhenOff = controlledParts[0].split(';').filter(id => id.trim().length > 0)\n const rowsActiveWhenOn = controlledParts[1].split(';').filter(id => id.trim().length > 0)\n\n return {\n kind: 'switch',\n id: inputId,\n varId,\n varName,\n labelKey,\n listingLabelKey,\n descriptionKey,\n defaultValue,\n offValue,\n onValue,\n slidersActiveWhenOff: rowsActiveWhenOff,\n slidersActiveWhenOn: rowsActiveWhenOn\n }\n }\n\n // Call a different converter function depending on the input type\n let inputSpec: SliderSpec | SwitchSpec\n switch (inputType) {\n case 'slider': {\n inputSpec = sliderSpecFromCsv()\n break\n }\n case 'switch':\n case 'checkbox': {\n inputSpec = switchSpecFromCsv()\n break\n }\n case 'checkbox group':\n // TODO\n // XXX: For now, we specify the checkbox IDs as a semicolon separated list\n // in the \"varname\" cell\n // const checkboxIds = row['varname'].split(';')\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n return inputSpec\n}\n\ninterface SliderRangeInfo {\n labelKeys: StringKey[]\n dividers: number[]\n}\n\nfunction getSliderRangeInfo(r: CsvRow, maxValue: number, context: ConfigContext): SliderRangeInfo {\n const strings = context.strings\n const labelKeys: StringKey[] = []\n const dividers: number[] = []\n\n // Get all labels to determine the number of ranges\n let rangeNum = 1\n while (rangeNum <= 5) {\n const label = optionalString(r[`range ${rangeNum} label`])\n if (!label) {\n break\n }\n const labelKey = strings.add(genStringKey('input_range', label), label, layout, 'Slider Range Label')\n if (!labelKey) {\n break\n }\n labelKeys.push(labelKey)\n rangeNum++\n }\n\n // Find dividing points between ranges; the absence of a final dividing point\n // indicates the use of discrete values\n const numRanges = rangeNum - 1\n for (rangeNum = 2; rangeNum <= numRanges; rangeNum++) {\n let divider = optionalNumber(r[`range ${rangeNum} start`])\n if (divider === undefined) {\n // Fall back on the slider max value when a divider is missing\n divider = maxValue\n }\n dividers.push(divider)\n }\n\n return {\n labelKeys,\n dividers\n }\n}\n"],"mappings":"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","names":["import_fs","import_path","layout","sanitizeHtml","joinPath","parseCsv","layout","import_fs","import_path","layout","key","kind","key","resolvePath","joinPath"]}
1
+ {"version":3,"sources":["../src/index.ts","../../../node_modules/.pnpm/tsup@7.2.0_typescript@5.2.2/node_modules/tsup/assets/cjs_shims.js","../src/processor.ts","../src/context.ts","../src/strings.ts","../src/var-names.ts","../src/gen-model-spec.ts","../src/gen-config-specs.ts","../src/read-config.ts","../src/gen-graphs.ts","../src/gen-inputs.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport type { ConfigProcessorOptions, ConfigProcessorOutputPaths } from './processor'\nexport { configProcessor } from './processor'\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL('file:' + __filename).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport type { BuildContext, ModelSpec } from '@sdeverywhere/build'\n\nimport { createConfigContext } from './context'\nimport { writeModelSpec } from './gen-model-spec'\nimport { generateConfigSpecs, writeConfigSpecs, writeSpecTypes } from './gen-config-specs'\n\nexport interface ConfigProcessorOutputPaths {\n /** The absolute path to the directory where model spec files will be written. */\n modelSpecsDir?: string\n\n /** The absolute path to the directory where config spec files will be written. */\n configSpecsDir?: string\n\n /** The absolute path to the directory where translated strings will be written. */\n stringsDir?: string\n}\n\nexport interface ConfigProcessorOptions {\n /**\n * The absolute path to the directory containing the CSV config files.\n */\n config: string\n\n /**\n * Either a single path to a base output directory (in which case, the recommended\n * directory structure will be used) or a `ConfigProcessorOutputPaths` containing specific paths.\n * If a single string is provided, the following subdirectories will be used:\n * ```\n * <out-dir>/\n * src/\n * config/\n * generated/\n * model/\n * generated/\n * strings/\n * ```\n */\n out?: string | ConfigProcessorOutputPaths\n}\n\n/**\n * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere\n * `UserConfig`. The returned function:\n * - reads CSV files from a `config` directory\n * - writes JS files to the configured output directories\n * - returns a `ModelSpec` that guides the rest of the `sde` build process\n */\nexport function configProcessor(options: ConfigProcessorOptions): (buildContext: BuildContext) => Promise<ModelSpec> {\n return buildContext => {\n return processModelConfig(buildContext, options)\n }\n}\n\nasync function processModelConfig(buildContext: BuildContext, options: ConfigProcessorOptions): Promise<ModelSpec> {\n const t0 = performance.now()\n\n // Resolve source (config) directory\n if (!existsSync(options.config)) {\n throw new Error(`The provided config dir '${options.config}' does not exist`)\n }\n\n // Resolve output directories\n let outModelSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outModelSpecsDir = joinPath(options.out, 'src', 'model', 'generated')\n } else {\n outModelSpecsDir = options.out.modelSpecsDir\n }\n }\n\n let outConfigSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outConfigSpecsDir = joinPath(options.out, 'src', 'config', 'generated')\n } else {\n outConfigSpecsDir = options.out.configSpecsDir\n }\n }\n\n let outStringsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outStringsDir = joinPath(options.out, 'strings')\n } else {\n outStringsDir = options.out.stringsDir\n }\n }\n\n // Create a container for strings, variables, etc\n const context = createConfigContext(buildContext, options.config)\n\n // Write the generated files\n context.log('info', 'Generating files...')\n\n const configSpecs = generateConfigSpecs(context)\n if (outConfigSpecsDir) {\n context.log('verbose', ' Writing config specs')\n writeConfigSpecs(context, configSpecs, outConfigSpecsDir)\n writeSpecTypes(context, outConfigSpecsDir)\n }\n\n if (outModelSpecsDir) {\n context.log('verbose', ' Writing model specs')\n writeModelSpec(context, outModelSpecsDir)\n }\n\n if (outStringsDir) {\n context.log('verbose', ' Writing strings')\n context.writeStringsFiles(outStringsDir)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `Done generating files (${elapsed}s)`)\n\n return {\n inputs: context.getOrderedInputs(),\n outputs: context.getOrderedOutputs(),\n datFiles: context.datFiles\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { join as joinPath, relative } from 'path'\n\nimport { parse as parseCsv } from 'csv-parse/sync'\n\nimport type { BuildContext, InputSpec, LogLevel, OutputSpec } from '@sdeverywhere/build'\n\nimport type { HexColor } from './spec-types'\nimport { Strings } from './strings'\nimport type { InputVarId, OutputVarId } from './var-names'\nimport { sdeNameForVensimVarName } from './var-names'\n\nexport type CsvRow = { [key: string]: string }\nexport type ColorId = string\n\nexport class ConfigContext {\n private readonly inputSpecs: Map<InputVarId, InputSpec> = new Map()\n private readonly outputVarNames: Map<OutputVarId, string> = new Map()\n private readonly staticVarNames: Map<string, Set<string>> = new Map()\n\n constructor(\n private readonly buildContext: BuildContext,\n private readonly configDir: string,\n public readonly strings: Strings,\n private readonly colorMap: Map<ColorId, HexColor>,\n public readonly graphDefaultMinTime: number,\n public readonly graphDefaultMaxTime: number,\n public readonly datFiles: string[]\n ) {}\n\n /**\n * Read a CSV file of the given name from the config directory.\n *\n * @param name The base name of the CSV file.\n */\n readConfigCsvFile(name: string): CsvRow[] {\n return readConfigCsvFile(this.configDir, name)\n }\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n this.buildContext.log(level, msg)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.buildContext.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n addInputVariable(\n inputId: string,\n inputVarName: string,\n defaultValue: number,\n minValue: number,\n maxValue: number\n ): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(inputVarName)\n if (this.inputSpecs.get(varId)) {\n // Fail if the variable was already added (there should only be one spec\n // per input variable)\n console.error(`ERROR: Input variable ${inputVarName} was already added`)\n }\n this.inputSpecs.set(varId, {\n inputId,\n varName: inputVarName,\n defaultValue,\n minValue,\n maxValue\n })\n }\n\n addOutputVariable(outputVarName: string): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(outputVarName)\n this.outputVarNames.set(varId, outputVarName)\n }\n\n addStaticVariable(sourceName: string, varName: string): void {\n const sourceVarNames = this.staticVarNames.get(sourceName)\n if (sourceVarNames) {\n sourceVarNames.add(varName)\n } else {\n const varNames: Set<string> = new Set()\n varNames.add(varName)\n this.staticVarNames.set(sourceName, varNames)\n }\n }\n\n getHexColorForId(colorId: ColorId): HexColor {\n return this.colorMap.get(colorId)\n }\n\n getOrderedInputs(): InputSpec[] {\n // TODO: It would be nice to alphabetize the inputs, but currently we have\n // code that assumes that the InputSpecs in the map have the same order\n // as the variables in the spec file and model config, so preserve the\n // existing order here for now\n return Array.from(this.inputSpecs.values())\n }\n\n getOrderedOutputs(): OutputSpec[] {\n // Sort the output variable names alphabetically\n const alphabetical = (a: string, b: string) => (a > b ? 1 : b > a ? -1 : 0)\n const varNames = Array.from(this.outputVarNames.values()).sort(alphabetical)\n return varNames.map(varName => {\n return {\n varName\n }\n })\n }\n\n writeStringsFiles(dstDir: string): void {\n this.strings.writeJsFiles(this.buildContext, dstDir /*, xlatLangs*/)\n }\n}\n\nexport function createConfigContext(buildContext: BuildContext, configDir: string): ConfigContext {\n // Read basic model configuration from `model.csv`\n const modelCsv = readConfigCsvFile(configDir, 'model')[0]\n const graphDefaultMinTime = Number(modelCsv['graph default min time'])\n const graphDefaultMaxTime = Number(modelCsv['graph default max time'])\n const datFilesString = modelCsv['model dat files']\n const origDatFiles = datFilesString.length > 0 ? datFilesString.split(';') : []\n\n // The dat file paths in the config file are assumed to be relative to\n // the project directory (i.e., the directory where the `sde.config.js`\n // file resides), so we need to convert to paths that are relative to\n // the `sde-prep` directory (since that is the \"model\" directory from\n // the perspective of the compile package).\n const prepDir = buildContext.config.prepDir\n const projDir = buildContext.config.rootDir\n const datFiles = origDatFiles.map(f => joinPath(relative(prepDir, projDir), f))\n\n // Read the static strings from `strings.csv`\n const strings = readStringsCsv(configDir)\n\n // Read color configuration from `colors.csv`\n const colorsCsv = readConfigCsvFile(configDir, 'colors')\n const colors = new Map()\n for (const row of colorsCsv) {\n const colorId = row['id']\n const hexColor = row['hex code']\n colors.set(colorId, hexColor)\n }\n\n return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles)\n}\n\nfunction configFilePath(configDir: string, name: string, ext: string): string {\n return joinPath(configDir, `${name}.${ext}`)\n}\n\nfunction readCsvFile(path: string): CsvRow[] {\n const data = readFileSync(path, 'utf8')\n return parseCsv(data, {\n columns: true,\n trim: true,\n skip_empty_lines: true,\n skip_records_with_empty_values: true\n })\n}\n\nfunction readConfigCsvFile(configDir: string, name: string): CsvRow[] {\n return readCsvFile(configFilePath(configDir, name, 'csv'))\n}\n\n/**\n * Initialize a `Strings` instance with the core strings from `strings.csv`.\n */\nfunction readStringsCsv(configDir: string): Strings {\n const strings = new Strings()\n\n // TODO: For now we use the same \"layout\" and \"context\" for all core strings\n const layout = 'default'\n const context = 'Core'\n\n const rows = readConfigCsvFile(configDir, 'strings')\n for (const row of rows) {\n const key = row['id']\n let str = row['string']\n str = str ? str.trim() : ''\n if (str) {\n strings.add(key, str, layout, context)\n }\n }\n\n return strings\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport sanitizeHtml from 'sanitize-html'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type { StringKey } from './spec-types'\n\ninterface StringRecord {\n key: StringKey\n str: string\n layout: string\n context: string\n grouping: string\n appendedStringKeys?: string[]\n}\n\ntype LangCode = string\ntype StringMap = Map<StringKey, string>\ntype XlatMap = Map<LangCode, StringMap>\n\nexport class Strings {\n private readonly records: Map<StringKey, StringRecord> = new Map()\n\n add(\n key: StringKey,\n str: string,\n layout: string,\n context: string,\n grouping?: string,\n appendedStringKeys?: string[]\n ): StringKey {\n checkInvisibleCharacters(str || '')\n\n if (!key) {\n throw new Error(`Must provide a key for the string: ${str}`)\n }\n\n const validKey = /^[0-9a-z_]+$/.test(key)\n if (!validKey) {\n throw new Error(`String key contains undesirable characters: ${key}`)\n }\n\n if (!layout) {\n throw new Error(`Must provide a layout (e.g., 'layout1' or 'not-translated')`)\n }\n\n if (!context) {\n throw new Error(`Must provide a context string: key=${key}, string=${str}`)\n }\n\n if (context === 'Core') {\n // For core strings from the `strings.csv` file, leave the context empty for now\n // (indicating this is a \"general\" string with no particular context)\n context = undefined\n }\n\n if (!grouping) {\n // Use 'primary' if grouping is not specified\n grouping = 'primary'\n }\n\n // Convert some HTML tags and entities to UTF-8\n if (str) {\n str = str.trim()\n str = htmlToUtf8(str)\n }\n\n // If the trimmed string is empty, do not create a new key, and return an empty string\n if (!str) {\n return ''\n }\n\n if (this.records.has(key)) {\n // TODO: For now, allow certain keys to appear more than once; we only add the string once\n const prefix = key.substring(0, key.indexOf('__'))\n switch (prefix) {\n case 'graph_dataset_label':\n case 'graph_xaxis_label':\n case 'graph_yaxis_label':\n case 'input_group_title':\n case 'input_range':\n case 'input_units':\n break\n default:\n throw new Error(`More than one string with key=${key}`)\n }\n }\n\n // Add the string record\n this.records.set(key, {\n key,\n str,\n layout,\n context,\n grouping,\n appendedStringKeys\n })\n\n return key\n }\n\n /**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param xlatLangs The set of languages that are configured for translation.\n */\n writeJsFiles(context: BuildContext, dstDir: string /*, xlatLangs: Map<LangCode, LangSpec>*/): void {\n writeLangJsFiles(context, dstDir, this.records /*, xlatLangs*/)\n }\n}\n\nfunction getSortedRecords(records: Map<StringKey, StringRecord>): StringRecord[] {\n // Sort records by string key\n return Array.from(records.values()).sort((a, b) => {\n return a.key > b.key ? 1 : b.key > a.key ? -1 : 0\n })\n}\n\nfunction checkInvisibleCharacters(s: string): void {\n if (s.includes('\\u00a0')) {\n const e = s.replace(/\\u00a0/g, 'HERE')\n throw new Error(\n `String contains one or more non-breaking space characters (to fix, replace \"HERE\" with a normal space):\\n ${e}`\n )\n }\n}\n\nfunction utf8SubscriptToHtml(key: StringKey, s: string): string {\n if (key.includes('graph_yaxis_label')) {\n // Chart.js doesn't support using HTML tags like subscripts or superscripts\n // in axis labels. For now, we will convert subscript literals in axis labels\n // to a simple number. (We could preserve the subscript literal, but it doesn't\n // render all that well.)\n s = s.replace(/₂/gi, '2')\n s = s.replace(/₃/gi, '3')\n s = s.replace(/₄/gi, '4')\n s = s.replace(/₆/gi, '6')\n return s\n }\n\n // Unicode subscript literals render differently in some browsers (very low\n // in Safari for example), so we will replace them with HTML `sub` tags\n s = s.replace(/₂/gi, '<sub>2</sub>')\n s = s.replace(/₃/gi, '<sub>3</sub>')\n s = s.replace(/₄/gi, '<sub>4</sub>')\n s = s.replace(/₆/gi, '<sub>6</sub>')\n\n // If the subscript tag is followed by a space, that space needs to be\n // replaced with a non-breaking space, otherwise the whitespace will be lost\n s = s.replace(/<\\/sub> /gi, '</sub>&nbsp;')\n\n return s\n}\n\nfunction htmlSubscriptAndSuperscriptToUtf8(s: string): string {\n // Subscripts have a straight mapping in Unicode (U+208x)\n s = s.replace(/<sub>(\\d)<\\/sub>/gi, (_match, p1) => String.fromCharCode(0x2080 + Number(p1)))\n\n // Superscripts don't have a straight mapping, so it's easier to just\n // replace the ones we care about. There are some others (12, 18, -5)\n // that don't render well when replaced with their Unicode superscript\n // equivalents, so we will leave those with HTML `sup` tags.\n s = s.replace(/<sup>6<\\/sup>/gi, '\\u2076')\n s = s.replace(/<sup>9<\\/sup>/gi, '\\u2079')\n\n return s\n}\n\nexport function htmlToUtf8(orig: string): string {\n // Replace common HTML tags and entities with UTF-8 characters\n let s = orig\n\n // Convert `sub` and `sup` tags\n s = htmlSubscriptAndSuperscriptToUtf8(s)\n\n let clean = sanitizeHtml(s, {\n allowedTags: ['a', 'b', 'br', 'i', 'em', 'li', 'p', 'strong', 'sub', 'sup', 'ul'],\n allowedAttributes: {\n a: ['href', 'target', 'rel']\n }\n })\n\n // XXX: The `sanitize-html` package converts `&nbsp;` to the Unicode\n // equivalent (`U+00A0`); we will convert it back to `&nbsp;` to make\n // it more obvious and easier to view in a translation tool\n clean = clean.replace(/\\u00a0/gi, '&nbsp;')\n\n // if (clean !== s) {\n // console.log(`IN: ${orig}`)\n // console.log(`O1: ${s}`)\n // console.log(`O2: ${clean}\\n`)\n // }\n\n return clean\n}\n\n/**\n * Generate a string key for the given string by replacing special characters with\n * underscores and converting other characters to lowercase.\n */\nexport function genStringKey(prefix: string, s: string): string {\n checkInvisibleCharacters(s)\n\n let key = s.toLowerCase()\n key = key.replace(/ – /g, '_') // e.g. 'Data – Satellite' (with emdash) -> 'data_satellite'\n key = key.replace(/ \\/ /g, '_per_') // e.g. 'CO2 / TJ' -> 'co2_per_tj'\n key = key.replace(/ /g, '_')\n key = key.replace('₂', '2') // e.g. 'CO₂' -> 'co2'\n key = key.replace('<sub>2</sub>', '2') // e.g. 'CO<sub>2</sub>' -> 'co2'\n key = key.replace(/\\$\\//g, 'dollars_per_') // e.g. '$/year' -> 'dollars_per_year'\n key = key.replace(/%\\//g, 'pct_per_') // e.g. '%/year' -> 'pct_per_year'\n key = key.replace(/\\//g, '_per_') // e.g. 'CO2/TJ' -> 'co2_per_tj'\n key = key.replace(/º/g, 'degrees_') // e.g. 'ºC' -> 'degrees_c'\n key = key.replace(/\\*/g, '_') // e.g. 'CO2*year' -> 'co2_year'\n key = key.replace(/%/g, 'pct') // e.g. '%' -> 'pct'\n key = key.replace(/\\$/g, 'dollars') // e.g. '$' -> 'dollars'\n key = key.replace(/&/g, 'and') // e.g. 'Actions & Outcomes' -> 'actions_and_outcomes'\n key = key.replace(/\\//g, 'per') // e.g. 'Gigatons CO2/year' -> 'gigatons_co2_per_year'\n key = key.replace(/:/g, '') // e.g. 'Net:' -> 'net'\n key = key.replace(/\\./g, '') // e.g. 'U.S. Units' -> 'us_units'\n key = key.replace(/-/g, '_') // e.g. 'some-thing' -> 'some_thing'\n key = key.replace(/—/g, '_') // endash to underscore\n key = key.replace(/–/g, '_') // emdash to underscore\n key = key.replace(/,/g, '')\n key = key.replace(/\\(/g, '')\n key = key.replace(/\\)/g, '')\n key = key.replace(/\\\\n/g, '')\n key = key.replace(/<br>/g, '_')\n return `${prefix}__${key}`\n}\n\n/**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * These files are currently saved as plain JS (ES6) files. The only difference compared\n * to JSON files is these JS files start with `export default`, so converting to JSON is\n * as trivial as stripping those two words, if needed.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param records The string records.\n * //@param xlatLangs The set of languages that are configured for translation.\n */\nfunction writeLangJsFiles(\n context: BuildContext,\n dstDir: string,\n records: Map<StringKey, StringRecord>\n // xlatLangs: Map<LangCode, LangSpec>\n): void {\n const xlatMap: XlatMap = new Map()\n const sortedRecords = getSortedRecords(records)\n\n // const baseStringForKey = (key: StringKey) => {\n // const record = records.get(key)\n // if (!record) {\n // throw new Error(`No base string found for key=${key}`)\n // }\n // return record.str\n // }\n\n // Add base (e.g., English) strings that were gathered from the config files\n const enStrings: StringMap = new Map()\n for (const record of sortedRecords) {\n const s = record.str\n enStrings.set(record.key, utf8SubscriptToHtml(record.key, s))\n }\n // TODO: Don't assume English, make the base language configurable\n xlatMap.set('en', enStrings)\n\n // TODO: Enable support for translation files (for now, we only write base strings)\n // const hasSecondary =\n // existsSync(projectFilePath('localization', 'graph-descriptions')) &&\n // existsSync(projectFilePath('localization', 'input-descriptions'))\n // for (const lang of xlatLangs.keys()) {\n // const langStrings: StringMap = new Map()\n\n // let poMsgs: Map<string, string>\n // const primaryMsgs = readXlatPoFile('primary', lang)\n // if (hasSecondary) {\n // const graphMsgs = readXlatPoFile('graph-descriptions', lang)\n // const inputMsgs = readXlatPoFile('input-descriptions', lang)\n // poMsgs = new Map([...primaryMsgs, ...graphMsgs, ...inputMsgs])\n // } else {\n // poMsgs = primaryMsgs\n // }\n\n // const xlatStringForKey = (key: StringKey) => {\n // return poMsgs.get(key)\n // }\n\n // // Add the translation of each English string.\n // for (const record of sortedRecords) {\n // if (record.grouping !== 'primary') {\n // // Only include secondary strings (graph and input descriptions) if they are\n // // explicitly requested for this language; if not included, the English\n // // descriptions will be used as a fallback\n // if (xlatLangs.get(lang).includeSecondary !== true) {\n // continue\n // }\n // }\n\n // const xlatStr = xlatStringForKey(record.key)\n // if (xlatStr) {\n // // Add the translated string\n // const s = xlatStr\n // langStrings.set(record.key, utf8SubscriptToHtml(record.key, s))\n // } else {\n // // No translation for this string. We don't add the English string to\n // // map as a fallback. Instead, we configure the i18n library to use\n // // English strings as a fallback at runtime.\n // // console.warn(`WARNING: No translated string for lang=${lang} id=${stringObj.id}`)\n // }\n // }\n\n // xlatMap.set(lang, langStrings)\n // }\n\n // Write a JS file for each language for use in the core package\n for (const lang of xlatMap.keys()) {\n const stringsForLang = xlatMap.get(lang)\n const stringsObj = Object.fromEntries(stringsForLang)\n const json = JSON.stringify(stringsObj, null, 2)\n context.writeStagedFile('strings', dstDir, `${lang}.js`, `export default ${json}`)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport type InputVarId = string\nexport type OutputVarId = string\n\n/**\n * Helper function that converts a Vensim variable or subscript name\n * into a valid C identifier as used by SDE.\n * TODO: Import helper function from `sdeverywhere` package instead\n */\nfunction sdeNameForVensimName(name: string): string {\n return (\n '_' +\n name\n .trim()\n .replace(/\"/g, '_')\n .replace(/\\s+!$/g, '!')\n .replace(/\\s/g, '_')\n .replace(/,/g, '_')\n .replace(/-/g, '_')\n .replace(/\\./g, '_')\n .replace(/\\$/g, '_')\n .replace(/'/g, '_')\n .replace(/&/g, '_')\n .replace(/%/g, '_')\n .replace(/\\//g, '_')\n .replace(/\\|/g, '_')\n .toLowerCase()\n )\n}\n\n/**\n * Helper function that converts a Vensim variable name (possibly containing\n * subscripts) into a valid C identifier as used by SDE.\n * TODO: Import helper function from `sdeverywhere` package instead\n */\nexport function sdeNameForVensimVarName(varName: string): string {\n const m = varName.match(/([^[]+)(?:\\[([^\\]]+)\\])?/)\n if (!m) {\n throw new Error(`Invalid Vensim name: ${varName}`)\n }\n let id = sdeNameForVensimName(m[1])\n if (m[2]) {\n const subscripts = m[2].split(',').map(x => sdeNameForVensimName(x))\n id += `[${subscripts.join('][')}]`\n }\n\n return id\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext } from './context'\nimport { sdeNameForVensimVarName } from './var-names'\n\n/**\n * Write the `model-spec.ts` file used by the core package to initialize the model.\n */\nexport function writeModelSpec(context: ConfigContext, dstDir: string): void {\n // Create ordered arrays of inputs and outputs\n const inputVarIds = context.getOrderedInputs().map(i => sdeNameForVensimVarName(i.varName))\n const outputVarIds = context.getOrderedOutputs().map(o => sdeNameForVensimVarName(o.varName))\n\n // Generate the `model-spec.ts` file\n let tsContent = ''\n function emit(s: string): void {\n tsContent += s + '\\n'\n }\n\n emit('// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!')\n emit(`export const inputVarIds: string[] = ${JSON.stringify(inputVarIds, null, 2)}`)\n emit(`export const outputVarIds: string[] = ${JSON.stringify(outputVarIds, null, 2)}`)\n\n // Write the `model-spec.ts` file to the staged directory\n context.writeStagedFile('model', dstDir, 'model-spec.ts', tsContent)\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { dirname, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { ConfigContext } from './context'\nimport { generateGraphSpecs } from './gen-graphs'\nimport { generateInputsConfig } from './gen-inputs'\nimport type { GraphId, GraphSpec, InputId, InputSpec } from './spec-types'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport interface ConfigSpecs {\n graphSpecs: Map<GraphId, GraphSpec>\n inputSpecs: Map<InputId, InputSpec>\n}\n\n/**\n * Convert the CSV files in the `config` directory to config specs that can be\n * used in the core package.\n */\nexport function generateConfigSpecs(context: ConfigContext): ConfigSpecs {\n // Convert `graphs.csv` to graph specs\n context.log('verbose', ' Reading graph specs')\n const graphSpecs = generateGraphSpecs(context)\n\n // Convert `inputs.csv` to input specs\n context.log('verbose', ' Reading input specs')\n const inputSpecs = generateInputsConfig(context)\n\n // Include extra output variables that should be included in the generated\n // model even though they are not referenced in any graph specs\n context.log('verbose', ' Reading extra output variables')\n const extraOutputsCsv = context.readConfigCsvFile('outputs')\n for (const row of extraOutputsCsv) {\n const varName = row['variable name']\n if (varName) {\n context.addOutputVariable(varName)\n }\n }\n\n return {\n graphSpecs,\n inputSpecs\n }\n}\n\n/**\n * Write the `config-specs.ts` file to the given destination directory.\n */\nexport function writeConfigSpecs(context: ConfigContext, config: ConfigSpecs, dstDir: string): void {\n // Generate one big string containing the TypeScript source that will be\n // loaded by `config.ts` at runtime\n let tsContent = ''\n function emit(s: string): void {\n tsContent += s + '\\n'\n }\n\n emit('// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!')\n emit('')\n emit(`import type { GraphSpec, InputSpec } from './spec-types'`)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function emitArray(type: string, values: Iterable<any>): void {\n const varName = type.charAt(0).toLowerCase() + type.slice(1) + 's'\n const array = Array.from(values)\n const json = JSON.stringify(array, null, 2)\n emit('')\n emit(`export const ${varName}: ${type}[] = ${json}`)\n }\n\n emitArray('GraphSpec', config.graphSpecs.values())\n emitArray('InputSpec', config.inputSpecs.values())\n\n // Write the `config-specs.ts` file\n context.writeStagedFile('config', dstDir, 'config-specs.ts', tsContent)\n}\n\n/**\n * Write the `spec-types.ts` file to the given destination directory.\n */\nexport function writeSpecTypes(context: ConfigContext, dstDir: string): void {\n // Copy the `spec-types.ts` file. Currently we keep the full source of the file\n // in the `dist` directory for this package so that we can access it here.\n const tsFile = 'spec-types.ts'\n const tsPath = resolvePath(__dirname, tsFile)\n const tsContent = readFileSync(tsPath, 'utf8')\n context.writeStagedFile('config', dstDir, tsFile, tsContent)\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport function optionalString(stringValue?: string): string | undefined {\n if (stringValue !== undefined && stringValue.length > 0) {\n return stringValue\n } else {\n return undefined\n }\n}\n\nexport function optionalNumber(stringValue?: string): number | undefined {\n if (stringValue !== undefined && stringValue.length > 0) {\n return Number(stringValue)\n } else {\n return undefined\n }\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext, CsvRow } from './context'\nimport { optionalNumber, optionalString } from './read-config'\nimport type {\n GraphAlternateSpec,\n GraphDatasetSpec,\n GraphId,\n GraphKind,\n GraphLegendItemSpec,\n GraphSide,\n GraphSpec,\n LineStyle,\n LineStyleModifier,\n StringKey,\n UnitSystem\n} from './spec-types'\nimport { genStringKey, htmlToUtf8 } from './strings'\nimport { sdeNameForVensimVarName } from './var-names'\n\n/**\n * Convert the `config/graphs.csv` file to config specs that can be used in\n * the core package.\n */\nexport function generateGraphSpecs(context: ConfigContext): Map<GraphId, GraphSpec> {\n // TODO: Optionally read the graph descriptions from `graphs.md`\n // let descriptions: Map<GraphId, Description>\n // if (useDescriptions) {\n // descriptions = readGraphDescriptions(context)\n // } else {\n // descriptions = undefined\n // }\n\n // Convert `graphs.csv` to graph specs\n const graphsCsv = context.readConfigCsvFile('graphs')\n const graphSpecs: Map<GraphId, GraphSpec> = new Map()\n for (const row of graphsCsv) {\n const spec = graphSpecFromCsv(row, context)\n if (spec) {\n graphSpecs.set(spec.id, spec)\n }\n }\n\n return graphSpecs\n}\n\nfunction graphSpecFromCsv(g: CsvRow, context: ConfigContext): GraphSpec | undefined {\n const strings = context.strings\n\n // TODO: For now, all strings use the same \"layout\" specifier; this could be customized\n // to provide a \"maximum length\" hint for a group of strings to the translation tool\n const layout = 'default'\n\n function requiredString(key: string): string {\n const value = g[key]\n if (value === undefined || typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`Must specify '${key}' for graph ${g.id}`)\n }\n return value\n }\n\n // Extract required fields\n const graphIdParts = requiredString('id').split(';')\n const graphId = graphIdParts[0]\n const graphIdBaseParts = graphId.split('-')\n const graphBaseId = graphIdBaseParts[0]\n const title = requiredString('graph title')\n\n // Extract optional fields\n const menuTitle = optionalString(g['menu title'])\n const miniTitle = optionalString(g['mini title'])\n const parentMenu = optionalString(g['parent menu'])\n const description = optionalString(g['description'])\n const kindString = optionalString(g['kind'])\n\n // Skip rows that have an empty `parent menu` value; this can be used to omit graphs\n // from the product until they've been fully reviewed and approved\n if (!parentMenu) {\n context.log('info', `Skipping graph ${graphId} (${title})`)\n return undefined\n }\n\n // TODO: Check for a description\n // let desc: Description\n // if (descriptions) {\n // desc = descriptions.get(graphBaseId)\n // if (!desc) {\n // throw new Error(`Graph description for ${graphBaseId} not found in graphs.md`)\n // }\n // }\n\n // Helper that creates a string key prefix\n const key = (kind: string) => `graph_${graphBaseId.padStart(3, '0')}_${kind}`\n\n // Helper that creates a string context\n const strCtxt = (kind: string) => {\n const parent = htmlToUtf8(parentMenu).replace('&amp;', '&')\n const displayTitle = htmlToUtf8(menuTitle || title).replace('&amp;', '&')\n return `Graph ${kind}: ${parent} > ${displayTitle}`\n }\n\n const titleKey = strings.add(key('title'), title, layout, strCtxt('Title'))\n let menuTitleKey: StringKey\n if (menuTitle) {\n menuTitleKey = strings.add(key('menu_title'), menuTitle, layout, strCtxt('Menu Item'))\n }\n\n let miniTitleKey: StringKey\n if (miniTitle) {\n miniTitleKey = strings.add(key('mini_title'), miniTitle, layout, strCtxt('Title (for Mini View)'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'graph-descriptions')\n }\n\n // TODO: Validate kind?\n const kind: GraphKind = kindString\n\n // TODO: Validate graph side?\n const sideString = optionalString(g['side'])\n const side: GraphSide = sideString\n\n // Determine if this graph is associated with a particular unit system and\n // has an alternate version\n const unitsString = optionalString(g['units'])\n const altIdString = optionalString(g['alternate'])\n let unitSystem: UnitSystem\n let alternates: GraphAlternateSpec[]\n if (unitsString && altIdString) {\n if (unitsString === 'metric') {\n // This graph is metric with a U.S. alternate\n unitSystem = 'metric'\n alternates = [\n {\n id: altIdString,\n unitSystem: 'us'\n }\n ]\n } else if (unitsString === 'us') {\n // This graph is U.S. with a metric alternate\n unitSystem = 'us'\n alternates = [\n {\n id: altIdString,\n unitSystem: 'metric'\n }\n ]\n }\n }\n\n const xMin = optionalNumber(g['x axis min']) || context.graphDefaultMinTime\n const xMax = optionalNumber(g['x axis max']) || context.graphDefaultMaxTime\n const xAxisLabel = optionalString(g['x axis label'])\n let xAxisLabelKey: StringKey\n if (xAxisLabel) {\n xAxisLabelKey = strings.add(genStringKey('graph_xaxis_label', xAxisLabel), xAxisLabel, layout, 'Graph X-Axis Label')\n }\n\n const yMin = optionalNumber(g['y axis min']) || 0\n const yMax = optionalNumber(g['y axis max'])\n const ySoftMax = optionalNumber(g['y axis soft max'])\n const yFormat = optionalString(g['y axis format']) || '.0f'\n const yAxisLabel = optionalString(g['y axis label'])\n let yAxisLabelKey: StringKey\n if (yAxisLabel) {\n yAxisLabelKey = strings.add(genStringKey('graph_yaxis_label', yAxisLabel), yAxisLabel, layout, 'Graph Y-Axis Label')\n }\n\n const datasets: GraphDatasetSpec[] = []\n interface Overrides {\n sourceName?: string\n colorId?: string\n }\n function addDataset(index: number, overrides?: Overrides): void {\n const plotKey = (name: string) => `plot ${index} ${name}`\n const varName = g[plotKey('variable')]\n if (!varName) {\n return\n }\n\n const varId = sdeNameForVensimVarName(varName)\n const externalSourceName = overrides?.sourceName || optionalString(g[plotKey('source')])\n const datasetLabel = optionalString(g[plotKey('label')])\n let labelKey: StringKey\n if (datasetLabel) {\n labelKey = strings.add(\n genStringKey('graph_dataset_label', datasetLabel),\n datasetLabel,\n layout,\n 'Graph Dataset Label'\n )\n }\n\n const colorId = overrides?.colorId || requiredString(plotKey('color'))\n const hexColor = context.getHexColorForId(colorId)\n if (!hexColor) {\n throw new Error(`Graph ${graphId} references an unknown color ${colorId}`)\n }\n\n const lineStyleAndModString = optionalString(g[plotKey('style')]) || 'line'\n const lineStyleParts = lineStyleAndModString.split(';')\n const lineStyleString = lineStyleParts[0]\n const lineStyleModifierString = lineStyleParts.length > 1 ? lineStyleParts[1] : undefined\n\n // TODO: Validate line style and modifiers?\n const lineStyle: LineStyle = lineStyleString\n let lineStyleModifiers: ReadonlyArray<LineStyleModifier>\n if (lineStyleModifierString) {\n // TODO: For now, we assume at most one modifier; should change this to allow > 1\n lineStyleModifiers = [lineStyleModifierString]\n }\n\n if (externalSourceName && externalSourceName !== 'Ref') {\n // Add the variable to the set of vars to be included in the static data file\n context.addStaticVariable(externalSourceName, varName)\n } else {\n // Add the variable if this is a normal model output (in which case the source\n // name is undefined) or if it will be captured as \"Ref\" (baseline) values\n context.addOutputVariable(varName)\n }\n\n const datasetSpec: GraphDatasetSpec = {\n varId,\n varName,\n externalSourceName,\n labelKey,\n color: hexColor,\n lineStyle,\n lineStyleModifiers\n }\n datasets.push(datasetSpec)\n }\n\n // Add each dataset configured in graphs.csv\n for (let i = 1; i <= 11; i++) {\n addDataset(i)\n }\n\n // Only show legend items for datasets that have a label (i.e., ignore\n // some special ones, like the ones used to show dotted reference lines)\n const legendItems: GraphLegendItemSpec[] = datasets\n .filter(dataset => dataset.labelKey?.length > 0)\n .map(dataset => {\n return {\n color: dataset.color,\n labelKey: dataset.labelKey\n }\n })\n\n const graphSpec: GraphSpec = {\n id: graphId,\n kind,\n titleKey,\n miniTitleKey,\n menuTitleKey,\n descriptionKey,\n side,\n unitSystem,\n alternates,\n xMin,\n xMax,\n xAxisLabelKey,\n yMin,\n yMax,\n ySoftMax,\n yAxisLabelKey,\n yFormat,\n datasets,\n legendItems\n }\n\n // Add the graph to the menu\n // context.addGraphMenuItem(graphSpec, parentMenu)\n\n return graphSpec\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext, CsvRow } from './context'\nimport { optionalNumber, optionalString } from './read-config'\nimport type { InputId, InputSpec, SliderSpec, StringKey, SwitchSpec } from './spec-types'\nimport { genStringKey, htmlToUtf8 } from './strings'\nimport { sdeNameForVensimVarName } from './var-names'\n\n// TODO: For now, all strings use the same \"layout\" specifier; this could be customized\n// to provide a \"maximum length\" hint for a group of strings to the translation tool\nconst layout = 'default'\n\n/**\n * Convert the `config/inputs.csv` file to config specs that can be used in\n * the core package.\n */\nexport function generateInputsConfig(context: ConfigContext): Map<InputId, InputSpec> {\n // Convert `inputs.csv` to input specs\n const inputsCsv = context.readConfigCsvFile('inputs')\n const inputSpecs: Map<InputId, InputSpec> = new Map()\n for (const row of inputsCsv) {\n const spec = inputSpecFromCsv(row, context)\n if (spec) {\n inputSpecs.set(spec.id, spec)\n }\n }\n\n return inputSpecs\n}\n\nfunction inputSpecFromCsv(r: CsvRow, context: ConfigContext): InputSpec | undefined {\n const strings = context.strings\n\n function requiredString(key: string): string {\n const value = r[key]\n if (value === undefined || typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`Must specify '${key}' for input ${r.id}`)\n }\n return value\n }\n\n function requiredNumber(key: string): number {\n const stringValue = requiredString(key)\n const numValue = Number(stringValue)\n if (numValue === undefined) {\n throw new Error(`Must specify numeric '${key}' for input ${r.id}`)\n }\n return numValue\n }\n\n // Extract required fields\n const inputIdParts = requiredString('id').split(';')\n const inputId = inputIdParts[0]\n const viewId = optionalString(r['viewid'])\n const label = optionalString(r['label']) || ''\n const inputType = requiredString('input type')\n\n // Skip rows that have an empty `viewid` value; this can be used to omit inputs\n // from the product until they've been fully reviewed and approved\n if (!viewId) {\n context.log('info', `Skipping input ${inputId} (${label})`)\n return undefined\n }\n\n // Extract optional fields\n const description = optionalString(r['description'])\n\n // Helper that creates a string key prefix\n const key = (kind: string) => `input_${inputId.padStart(3, '0')}_${kind}`\n\n // For now, use the group name defined in `inputs.csv`\n const groupTitle = optionalString(r['group name'])\n if (!groupTitle) {\n throw new Error(`Must specify 'group name' for input ${inputId}`)\n }\n const groupTitleKey = genStringKey('input_group_title', groupTitle)\n strings.add(groupTitleKey, groupTitle, layout, 'Input Group Title')\n\n let typeLabel: string\n switch (inputType) {\n case 'slider':\n typeLabel = 'Slider'\n break\n case 'switch':\n typeLabel = 'Switch'\n break\n case 'checkbox':\n typeLabel = 'Checkbox'\n break\n case 'checkbox group':\n typeLabel = 'Checkbox Group'\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n // Helper that creates a string context\n const strCtxt = (kind: string) => {\n const labelText = htmlToUtf8(label).replace('&amp;', '&')\n return `${typeLabel} ${kind}: ${groupTitle} > ${labelText}`\n }\n\n const labelKey = strings.add(key('label'), label, layout, strCtxt('Label'))\n\n const listingLabel = optionalString(r['listing label'])\n let listingLabelKey: StringKey\n if (listingLabel) {\n listingLabelKey = strings.add(key('action_label'), listingLabel, layout, strCtxt('Action Label'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'input-descriptions')\n }\n\n // Converts a slider row in `inputs.csv` to a `SliderSpec`\n function sliderSpecFromCsv(): SliderSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const defaultValue = requiredNumber('slider/switch default')\n const minValue = requiredNumber('slider min')\n const maxValue = requiredNumber('slider max')\n const step = requiredNumber('slider step')\n const reversed = optionalString(r['reversed']) === 'yes'\n\n if (defaultValue < minValue || defaultValue > maxValue) {\n let e = `Default value for slider ${inputId} is out of range: `\n e += `default=${defaultValue} min=${minValue} max=${maxValue}`\n throw new Error(e)\n }\n context.addInputVariable(inputId, varName, defaultValue, minValue, maxValue)\n\n const format = optionalString(r['format']) || '.0f'\n\n const units = optionalString(r['units'])\n let unitsKey: StringKey\n if (units) {\n unitsKey = strings.add(genStringKey('input_units', units), units, layout, 'Slider Units')\n }\n\n const rangeInfo = getSliderRangeInfo(r, maxValue, context)\n const rangeLabelKeys = rangeInfo.labelKeys\n const rangeDividers = rangeInfo.dividers\n\n return {\n kind: 'slider',\n id: inputId,\n varId,\n varName,\n defaultValue,\n minValue,\n maxValue,\n step,\n reversed,\n labelKey,\n listingLabelKey,\n descriptionKey,\n unitsKey,\n rangeLabelKeys,\n rangeDividers,\n format\n }\n }\n\n // Converts a switch row in `inputs.csv` to a `SwitchSpec`\n function switchSpecFromCsv(): SwitchSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const onValue = requiredNumber('enabled value')\n const offValue = requiredNumber('disabled value')\n const defaultValue = requiredNumber('slider/switch default')\n if (defaultValue !== onValue && defaultValue !== offValue) {\n throw new Error(\n `Invalid default value for switch ${inputId}: off=${offValue} on=${onValue} default=${defaultValue}`\n )\n }\n\n const minValue = Math.min(offValue, onValue)\n const maxValue = Math.max(offValue, onValue)\n context.addInputVariable(inputId, varName, defaultValue, minValue, maxValue)\n\n // The `controlled input ids` field dictates which rows are active\n // when this switch is on or off. Examples of the format of this field:\n // 1;2;3|4;5;6\n // 1|2;3;4;5\n // |1\n // On the left side of the '|' are the rows that are active when the\n // switch is in an off position, and on the right side are the rows\n // that are active when the switch is in an on position. Usually the\n // \"active when off\" rows are above the switch in the UI, and the\n // \"active when on\" rows are below the switch.\n const controlledInputIds = requiredString('controlled input ids')\n const controlledParts = controlledInputIds.split('|')\n const rowsActiveWhenOff = controlledParts[0].split(';').filter(id => id.trim().length > 0)\n const rowsActiveWhenOn = controlledParts[1].split(';').filter(id => id.trim().length > 0)\n\n return {\n kind: 'switch',\n id: inputId,\n varId,\n varName,\n labelKey,\n listingLabelKey,\n descriptionKey,\n defaultValue,\n offValue,\n onValue,\n slidersActiveWhenOff: rowsActiveWhenOff,\n slidersActiveWhenOn: rowsActiveWhenOn\n }\n }\n\n // Call a different converter function depending on the input type\n let inputSpec: SliderSpec | SwitchSpec\n switch (inputType) {\n case 'slider': {\n inputSpec = sliderSpecFromCsv()\n break\n }\n case 'switch':\n case 'checkbox': {\n inputSpec = switchSpecFromCsv()\n break\n }\n case 'checkbox group':\n // TODO\n // XXX: For now, we specify the checkbox IDs as a semicolon separated list\n // in the \"varname\" cell\n // const checkboxIds = row['varname'].split(';')\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n return inputSpec\n}\n\ninterface SliderRangeInfo {\n labelKeys: StringKey[]\n dividers: number[]\n}\n\nfunction getSliderRangeInfo(r: CsvRow, maxValue: number, context: ConfigContext): SliderRangeInfo {\n const strings = context.strings\n const labelKeys: StringKey[] = []\n const dividers: number[] = []\n\n // Get all labels to determine the number of ranges\n let rangeNum = 1\n while (rangeNum <= 5) {\n const label = optionalString(r[`range ${rangeNum} label`])\n if (!label) {\n break\n }\n const labelKey = strings.add(genStringKey('input_range', label), label, layout, 'Slider Range Label')\n if (!labelKey) {\n break\n }\n labelKeys.push(labelKey)\n rangeNum++\n }\n\n // Find dividing points between ranges; the absence of a final dividing point\n // indicates the use of discrete values\n const numRanges = rangeNum - 1\n for (rangeNum = 2; rangeNum <= numRanges; rangeNum++) {\n let divider = optionalNumber(r[`range ${rangeNum} start`])\n if (divider === undefined) {\n // Fall back on the slider max value when a divider is missing\n divider = maxValue\n }\n dividers.push(divider)\n }\n\n return {\n labelKeys,\n dividers\n }\n}\n"],"mappings":"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","names":["import_fs","import_path","layout","sanitizeHtml","joinPath","parseCsv","layout","import_fs","import_path","layout","key","kind","key","resolvePath","joinPath"]}
@@ -0,0 +1,41 @@
1
+ import { BuildContext, ModelSpec } from '@sdeverywhere/build';
2
+
3
+ interface ConfigProcessorOutputPaths {
4
+ /** The absolute path to the directory where model spec files will be written. */
5
+ modelSpecsDir?: string;
6
+ /** The absolute path to the directory where config spec files will be written. */
7
+ configSpecsDir?: string;
8
+ /** The absolute path to the directory where translated strings will be written. */
9
+ stringsDir?: string;
10
+ }
11
+ interface ConfigProcessorOptions {
12
+ /**
13
+ * The absolute path to the directory containing the CSV config files.
14
+ */
15
+ config: string;
16
+ /**
17
+ * Either a single path to a base output directory (in which case, the recommended
18
+ * directory structure will be used) or a `ConfigProcessorOutputPaths` containing specific paths.
19
+ * If a single string is provided, the following subdirectories will be used:
20
+ * ```
21
+ * <out-dir>/
22
+ * src/
23
+ * config/
24
+ * generated/
25
+ * model/
26
+ * generated/
27
+ * strings/
28
+ * ```
29
+ */
30
+ out?: string | ConfigProcessorOutputPaths;
31
+ }
32
+ /**
33
+ * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere
34
+ * `UserConfig`. The returned function:
35
+ * - reads CSV files from a `config` directory
36
+ * - writes JS files to the configured output directories
37
+ * - returns a `ModelSpec` that guides the rest of the `sde` build process
38
+ */
39
+ declare function configProcessor(options: ConfigProcessorOptions): (buildContext: BuildContext) => Promise<ModelSpec>;
40
+
41
+ export { ConfigProcessorOptions, ConfigProcessorOutputPaths, configProcessor };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { BuildContext, ModelSpec } from '@sdeverywhere/build';
2
2
 
3
- interface ConfigOutputPaths {
3
+ interface ConfigProcessorOutputPaths {
4
4
  /** The absolute path to the directory where model spec files will be written. */
5
5
  modelSpecsDir?: string;
6
6
  /** The absolute path to the directory where config spec files will be written. */
@@ -8,15 +8,16 @@ interface ConfigOutputPaths {
8
8
  /** The absolute path to the directory where translated strings will be written. */
9
9
  stringsDir?: string;
10
10
  }
11
- interface ConfigOptions {
11
+ interface ConfigProcessorOptions {
12
12
  /**
13
13
  * The absolute path to the directory containing the CSV config files.
14
14
  */
15
15
  config: string;
16
16
  /**
17
17
  * Either a single path to a base output directory (in which case, the recommended
18
- * directory structure will be used) or a `ConfigOutputPaths` containing specific paths.
18
+ * directory structure will be used) or a `ConfigProcessorOutputPaths` containing specific paths.
19
19
  * If a single string is provided, the following subdirectories will be used:
20
+ * ```
20
21
  * <out-dir>/
21
22
  * src/
22
23
  * config/
@@ -24,8 +25,9 @@ interface ConfigOptions {
24
25
  * model/
25
26
  * generated/
26
27
  * strings/
28
+ * ```
27
29
  */
28
- out?: string | ConfigOutputPaths;
30
+ out?: string | ConfigProcessorOutputPaths;
29
31
  }
30
32
  /**
31
33
  * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere
@@ -34,6 +36,6 @@ interface ConfigOptions {
34
36
  * - writes JS files to the configured output directories
35
37
  * - returns a `ModelSpec` that guides the rest of the `sde` build process
36
38
  */
37
- declare function configProcessor(options: ConfigOptions): (buildContext: BuildContext) => Promise<ModelSpec>;
39
+ declare function configProcessor(options: ConfigProcessorOptions): (buildContext: BuildContext) => Promise<ModelSpec>;
38
40
 
39
- export { configProcessor };
41
+ export { ConfigProcessorOptions, ConfigProcessorOutputPaths, configProcessor };
package/dist/index.js CHANGED
@@ -65,8 +65,20 @@ var Strings = class {
65
65
  });
66
66
  return key;
67
67
  }
68
+ /**
69
+ * Write a `<lang>.js` file containing translated strings for each supported language.
70
+ *
71
+ * @param context The build context.
72
+ * @param dstDir The `strings` directory in the core package.
73
+ * @param xlatLangs The set of languages that are configured for translation.
74
+ */
68
75
  writeJsFiles(context, dstDir) {
69
- writeLangJsFiles(context, dstDir, this.records);
76
+ writeLangJsFiles(
77
+ context,
78
+ dstDir,
79
+ this.records
80
+ /*, xlatLangs*/
81
+ );
70
82
  }
71
83
  };
72
84
  function getSortedRecords(records) {
@@ -193,21 +205,49 @@ var ConfigContext = class {
193
205
  this.outputVarNames = /* @__PURE__ */ new Map();
194
206
  this.staticVarNames = /* @__PURE__ */ new Map();
195
207
  }
208
+ /**
209
+ * Read a CSV file of the given name from the config directory.
210
+ *
211
+ * @param name The base name of the CSV file.
212
+ */
196
213
  readConfigCsvFile(name) {
197
214
  return readConfigCsvFile(this.configDir, name);
198
215
  }
216
+ /**
217
+ * Log a message to the console and/or the in-browser overlay panel.
218
+ *
219
+ * @param level The log level (verbose, info, error).
220
+ * @param msg The message.
221
+ */
199
222
  log(level, msg) {
200
223
  this.buildContext.log(level, msg);
201
224
  }
225
+ /**
226
+ * Write a file to the staged directory.
227
+ *
228
+ * This file will be copied (along with other staged files) into the destination
229
+ * directory only after the build process has completed. Copying all staged files
230
+ * at once helps improve the local development experience by making it so that
231
+ * live reloading tools only need to refresh once instead of every time a build
232
+ * file is written.
233
+ *
234
+ * @param srcDir The directory underneath the configured `staged` directory where
235
+ * the file will be written (this must be a relative path).
236
+ * @param dstDir The absolute path to the destination directory where the staged
237
+ * file will be copied when the build has completed.
238
+ * @param filename The name of the file.
239
+ * @param content The file content.
240
+ */
202
241
  writeStagedFile(srcDir, dstDir, filename, content) {
203
242
  this.buildContext.writeStagedFile(srcDir, dstDir, filename, content);
204
243
  }
205
- addInputVariable(inputVarName, defaultValue, minValue, maxValue) {
244
+ addInputVariable(inputId, inputVarName, defaultValue, minValue, maxValue) {
206
245
  const varId = sdeNameForVensimVarName(inputVarName);
207
246
  if (this.inputSpecs.get(varId)) {
208
247
  console.error(`ERROR: Input variable ${inputVarName} was already added`);
209
248
  }
210
249
  this.inputSpecs.set(varId, {
250
+ inputId,
211
251
  varName: inputVarName,
212
252
  defaultValue,
213
253
  minValue,
@@ -244,7 +284,11 @@ var ConfigContext = class {
244
284
  });
245
285
  }
246
286
  writeStringsFiles(dstDir) {
247
- this.strings.writeJsFiles(this.buildContext, dstDir);
287
+ this.strings.writeJsFiles(
288
+ this.buildContext,
289
+ dstDir
290
+ /*, xlatLangs*/
291
+ );
248
292
  }
249
293
  };
250
294
  function createConfigContext(buildContext, configDir) {
@@ -437,7 +481,7 @@ function graphSpecFromCsv(g, context) {
437
481
  return;
438
482
  }
439
483
  const varId = sdeNameForVensimVarName(varName);
440
- const externalSourceName = (overrides == null ? void 0 : overrides.sourceName) || optionalString(g[plotKey("source")]);
484
+ const externalSourceName = overrides?.sourceName || optionalString(g[plotKey("source")]);
441
485
  const datasetLabel = optionalString(g[plotKey("label")]);
442
486
  let labelKey;
443
487
  if (datasetLabel) {
@@ -448,7 +492,7 @@ function graphSpecFromCsv(g, context) {
448
492
  "Graph Dataset Label"
449
493
  );
450
494
  }
451
- const colorId = (overrides == null ? void 0 : overrides.colorId) || requiredString(plotKey("color"));
495
+ const colorId = overrides?.colorId || requiredString(plotKey("color"));
452
496
  const hexColor = context.getHexColorForId(colorId);
453
497
  if (!hexColor) {
454
498
  throw new Error(`Graph ${graphId} references an unknown color ${colorId}`);
@@ -481,10 +525,7 @@ function graphSpecFromCsv(g, context) {
481
525
  for (let i = 1; i <= 11; i++) {
482
526
  addDataset(i);
483
527
  }
484
- const legendItems = datasets.filter((dataset) => {
485
- var _a;
486
- return ((_a = dataset.labelKey) == null ? void 0 : _a.length) > 0;
487
- }).map((dataset) => {
528
+ const legendItems = datasets.filter((dataset) => dataset.labelKey?.length > 0).map((dataset) => {
488
529
  return {
489
530
  color: dataset.color,
490
531
  labelKey: dataset.labelKey
@@ -605,7 +646,7 @@ function inputSpecFromCsv(r, context) {
605
646
  e += `default=${defaultValue} min=${minValue} max=${maxValue}`;
606
647
  throw new Error(e);
607
648
  }
608
- context.addInputVariable(varName, defaultValue, minValue, maxValue);
649
+ context.addInputVariable(inputId, varName, defaultValue, minValue, maxValue);
609
650
  const format = optionalString(r["format"]) || ".0f";
610
651
  const units = optionalString(r["units"]);
611
652
  let unitsKey;
@@ -647,7 +688,7 @@ function inputSpecFromCsv(r, context) {
647
688
  }
648
689
  const minValue = Math.min(offValue, onValue);
649
690
  const maxValue = Math.max(offValue, onValue);
650
- context.addInputVariable(varName, defaultValue, minValue, maxValue);
691
+ context.addInputVariable(inputId, varName, defaultValue, minValue, maxValue);
651
692
  const controlledInputIds = requiredString("controlled input ids");
652
693
  const controlledParts = controlledInputIds.split("|");
653
694
  const rowsActiveWhenOff = controlledParts[0].split(";").filter((id) => id.trim().length > 0);
@@ -717,7 +758,7 @@ function getSliderRangeInfo(r, maxValue, context) {
717
758
  }
718
759
 
719
760
  // src/gen-config-specs.ts
720
- var __dirname = dirname(fileURLToPath(import.meta.url));
761
+ var __dirname2 = dirname(fileURLToPath(import.meta.url));
721
762
  function generateConfigSpecs(context) {
722
763
  context.log("verbose", " Reading graph specs");
723
764
  const graphSpecs = generateGraphSpecs(context);
@@ -757,7 +798,7 @@ function writeConfigSpecs(context, config, dstDir) {
757
798
  }
758
799
  function writeSpecTypes(context, dstDir) {
759
800
  const tsFile = "spec-types.ts";
760
- const tsPath = resolvePath(__dirname, tsFile);
801
+ const tsPath = resolvePath(__dirname2, tsFile);
761
802
  const tsContent = readFileSync2(tsPath, "utf8");
762
803
  context.writeStagedFile("config", dstDir, tsFile, tsContent);
763
804
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/processor.ts","../src/context.ts","../src/strings.ts","../src/var-names.ts","../src/gen-model-spec.ts","../src/gen-config-specs.ts","../src/read-config.ts","../src/gen-graphs.ts","../src/gen-inputs.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport type { BuildContext, ModelSpec } from '@sdeverywhere/build'\n\nimport { createConfigContext } from './context'\nimport { writeModelSpec } from './gen-model-spec'\nimport { generateConfigSpecs, writeConfigSpecs, writeSpecTypes } from './gen-config-specs'\n\nexport interface ConfigOutputPaths {\n /** The absolute path to the directory where model spec files will be written. */\n modelSpecsDir?: string\n\n /** The absolute path to the directory where config spec files will be written. */\n configSpecsDir?: string\n\n /** The absolute path to the directory where translated strings will be written. */\n stringsDir?: string\n}\n\nexport interface ConfigOptions {\n /**\n * The absolute path to the directory containing the CSV config files.\n */\n config: string\n\n /**\n * Either a single path to a base output directory (in which case, the recommended\n * directory structure will be used) or a `ConfigOutputPaths` containing specific paths.\n * If a single string is provided, the following subdirectories will be used:\n * <out-dir>/\n * src/\n * config/\n * generated/\n * model/\n * generated/\n * strings/\n */\n out?: string | ConfigOutputPaths\n}\n\n/**\n * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere\n * `UserConfig`. The returned function:\n * - reads CSV files from a `config` directory\n * - writes JS files to the configured output directories\n * - returns a `ModelSpec` that guides the rest of the `sde` build process\n */\nexport function configProcessor(options: ConfigOptions): (buildContext: BuildContext) => Promise<ModelSpec> {\n return buildContext => {\n return processModelConfig(buildContext, options)\n }\n}\n\nasync function processModelConfig(buildContext: BuildContext, options: ConfigOptions): Promise<ModelSpec> {\n const t0 = performance.now()\n\n // Resolve source (config) directory\n if (!existsSync(options.config)) {\n throw new Error(`The provided config dir '${options.config}' does not exist`)\n }\n\n // Resolve output directories\n let outModelSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outModelSpecsDir = joinPath(options.out, 'src', 'model', 'generated')\n } else {\n outModelSpecsDir = options.out.modelSpecsDir\n }\n }\n\n let outConfigSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outConfigSpecsDir = joinPath(options.out, 'src', 'config', 'generated')\n } else {\n outConfigSpecsDir = options.out.configSpecsDir\n }\n }\n\n let outStringsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outStringsDir = joinPath(options.out, 'strings')\n } else {\n outStringsDir = options.out.stringsDir\n }\n }\n\n // Create a container for strings, variables, etc\n const context = createConfigContext(buildContext, options.config)\n\n // Write the generated files\n context.log('info', 'Generating files...')\n\n const configSpecs = generateConfigSpecs(context)\n if (outConfigSpecsDir) {\n context.log('verbose', ' Writing config specs')\n writeConfigSpecs(context, configSpecs, outConfigSpecsDir)\n writeSpecTypes(context, outConfigSpecsDir)\n }\n\n if (outModelSpecsDir) {\n context.log('verbose', ' Writing model specs')\n writeModelSpec(context, outModelSpecsDir)\n }\n\n if (outStringsDir) {\n context.log('verbose', ' Writing strings')\n context.writeStringsFiles(outStringsDir)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `Done generating files (${elapsed}s)`)\n\n return {\n inputs: context.getOrderedInputs(),\n outputs: context.getOrderedOutputs(),\n datFiles: context.datFiles\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { join as joinPath, relative } from 'path'\n\nimport { parse as parseCsv } from 'csv-parse/sync'\n\nimport type { BuildContext, InputSpec, LogLevel, OutputSpec } from '@sdeverywhere/build'\n\nimport type { HexColor } from './spec-types'\nimport { Strings } from './strings'\nimport type { InputVarId, OutputVarId } from './var-names'\nimport { sdeNameForVensimVarName } from './var-names'\n\nexport type CsvRow = { [key: string]: string }\nexport type ColorId = string\n\nexport class ConfigContext {\n private readonly inputSpecs: Map<InputVarId, InputSpec> = new Map()\n private readonly outputVarNames: Map<OutputVarId, string> = new Map()\n private readonly staticVarNames: Map<string, Set<string>> = new Map()\n\n constructor(\n private readonly buildContext: BuildContext,\n private readonly configDir: string,\n public readonly strings: Strings,\n private readonly colorMap: Map<ColorId, HexColor>,\n public readonly graphDefaultMinTime: number,\n public readonly graphDefaultMaxTime: number,\n public readonly datFiles: string[]\n ) {}\n\n /**\n * Read a CSV file of the given name from the config directory.\n *\n * @param name The base name of the CSV file.\n */\n readConfigCsvFile(name: string): CsvRow[] {\n return readConfigCsvFile(this.configDir, name)\n }\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n this.buildContext.log(level, msg)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.buildContext.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n addInputVariable(inputVarName: string, defaultValue: number, minValue: number, maxValue: number): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(inputVarName)\n if (this.inputSpecs.get(varId)) {\n // Fail if the variable was already added (there should only be one spec\n // per input variable)\n console.error(`ERROR: Input variable ${inputVarName} was already added`)\n }\n this.inputSpecs.set(varId, {\n varName: inputVarName,\n defaultValue,\n minValue,\n maxValue\n })\n }\n\n addOutputVariable(outputVarName: string): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(outputVarName)\n this.outputVarNames.set(varId, outputVarName)\n }\n\n addStaticVariable(sourceName: string, varName: string): void {\n const sourceVarNames = this.staticVarNames.get(sourceName)\n if (sourceVarNames) {\n sourceVarNames.add(varName)\n } else {\n const varNames: Set<string> = new Set()\n varNames.add(varName)\n this.staticVarNames.set(sourceName, varNames)\n }\n }\n\n getHexColorForId(colorId: ColorId): HexColor {\n return this.colorMap.get(colorId)\n }\n\n getOrderedInputs(): InputSpec[] {\n // TODO: It would be nice to alphabetize the inputs, but currently we have\n // code that assumes that the InputSpecs in the map have the same order\n // as the variables in the spec file and model config, so preserve the\n // existing order here for now\n return Array.from(this.inputSpecs.values())\n }\n\n getOrderedOutputs(): OutputSpec[] {\n // Sort the output variable names alphabetically\n const alphabetical = (a: string, b: string) => (a > b ? 1 : b > a ? -1 : 0)\n const varNames = Array.from(this.outputVarNames.values()).sort(alphabetical)\n return varNames.map(varName => {\n return {\n varName\n }\n })\n }\n\n writeStringsFiles(dstDir: string): void {\n this.strings.writeJsFiles(this.buildContext, dstDir /*, xlatLangs*/)\n }\n}\n\nexport function createConfigContext(buildContext: BuildContext, configDir: string): ConfigContext {\n // Read basic model configuration from `model.csv`\n const modelCsv = readConfigCsvFile(configDir, 'model')[0]\n const graphDefaultMinTime = Number(modelCsv['graph default min time'])\n const graphDefaultMaxTime = Number(modelCsv['graph default max time'])\n const datFilesString = modelCsv['model dat files']\n const origDatFiles = datFilesString.length > 0 ? datFilesString.split(';') : []\n\n // The dat file paths in the config file are assumed to be relative to\n // the project directory (i.e., the directory where the `sde.config.js`\n // file resides), so we need to convert to paths that are relative to\n // the `sde-prep` directory (since that is the \"model\" directory from\n // the perspective of the compile package).\n const prepDir = buildContext.config.prepDir\n const projDir = buildContext.config.rootDir\n const datFiles = origDatFiles.map(f => joinPath(relative(prepDir, projDir), f))\n\n // Read the static strings from `strings.csv`\n const strings = readStringsCsv(configDir)\n\n // Read color configuration from `colors.csv`\n const colorsCsv = readConfigCsvFile(configDir, 'colors')\n const colors = new Map()\n for (const row of colorsCsv) {\n const colorId = row['id']\n const hexColor = row['hex code']\n colors.set(colorId, hexColor)\n }\n\n return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles)\n}\n\nfunction configFilePath(configDir: string, name: string, ext: string): string {\n return joinPath(configDir, `${name}.${ext}`)\n}\n\nfunction readCsvFile(path: string): CsvRow[] {\n const data = readFileSync(path, 'utf8')\n return parseCsv(data, {\n columns: true,\n trim: true,\n skip_empty_lines: true,\n skip_records_with_empty_values: true\n })\n}\n\nfunction readConfigCsvFile(configDir: string, name: string): CsvRow[] {\n return readCsvFile(configFilePath(configDir, name, 'csv'))\n}\n\n/**\n * Initialize a `Strings` instance with the core strings from `strings.csv`.\n */\nfunction readStringsCsv(configDir: string): Strings {\n const strings = new Strings()\n\n // TODO: For now we use the same \"layout\" and \"context\" for all core strings\n const layout = 'default'\n const context = 'Core'\n\n const rows = readConfigCsvFile(configDir, 'strings')\n for (const row of rows) {\n const key = row['id']\n let str = row['string']\n str = str ? str.trim() : ''\n if (str) {\n strings.add(key, str, layout, context)\n }\n }\n\n return strings\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport sanitizeHtml from 'sanitize-html'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type { StringKey } from './spec-types'\n\ninterface StringRecord {\n key: StringKey\n str: string\n layout: string\n context: string\n grouping: string\n appendedStringKeys?: string[]\n}\n\ntype LangCode = string\ntype StringMap = Map<StringKey, string>\ntype XlatMap = Map<LangCode, StringMap>\n\nexport class Strings {\n private readonly records: Map<StringKey, StringRecord> = new Map()\n\n add(\n key: StringKey,\n str: string,\n layout: string,\n context: string,\n grouping?: string,\n appendedStringKeys?: string[]\n ): StringKey {\n checkInvisibleCharacters(str || '')\n\n if (!key) {\n throw new Error(`Must provide a key for the string: ${str}`)\n }\n\n const validKey = /^[0-9a-z_]+$/.test(key)\n if (!validKey) {\n throw new Error(`String key contains undesirable characters: ${key}`)\n }\n\n if (!layout) {\n throw new Error(`Must provide a layout (e.g., 'layout1' or 'not-translated')`)\n }\n\n if (!context) {\n throw new Error(`Must provide a context string: key=${key}, string=${str}`)\n }\n\n if (context === 'Core') {\n // For core strings from the `strings.csv` file, leave the context empty for now\n // (indicating this is a \"general\" string with no particular context)\n context = undefined\n }\n\n if (!grouping) {\n // Use 'primary' if grouping is not specified\n grouping = 'primary'\n }\n\n // Convert some HTML tags and entities to UTF-8\n if (str) {\n str = str.trim()\n str = htmlToUtf8(str)\n }\n\n // If the trimmed string is empty, do not create a new key, and return an empty string\n if (!str) {\n return ''\n }\n\n if (this.records.has(key)) {\n // TODO: For now, allow certain keys to appear more than once; we only add the string once\n const prefix = key.substring(0, key.indexOf('__'))\n switch (prefix) {\n case 'graph_dataset_label':\n case 'graph_xaxis_label':\n case 'graph_yaxis_label':\n case 'input_group_title':\n case 'input_range':\n case 'input_units':\n break\n default:\n throw new Error(`More than one string with key=${key}`)\n }\n }\n\n // Add the string record\n this.records.set(key, {\n key,\n str,\n layout,\n context,\n grouping,\n appendedStringKeys\n })\n\n return key\n }\n\n /**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param xlatLangs The set of languages that are configured for translation.\n */\n writeJsFiles(context: BuildContext, dstDir: string /*, xlatLangs: Map<LangCode, LangSpec>*/): void {\n writeLangJsFiles(context, dstDir, this.records /*, xlatLangs*/)\n }\n}\n\nfunction getSortedRecords(records: Map<StringKey, StringRecord>): StringRecord[] {\n // Sort records by string key\n return Array.from(records.values()).sort((a, b) => {\n return a.key > b.key ? 1 : b.key > a.key ? -1 : 0\n })\n}\n\nfunction checkInvisibleCharacters(s: string): void {\n if (s.includes('\\u00a0')) {\n const e = s.replace(/\\u00a0/g, 'HERE')\n throw new Error(\n `String contains one or more non-breaking space characters (to fix, replace \"HERE\" with a normal space):\\n ${e}`\n )\n }\n}\n\nfunction utf8SubscriptToHtml(key: StringKey, s: string): string {\n if (key.includes('graph_yaxis_label')) {\n // Chart.js doesn't support using HTML tags like subscripts or superscripts\n // in axis labels. For now, we will convert subscript literals in axis labels\n // to a simple number. (We could preserve the subscript literal, but it doesn't\n // render all that well.)\n s = s.replace(/₂/gi, '2')\n s = s.replace(/₃/gi, '3')\n s = s.replace(/₄/gi, '4')\n s = s.replace(/₆/gi, '6')\n return s\n }\n\n // Unicode subscript literals render differently in some browsers (very low\n // in Safari for example), so we will replace them with HTML `sub` tags\n s = s.replace(/₂/gi, '<sub>2</sub>')\n s = s.replace(/₃/gi, '<sub>3</sub>')\n s = s.replace(/₄/gi, '<sub>4</sub>')\n s = s.replace(/₆/gi, '<sub>6</sub>')\n\n // If the subscript tag is followed by a space, that space needs to be\n // replaced with a non-breaking space, otherwise the whitespace will be lost\n s = s.replace(/<\\/sub> /gi, '</sub>&nbsp;')\n\n return s\n}\n\nfunction htmlSubscriptAndSuperscriptToUtf8(s: string): string {\n // Subscripts have a straight mapping in Unicode (U+208x)\n s = s.replace(/<sub>(\\d)<\\/sub>/gi, (_match, p1) => String.fromCharCode(0x2080 + Number(p1)))\n\n // Superscripts don't have a straight mapping, so it's easier to just\n // replace the ones we care about. There are some others (12, 18, -5)\n // that don't render well when replaced with their Unicode superscript\n // equivalents, so we will leave those with HTML `sup` tags.\n s = s.replace(/<sup>6<\\/sup>/gi, '\\u2076')\n s = s.replace(/<sup>9<\\/sup>/gi, '\\u2079')\n\n return s\n}\n\nexport function htmlToUtf8(orig: string): string {\n // Replace common HTML tags and entities with UTF-8 characters\n let s = orig\n\n // Convert `sub` and `sup` tags\n s = htmlSubscriptAndSuperscriptToUtf8(s)\n\n let clean = sanitizeHtml(s, {\n allowedTags: ['a', 'b', 'br', 'i', 'em', 'li', 'p', 'strong', 'sub', 'sup', 'ul'],\n allowedAttributes: {\n a: ['href', 'target', 'rel']\n }\n })\n\n // XXX: The `sanitize-html` package converts `&nbsp;` to the Unicode\n // equivalent (`U+00A0`); we will convert it back to `&nbsp;` to make\n // it more obvious and easier to view in a translation tool\n clean = clean.replace(/\\u00a0/gi, '&nbsp;')\n\n // if (clean !== s) {\n // console.log(`IN: ${orig}`)\n // console.log(`O1: ${s}`)\n // console.log(`O2: ${clean}\\n`)\n // }\n\n return clean\n}\n\n/**\n * Generate a string key for the given string by replacing special characters with\n * underscores and converting other characters to lowercase.\n */\nexport function genStringKey(prefix: string, s: string): string {\n checkInvisibleCharacters(s)\n\n let key = s.toLowerCase()\n key = key.replace(/ – /g, '_') // e.g. 'Data – Satellite' (with emdash) -> 'data_satellite'\n key = key.replace(/ \\/ /g, '_per_') // e.g. 'CO2 / TJ' -> 'co2_per_tj'\n key = key.replace(/ /g, '_')\n key = key.replace('₂', '2') // e.g. 'CO₂' -> 'co2'\n key = key.replace('<sub>2</sub>', '2') // e.g. 'CO<sub>2</sub>' -> 'co2'\n key = key.replace(/\\$\\//g, 'dollars_per_') // e.g. '$/year' -> 'dollars_per_year'\n key = key.replace(/%\\//g, 'pct_per_') // e.g. '%/year' -> 'pct_per_year'\n key = key.replace(/\\//g, '_per_') // e.g. 'CO2/TJ' -> 'co2_per_tj'\n key = key.replace(/º/g, 'degrees_') // e.g. 'ºC' -> 'degrees_c'\n key = key.replace(/\\*/g, '_') // e.g. 'CO2*year' -> 'co2_year'\n key = key.replace(/%/g, 'pct') // e.g. '%' -> 'pct'\n key = key.replace(/\\$/g, 'dollars') // e.g. '$' -> 'dollars'\n key = key.replace(/&/g, 'and') // e.g. 'Actions & Outcomes' -> 'actions_and_outcomes'\n key = key.replace(/\\//g, 'per') // e.g. 'Gigatons CO2/year' -> 'gigatons_co2_per_year'\n key = key.replace(/:/g, '') // e.g. 'Net:' -> 'net'\n key = key.replace(/\\./g, '') // e.g. 'U.S. Units' -> 'us_units'\n key = key.replace(/-/g, '_') // e.g. 'some-thing' -> 'some_thing'\n key = key.replace(/—/g, '_') // endash to underscore\n key = key.replace(/–/g, '_') // emdash to underscore\n key = key.replace(/,/g, '')\n key = key.replace(/\\(/g, '')\n key = key.replace(/\\)/g, '')\n key = key.replace(/\\\\n/g, '')\n key = key.replace(/<br>/g, '_')\n return `${prefix}__${key}`\n}\n\n/**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * These files are currently saved as plain JS (ES6) files. The only difference compared\n * to JSON files is these JS files start with `export default`, so converting to JSON is\n * as trivial as stripping those two words, if needed.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param records The string records.\n * //@param xlatLangs The set of languages that are configured for translation.\n */\nfunction writeLangJsFiles(\n context: BuildContext,\n dstDir: string,\n records: Map<StringKey, StringRecord>\n // xlatLangs: Map<LangCode, LangSpec>\n): void {\n const xlatMap: XlatMap = new Map()\n const sortedRecords = getSortedRecords(records)\n\n // const baseStringForKey = (key: StringKey) => {\n // const record = records.get(key)\n // if (!record) {\n // throw new Error(`No base string found for key=${key}`)\n // }\n // return record.str\n // }\n\n // Add base (e.g., English) strings that were gathered from the config files\n const enStrings: StringMap = new Map()\n for (const record of sortedRecords) {\n const s = record.str\n enStrings.set(record.key, utf8SubscriptToHtml(record.key, s))\n }\n // TODO: Don't assume English, make the base language configurable\n xlatMap.set('en', enStrings)\n\n // TODO: Enable support for translation files (for now, we only write base strings)\n // const hasSecondary =\n // existsSync(projectFilePath('localization', 'graph-descriptions')) &&\n // existsSync(projectFilePath('localization', 'input-descriptions'))\n // for (const lang of xlatLangs.keys()) {\n // const langStrings: StringMap = new Map()\n\n // let poMsgs: Map<string, string>\n // const primaryMsgs = readXlatPoFile('primary', lang)\n // if (hasSecondary) {\n // const graphMsgs = readXlatPoFile('graph-descriptions', lang)\n // const inputMsgs = readXlatPoFile('input-descriptions', lang)\n // poMsgs = new Map([...primaryMsgs, ...graphMsgs, ...inputMsgs])\n // } else {\n // poMsgs = primaryMsgs\n // }\n\n // const xlatStringForKey = (key: StringKey) => {\n // return poMsgs.get(key)\n // }\n\n // // Add the translation of each English string.\n // for (const record of sortedRecords) {\n // if (record.grouping !== 'primary') {\n // // Only include secondary strings (graph and input descriptions) if they are\n // // explicitly requested for this language; if not included, the English\n // // descriptions will be used as a fallback\n // if (xlatLangs.get(lang).includeSecondary !== true) {\n // continue\n // }\n // }\n\n // const xlatStr = xlatStringForKey(record.key)\n // if (xlatStr) {\n // // Add the translated string\n // const s = xlatStr\n // langStrings.set(record.key, utf8SubscriptToHtml(record.key, s))\n // } else {\n // // No translation for this string. We don't add the English string to\n // // map as a fallback. Instead, we configure the i18n library to use\n // // English strings as a fallback at runtime.\n // // console.warn(`WARNING: No translated string for lang=${lang} id=${stringObj.id}`)\n // }\n // }\n\n // xlatMap.set(lang, langStrings)\n // }\n\n // Write a JS file for each language for use in the core package\n for (const lang of xlatMap.keys()) {\n const stringsForLang = xlatMap.get(lang)\n const stringsObj = Object.fromEntries(stringsForLang)\n const json = JSON.stringify(stringsObj, null, 2)\n context.writeStagedFile('strings', dstDir, `${lang}.js`, `export default ${json}`)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport type InputVarId = string\nexport type OutputVarId = string\n\n/**\n * Helper function that converts a Vensim variable or subscript name\n * into a valid C identifier as used by SDE.\n * TODO: Import helper function from `sdeverywhere` package instead\n */\nfunction sdeNameForVensimName(name: string): string {\n return (\n '_' +\n name\n .trim()\n .replace(/\"/g, '_')\n .replace(/\\s+!$/g, '!')\n .replace(/\\s/g, '_')\n .replace(/,/g, '_')\n .replace(/-/g, '_')\n .replace(/\\./g, '_')\n .replace(/\\$/g, '_')\n .replace(/'/g, '_')\n .replace(/&/g, '_')\n .replace(/%/g, '_')\n .replace(/\\//g, '_')\n .replace(/\\|/g, '_')\n .toLowerCase()\n )\n}\n\n/**\n * Helper function that converts a Vensim variable name (possibly containing\n * subscripts) into a valid C identifier as used by SDE.\n * TODO: Import helper function from `sdeverywhere` package instead\n */\nexport function sdeNameForVensimVarName(varName: string): string {\n const m = varName.match(/([^[]+)(?:\\[([^\\]]+)\\])?/)\n if (!m) {\n throw new Error(`Invalid Vensim name: ${varName}`)\n }\n let id = sdeNameForVensimName(m[1])\n if (m[2]) {\n const subscripts = m[2].split(',').map(x => sdeNameForVensimName(x))\n id += `[${subscripts.join('][')}]`\n }\n\n return id\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext } from './context'\nimport { sdeNameForVensimVarName } from './var-names'\n\n/**\n * Write the `model-spec.ts` file used by the core package to initialize the model.\n */\nexport function writeModelSpec(context: ConfigContext, dstDir: string): void {\n // Create ordered arrays of inputs and outputs\n const inputVarIds = context.getOrderedInputs().map(i => sdeNameForVensimVarName(i.varName))\n const outputVarIds = context.getOrderedOutputs().map(o => sdeNameForVensimVarName(o.varName))\n\n // Generate the `model-spec.ts` file\n let tsContent = ''\n function emit(s: string): void {\n tsContent += s + '\\n'\n }\n\n emit('// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!')\n emit(`export const inputVarIds: string[] = ${JSON.stringify(inputVarIds, null, 2)}`)\n emit(`export const outputVarIds: string[] = ${JSON.stringify(outputVarIds, null, 2)}`)\n\n // Write the `model-spec.ts` file to the staged directory\n context.writeStagedFile('model', dstDir, 'model-spec.ts', tsContent)\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { dirname, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { ConfigContext } from './context'\nimport { generateGraphSpecs } from './gen-graphs'\nimport { generateInputsConfig } from './gen-inputs'\nimport type { GraphId, GraphSpec, InputId, InputSpec } from './spec-types'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport interface ConfigSpecs {\n graphSpecs: Map<GraphId, GraphSpec>\n inputSpecs: Map<InputId, InputSpec>\n}\n\n/**\n * Convert the CSV files in the `config` directory to config specs that can be\n * used in the core package.\n */\nexport function generateConfigSpecs(context: ConfigContext): ConfigSpecs {\n // Convert `graphs.csv` to graph specs\n context.log('verbose', ' Reading graph specs')\n const graphSpecs = generateGraphSpecs(context)\n\n // Convert `inputs.csv` to input specs\n context.log('verbose', ' Reading input specs')\n const inputSpecs = generateInputsConfig(context)\n\n // Include extra output variables that should be included in the generated\n // model even though they are not referenced in any graph specs\n context.log('verbose', ' Reading extra output variables')\n const extraOutputsCsv = context.readConfigCsvFile('outputs')\n for (const row of extraOutputsCsv) {\n const varName = row['variable name']\n if (varName) {\n context.addOutputVariable(varName)\n }\n }\n\n return {\n graphSpecs,\n inputSpecs\n }\n}\n\n/**\n * Write the `config-specs.ts` file to the given destination directory.\n */\nexport function writeConfigSpecs(context: ConfigContext, config: ConfigSpecs, dstDir: string): void {\n // Generate one big string containing the TypeScript source that will be\n // loaded by `config.ts` at runtime\n let tsContent = ''\n function emit(s: string): void {\n tsContent += s + '\\n'\n }\n\n emit('// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!')\n emit('')\n emit(`import type { GraphSpec, InputSpec } from './spec-types'`)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function emitArray(type: string, values: Iterable<any>): void {\n const varName = type.charAt(0).toLowerCase() + type.slice(1) + 's'\n const array = Array.from(values)\n const json = JSON.stringify(array, null, 2)\n emit('')\n emit(`export const ${varName}: ${type}[] = ${json}`)\n }\n\n emitArray('GraphSpec', config.graphSpecs.values())\n emitArray('InputSpec', config.inputSpecs.values())\n\n // Write the `config-specs.ts` file\n context.writeStagedFile('config', dstDir, 'config-specs.ts', tsContent)\n}\n\n/**\n * Write the `spec-types.ts` file to the given destination directory.\n */\nexport function writeSpecTypes(context: ConfigContext, dstDir: string): void {\n // Copy the `spec-types.ts` file. Currently we keep the full source of the file\n // in the `dist` directory for this package so that we can access it here.\n const tsFile = 'spec-types.ts'\n const tsPath = resolvePath(__dirname, tsFile)\n const tsContent = readFileSync(tsPath, 'utf8')\n context.writeStagedFile('config', dstDir, tsFile, tsContent)\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport function optionalString(stringValue?: string): string | undefined {\n if (stringValue !== undefined && stringValue.length > 0) {\n return stringValue\n } else {\n return undefined\n }\n}\n\nexport function optionalNumber(stringValue?: string): number | undefined {\n if (stringValue !== undefined && stringValue.length > 0) {\n return Number(stringValue)\n } else {\n return undefined\n }\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext, CsvRow } from './context'\nimport { optionalNumber, optionalString } from './read-config'\nimport type {\n GraphAlternateSpec,\n GraphDatasetSpec,\n GraphId,\n GraphKind,\n GraphLegendItemSpec,\n GraphSide,\n GraphSpec,\n LineStyle,\n LineStyleModifier,\n StringKey,\n UnitSystem\n} from './spec-types'\nimport { genStringKey, htmlToUtf8 } from './strings'\nimport { sdeNameForVensimVarName } from './var-names'\n\n/**\n * Convert the `config/graphs.csv` file to config specs that can be used in\n * the core package.\n */\nexport function generateGraphSpecs(context: ConfigContext): Map<GraphId, GraphSpec> {\n // TODO: Optionally read the graph descriptions from `graphs.md`\n // let descriptions: Map<GraphId, Description>\n // if (useDescriptions) {\n // descriptions = readGraphDescriptions(context)\n // } else {\n // descriptions = undefined\n // }\n\n // Convert `graphs.csv` to graph specs\n const graphsCsv = context.readConfigCsvFile('graphs')\n const graphSpecs: Map<GraphId, GraphSpec> = new Map()\n for (const row of graphsCsv) {\n const spec = graphSpecFromCsv(row, context)\n if (spec) {\n graphSpecs.set(spec.id, spec)\n }\n }\n\n return graphSpecs\n}\n\nfunction graphSpecFromCsv(g: CsvRow, context: ConfigContext): GraphSpec | undefined {\n const strings = context.strings\n\n // TODO: For now, all strings use the same \"layout\" specifier; this could be customized\n // to provide a \"maximum length\" hint for a group of strings to the translation tool\n const layout = 'default'\n\n function requiredString(key: string): string {\n const value = g[key]\n if (value === undefined || typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`Must specify '${key}' for graph ${g.id}`)\n }\n return value\n }\n\n // Extract required fields\n const graphIdParts = requiredString('id').split(';')\n const graphId = graphIdParts[0]\n const graphIdBaseParts = graphId.split('-')\n const graphBaseId = graphIdBaseParts[0]\n const title = requiredString('graph title')\n\n // Extract optional fields\n const menuTitle = optionalString(g['menu title'])\n const miniTitle = optionalString(g['mini title'])\n const parentMenu = optionalString(g['parent menu'])\n const description = optionalString(g['description'])\n const kindString = optionalString(g['kind'])\n\n // Skip rows that have an empty `parent menu` value; this can be used to omit graphs\n // from the product until they've been fully reviewed and approved\n if (!parentMenu) {\n context.log('info', `Skipping graph ${graphId} (${title})`)\n return undefined\n }\n\n // TODO: Check for a description\n // let desc: Description\n // if (descriptions) {\n // desc = descriptions.get(graphBaseId)\n // if (!desc) {\n // throw new Error(`Graph description for ${graphBaseId} not found in graphs.md`)\n // }\n // }\n\n // Helper that creates a string key prefix\n const key = (kind: string) => `graph_${graphBaseId.padStart(3, '0')}_${kind}`\n\n // Helper that creates a string context\n const strCtxt = (kind: string) => {\n const parent = htmlToUtf8(parentMenu).replace('&amp;', '&')\n const displayTitle = htmlToUtf8(menuTitle || title).replace('&amp;', '&')\n return `Graph ${kind}: ${parent} > ${displayTitle}`\n }\n\n const titleKey = strings.add(key('title'), title, layout, strCtxt('Title'))\n let menuTitleKey: StringKey\n if (menuTitle) {\n menuTitleKey = strings.add(key('menu_title'), menuTitle, layout, strCtxt('Menu Item'))\n }\n\n let miniTitleKey: StringKey\n if (miniTitle) {\n miniTitleKey = strings.add(key('mini_title'), miniTitle, layout, strCtxt('Title (for Mini View)'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'graph-descriptions')\n }\n\n // TODO: Validate kind?\n const kind: GraphKind = kindString\n\n // TODO: Validate graph side?\n const sideString = optionalString(g['side'])\n const side: GraphSide = sideString\n\n // Determine if this graph is associated with a particular unit system and\n // has an alternate version\n const unitsString = optionalString(g['units'])\n const altIdString = optionalString(g['alternate'])\n let unitSystem: UnitSystem\n let alternates: GraphAlternateSpec[]\n if (unitsString && altIdString) {\n if (unitsString === 'metric') {\n // This graph is metric with a U.S. alternate\n unitSystem = 'metric'\n alternates = [\n {\n id: altIdString,\n unitSystem: 'us'\n }\n ]\n } else if (unitsString === 'us') {\n // This graph is U.S. with a metric alternate\n unitSystem = 'us'\n alternates = [\n {\n id: altIdString,\n unitSystem: 'metric'\n }\n ]\n }\n }\n\n const xMin = optionalNumber(g['x axis min']) || context.graphDefaultMinTime\n const xMax = optionalNumber(g['x axis max']) || context.graphDefaultMaxTime\n const xAxisLabel = optionalString(g['x axis label'])\n let xAxisLabelKey: StringKey\n if (xAxisLabel) {\n xAxisLabelKey = strings.add(genStringKey('graph_xaxis_label', xAxisLabel), xAxisLabel, layout, 'Graph X-Axis Label')\n }\n\n const yMin = optionalNumber(g['y axis min']) || 0\n const yMax = optionalNumber(g['y axis max'])\n const ySoftMax = optionalNumber(g['y axis soft max'])\n const yFormat = optionalString(g['y axis format']) || '.0f'\n const yAxisLabel = optionalString(g['y axis label'])\n let yAxisLabelKey: StringKey\n if (yAxisLabel) {\n yAxisLabelKey = strings.add(genStringKey('graph_yaxis_label', yAxisLabel), yAxisLabel, layout, 'Graph Y-Axis Label')\n }\n\n const datasets: GraphDatasetSpec[] = []\n interface Overrides {\n sourceName?: string\n colorId?: string\n }\n function addDataset(index: number, overrides?: Overrides): void {\n const plotKey = (name: string) => `plot ${index} ${name}`\n const varName = g[plotKey('variable')]\n if (!varName) {\n return\n }\n\n const varId = sdeNameForVensimVarName(varName)\n const externalSourceName = overrides?.sourceName || optionalString(g[plotKey('source')])\n const datasetLabel = optionalString(g[plotKey('label')])\n let labelKey: StringKey\n if (datasetLabel) {\n labelKey = strings.add(\n genStringKey('graph_dataset_label', datasetLabel),\n datasetLabel,\n layout,\n 'Graph Dataset Label'\n )\n }\n\n const colorId = overrides?.colorId || requiredString(plotKey('color'))\n const hexColor = context.getHexColorForId(colorId)\n if (!hexColor) {\n throw new Error(`Graph ${graphId} references an unknown color ${colorId}`)\n }\n\n const lineStyleAndModString = optionalString(g[plotKey('style')]) || 'line'\n const lineStyleParts = lineStyleAndModString.split(';')\n const lineStyleString = lineStyleParts[0]\n const lineStyleModifierString = lineStyleParts.length > 1 ? lineStyleParts[1] : undefined\n\n // TODO: Validate line style and modifiers?\n const lineStyle: LineStyle = lineStyleString\n let lineStyleModifiers: ReadonlyArray<LineStyleModifier>\n if (lineStyleModifierString) {\n // TODO: For now, we assume at most one modifier; should change this to allow > 1\n lineStyleModifiers = [lineStyleModifierString]\n }\n\n if (externalSourceName && externalSourceName !== 'Ref') {\n // Add the variable to the set of vars to be included in the static data file\n context.addStaticVariable(externalSourceName, varName)\n } else {\n // Add the variable if this is a normal model output (in which case the source\n // name is undefined) or if it will be captured as \"Ref\" (baseline) values\n context.addOutputVariable(varName)\n }\n\n const datasetSpec: GraphDatasetSpec = {\n varId,\n varName,\n externalSourceName,\n labelKey,\n color: hexColor,\n lineStyle,\n lineStyleModifiers\n }\n datasets.push(datasetSpec)\n }\n\n // Add each dataset configured in graphs.csv\n for (let i = 1; i <= 11; i++) {\n addDataset(i)\n }\n\n // Only show legend items for datasets that have a label (i.e., ignore\n // some special ones, like the ones used to show dotted reference lines)\n const legendItems: GraphLegendItemSpec[] = datasets\n .filter(dataset => dataset.labelKey?.length > 0)\n .map(dataset => {\n return {\n color: dataset.color,\n labelKey: dataset.labelKey\n }\n })\n\n const graphSpec: GraphSpec = {\n id: graphId,\n kind,\n titleKey,\n miniTitleKey,\n menuTitleKey,\n descriptionKey,\n side,\n unitSystem,\n alternates,\n xMin,\n xMax,\n xAxisLabelKey,\n yMin,\n yMax,\n ySoftMax,\n yAxisLabelKey,\n yFormat,\n datasets,\n legendItems\n }\n\n // Add the graph to the menu\n // context.addGraphMenuItem(graphSpec, parentMenu)\n\n return graphSpec\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext, CsvRow } from './context'\nimport { optionalNumber, optionalString } from './read-config'\nimport type { InputId, InputSpec, SliderSpec, StringKey, SwitchSpec } from './spec-types'\nimport { genStringKey, htmlToUtf8 } from './strings'\nimport { sdeNameForVensimVarName } from './var-names'\n\n// TODO: For now, all strings use the same \"layout\" specifier; this could be customized\n// to provide a \"maximum length\" hint for a group of strings to the translation tool\nconst layout = 'default'\n\n/**\n * Convert the `config/inputs.csv` file to config specs that can be used in\n * the core package.\n */\nexport function generateInputsConfig(context: ConfigContext): Map<InputId, InputSpec> {\n // Convert `inputs.csv` to input specs\n const inputsCsv = context.readConfigCsvFile('inputs')\n const inputSpecs: Map<InputId, InputSpec> = new Map()\n for (const row of inputsCsv) {\n const spec = inputSpecFromCsv(row, context)\n if (spec) {\n inputSpecs.set(spec.id, spec)\n }\n }\n\n return inputSpecs\n}\n\nfunction inputSpecFromCsv(r: CsvRow, context: ConfigContext): InputSpec | undefined {\n const strings = context.strings\n\n function requiredString(key: string): string {\n const value = r[key]\n if (value === undefined || typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`Must specify '${key}' for input ${r.id}`)\n }\n return value\n }\n\n function requiredNumber(key: string): number {\n const stringValue = requiredString(key)\n const numValue = Number(stringValue)\n if (numValue === undefined) {\n throw new Error(`Must specify numeric '${key}' for input ${r.id}`)\n }\n return numValue\n }\n\n // Extract required fields\n const inputIdParts = requiredString('id').split(';')\n const inputId = inputIdParts[0]\n const viewId = optionalString(r['viewid'])\n const label = optionalString(r['label']) || ''\n const inputType = requiredString('input type')\n\n // Skip rows that have an empty `viewid` value; this can be used to omit inputs\n // from the product until they've been fully reviewed and approved\n if (!viewId) {\n context.log('info', `Skipping input ${inputId} (${label})`)\n return undefined\n }\n\n // Extract optional fields\n const description = optionalString(r['description'])\n\n // Helper that creates a string key prefix\n const key = (kind: string) => `input_${inputId.padStart(3, '0')}_${kind}`\n\n // For now, use the group name defined in `inputs.csv`\n const groupTitle = optionalString(r['group name'])\n if (!groupTitle) {\n throw new Error(`Must specify 'group name' for input ${inputId}`)\n }\n const groupTitleKey = genStringKey('input_group_title', groupTitle)\n strings.add(groupTitleKey, groupTitle, layout, 'Input Group Title')\n\n let typeLabel: string\n switch (inputType) {\n case 'slider':\n typeLabel = 'Slider'\n break\n case 'switch':\n typeLabel = 'Switch'\n break\n case 'checkbox':\n typeLabel = 'Checkbox'\n break\n case 'checkbox group':\n typeLabel = 'Checkbox Group'\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n // Helper that creates a string context\n const strCtxt = (kind: string) => {\n const labelText = htmlToUtf8(label).replace('&amp;', '&')\n return `${typeLabel} ${kind}: ${groupTitle} > ${labelText}`\n }\n\n const labelKey = strings.add(key('label'), label, layout, strCtxt('Label'))\n\n const listingLabel = optionalString(r['listing label'])\n let listingLabelKey: StringKey\n if (listingLabel) {\n listingLabelKey = strings.add(key('action_label'), listingLabel, layout, strCtxt('Action Label'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'input-descriptions')\n }\n\n // Converts a slider row in `inputs.csv` to a `SliderSpec`\n function sliderSpecFromCsv(): SliderSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const defaultValue = requiredNumber('slider/switch default')\n const minValue = requiredNumber('slider min')\n const maxValue = requiredNumber('slider max')\n const step = requiredNumber('slider step')\n const reversed = optionalString(r['reversed']) === 'yes'\n\n if (defaultValue < minValue || defaultValue > maxValue) {\n let e = `Default value for slider ${inputId} is out of range: `\n e += `default=${defaultValue} min=${minValue} max=${maxValue}`\n throw new Error(e)\n }\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n const format = optionalString(r['format']) || '.0f'\n\n const units = optionalString(r['units'])\n let unitsKey: StringKey\n if (units) {\n unitsKey = strings.add(genStringKey('input_units', units), units, layout, 'Slider Units')\n }\n\n const rangeInfo = getSliderRangeInfo(r, maxValue, context)\n const rangeLabelKeys = rangeInfo.labelKeys\n const rangeDividers = rangeInfo.dividers\n\n return {\n kind: 'slider',\n id: inputId,\n varId,\n varName,\n defaultValue,\n minValue,\n maxValue,\n step,\n reversed,\n labelKey,\n listingLabelKey,\n descriptionKey,\n unitsKey,\n rangeLabelKeys,\n rangeDividers,\n format\n }\n }\n\n // Converts a switch row in `inputs.csv` to a `SwitchSpec`\n function switchSpecFromCsv(): SwitchSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const onValue = requiredNumber('enabled value')\n const offValue = requiredNumber('disabled value')\n const defaultValue = requiredNumber('slider/switch default')\n if (defaultValue !== onValue && defaultValue !== offValue) {\n throw new Error(\n `Invalid default value for switch ${inputId}: off=${offValue} on=${onValue} default=${defaultValue}`\n )\n }\n\n const minValue = Math.min(offValue, onValue)\n const maxValue = Math.max(offValue, onValue)\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n // The `controlled input ids` field dictates which rows are active\n // when this switch is on or off. Examples of the format of this field:\n // 1;2;3|4;5;6\n // 1|2;3;4;5\n // |1\n // On the left side of the '|' are the rows that are active when the\n // switch is in an off position, and on the right side are the rows\n // that are active when the switch is in an on position. Usually the\n // \"active when off\" rows are above the switch in the UI, and the\n // \"active when on\" rows are below the switch.\n const controlledInputIds = requiredString('controlled input ids')\n const controlledParts = controlledInputIds.split('|')\n const rowsActiveWhenOff = controlledParts[0].split(';').filter(id => id.trim().length > 0)\n const rowsActiveWhenOn = controlledParts[1].split(';').filter(id => id.trim().length > 0)\n\n return {\n kind: 'switch',\n id: inputId,\n varId,\n varName,\n labelKey,\n listingLabelKey,\n descriptionKey,\n defaultValue,\n offValue,\n onValue,\n slidersActiveWhenOff: rowsActiveWhenOff,\n slidersActiveWhenOn: rowsActiveWhenOn\n }\n }\n\n // Call a different converter function depending on the input type\n let inputSpec: SliderSpec | SwitchSpec\n switch (inputType) {\n case 'slider': {\n inputSpec = sliderSpecFromCsv()\n break\n }\n case 'switch':\n case 'checkbox': {\n inputSpec = switchSpecFromCsv()\n break\n }\n case 'checkbox group':\n // TODO\n // XXX: For now, we specify the checkbox IDs as a semicolon separated list\n // in the \"varname\" cell\n // const checkboxIds = row['varname'].split(';')\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n return inputSpec\n}\n\ninterface SliderRangeInfo {\n labelKeys: StringKey[]\n dividers: number[]\n}\n\nfunction getSliderRangeInfo(r: CsvRow, maxValue: number, context: ConfigContext): SliderRangeInfo {\n const strings = context.strings\n const labelKeys: StringKey[] = []\n const dividers: number[] = []\n\n // Get all labels to determine the number of ranges\n let rangeNum = 1\n while (rangeNum <= 5) {\n const label = optionalString(r[`range ${rangeNum} label`])\n if (!label) {\n break\n }\n const labelKey = strings.add(genStringKey('input_range', label), label, layout, 'Slider Range Label')\n if (!labelKey) {\n break\n }\n labelKeys.push(labelKey)\n rangeNum++\n }\n\n // Find dividing points between ranges; the absence of a final dividing point\n // indicates the use of discrete values\n const numRanges = rangeNum - 1\n for (rangeNum = 2; rangeNum <= numRanges; rangeNum++) {\n let divider = optionalNumber(r[`range ${rangeNum} start`])\n if (divider === undefined) {\n // Fall back on the slider max value when a divider is missing\n divider = maxValue\n }\n dividers.push(divider)\n }\n\n return {\n labelKeys,\n dividers\n }\n}\n"],"mappings":"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","names":["joinPath","layout","layout","readFileSync","layout","key","kind","key","readFileSync","joinPath"]}
1
+ {"version":3,"sources":["../src/processor.ts","../src/context.ts","../src/strings.ts","../src/var-names.ts","../src/gen-model-spec.ts","../src/gen-config-specs.ts","../src/read-config.ts","../src/gen-graphs.ts","../src/gen-inputs.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport type { BuildContext, ModelSpec } from '@sdeverywhere/build'\n\nimport { createConfigContext } from './context'\nimport { writeModelSpec } from './gen-model-spec'\nimport { generateConfigSpecs, writeConfigSpecs, writeSpecTypes } from './gen-config-specs'\n\nexport interface ConfigProcessorOutputPaths {\n /** The absolute path to the directory where model spec files will be written. */\n modelSpecsDir?: string\n\n /** The absolute path to the directory where config spec files will be written. */\n configSpecsDir?: string\n\n /** The absolute path to the directory where translated strings will be written. */\n stringsDir?: string\n}\n\nexport interface ConfigProcessorOptions {\n /**\n * The absolute path to the directory containing the CSV config files.\n */\n config: string\n\n /**\n * Either a single path to a base output directory (in which case, the recommended\n * directory structure will be used) or a `ConfigProcessorOutputPaths` containing specific paths.\n * If a single string is provided, the following subdirectories will be used:\n * ```\n * <out-dir>/\n * src/\n * config/\n * generated/\n * model/\n * generated/\n * strings/\n * ```\n */\n out?: string | ConfigProcessorOutputPaths\n}\n\n/**\n * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere\n * `UserConfig`. The returned function:\n * - reads CSV files from a `config` directory\n * - writes JS files to the configured output directories\n * - returns a `ModelSpec` that guides the rest of the `sde` build process\n */\nexport function configProcessor(options: ConfigProcessorOptions): (buildContext: BuildContext) => Promise<ModelSpec> {\n return buildContext => {\n return processModelConfig(buildContext, options)\n }\n}\n\nasync function processModelConfig(buildContext: BuildContext, options: ConfigProcessorOptions): Promise<ModelSpec> {\n const t0 = performance.now()\n\n // Resolve source (config) directory\n if (!existsSync(options.config)) {\n throw new Error(`The provided config dir '${options.config}' does not exist`)\n }\n\n // Resolve output directories\n let outModelSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outModelSpecsDir = joinPath(options.out, 'src', 'model', 'generated')\n } else {\n outModelSpecsDir = options.out.modelSpecsDir\n }\n }\n\n let outConfigSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outConfigSpecsDir = joinPath(options.out, 'src', 'config', 'generated')\n } else {\n outConfigSpecsDir = options.out.configSpecsDir\n }\n }\n\n let outStringsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outStringsDir = joinPath(options.out, 'strings')\n } else {\n outStringsDir = options.out.stringsDir\n }\n }\n\n // Create a container for strings, variables, etc\n const context = createConfigContext(buildContext, options.config)\n\n // Write the generated files\n context.log('info', 'Generating files...')\n\n const configSpecs = generateConfigSpecs(context)\n if (outConfigSpecsDir) {\n context.log('verbose', ' Writing config specs')\n writeConfigSpecs(context, configSpecs, outConfigSpecsDir)\n writeSpecTypes(context, outConfigSpecsDir)\n }\n\n if (outModelSpecsDir) {\n context.log('verbose', ' Writing model specs')\n writeModelSpec(context, outModelSpecsDir)\n }\n\n if (outStringsDir) {\n context.log('verbose', ' Writing strings')\n context.writeStringsFiles(outStringsDir)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `Done generating files (${elapsed}s)`)\n\n return {\n inputs: context.getOrderedInputs(),\n outputs: context.getOrderedOutputs(),\n datFiles: context.datFiles\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { join as joinPath, relative } from 'path'\n\nimport { parse as parseCsv } from 'csv-parse/sync'\n\nimport type { BuildContext, InputSpec, LogLevel, OutputSpec } from '@sdeverywhere/build'\n\nimport type { HexColor } from './spec-types'\nimport { Strings } from './strings'\nimport type { InputVarId, OutputVarId } from './var-names'\nimport { sdeNameForVensimVarName } from './var-names'\n\nexport type CsvRow = { [key: string]: string }\nexport type ColorId = string\n\nexport class ConfigContext {\n private readonly inputSpecs: Map<InputVarId, InputSpec> = new Map()\n private readonly outputVarNames: Map<OutputVarId, string> = new Map()\n private readonly staticVarNames: Map<string, Set<string>> = new Map()\n\n constructor(\n private readonly buildContext: BuildContext,\n private readonly configDir: string,\n public readonly strings: Strings,\n private readonly colorMap: Map<ColorId, HexColor>,\n public readonly graphDefaultMinTime: number,\n public readonly graphDefaultMaxTime: number,\n public readonly datFiles: string[]\n ) {}\n\n /**\n * Read a CSV file of the given name from the config directory.\n *\n * @param name The base name of the CSV file.\n */\n readConfigCsvFile(name: string): CsvRow[] {\n return readConfigCsvFile(this.configDir, name)\n }\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n this.buildContext.log(level, msg)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.buildContext.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n addInputVariable(\n inputId: string,\n inputVarName: string,\n defaultValue: number,\n minValue: number,\n maxValue: number\n ): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(inputVarName)\n if (this.inputSpecs.get(varId)) {\n // Fail if the variable was already added (there should only be one spec\n // per input variable)\n console.error(`ERROR: Input variable ${inputVarName} was already added`)\n }\n this.inputSpecs.set(varId, {\n inputId,\n varName: inputVarName,\n defaultValue,\n minValue,\n maxValue\n })\n }\n\n addOutputVariable(outputVarName: string): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(outputVarName)\n this.outputVarNames.set(varId, outputVarName)\n }\n\n addStaticVariable(sourceName: string, varName: string): void {\n const sourceVarNames = this.staticVarNames.get(sourceName)\n if (sourceVarNames) {\n sourceVarNames.add(varName)\n } else {\n const varNames: Set<string> = new Set()\n varNames.add(varName)\n this.staticVarNames.set(sourceName, varNames)\n }\n }\n\n getHexColorForId(colorId: ColorId): HexColor {\n return this.colorMap.get(colorId)\n }\n\n getOrderedInputs(): InputSpec[] {\n // TODO: It would be nice to alphabetize the inputs, but currently we have\n // code that assumes that the InputSpecs in the map have the same order\n // as the variables in the spec file and model config, so preserve the\n // existing order here for now\n return Array.from(this.inputSpecs.values())\n }\n\n getOrderedOutputs(): OutputSpec[] {\n // Sort the output variable names alphabetically\n const alphabetical = (a: string, b: string) => (a > b ? 1 : b > a ? -1 : 0)\n const varNames = Array.from(this.outputVarNames.values()).sort(alphabetical)\n return varNames.map(varName => {\n return {\n varName\n }\n })\n }\n\n writeStringsFiles(dstDir: string): void {\n this.strings.writeJsFiles(this.buildContext, dstDir /*, xlatLangs*/)\n }\n}\n\nexport function createConfigContext(buildContext: BuildContext, configDir: string): ConfigContext {\n // Read basic model configuration from `model.csv`\n const modelCsv = readConfigCsvFile(configDir, 'model')[0]\n const graphDefaultMinTime = Number(modelCsv['graph default min time'])\n const graphDefaultMaxTime = Number(modelCsv['graph default max time'])\n const datFilesString = modelCsv['model dat files']\n const origDatFiles = datFilesString.length > 0 ? datFilesString.split(';') : []\n\n // The dat file paths in the config file are assumed to be relative to\n // the project directory (i.e., the directory where the `sde.config.js`\n // file resides), so we need to convert to paths that are relative to\n // the `sde-prep` directory (since that is the \"model\" directory from\n // the perspective of the compile package).\n const prepDir = buildContext.config.prepDir\n const projDir = buildContext.config.rootDir\n const datFiles = origDatFiles.map(f => joinPath(relative(prepDir, projDir), f))\n\n // Read the static strings from `strings.csv`\n const strings = readStringsCsv(configDir)\n\n // Read color configuration from `colors.csv`\n const colorsCsv = readConfigCsvFile(configDir, 'colors')\n const colors = new Map()\n for (const row of colorsCsv) {\n const colorId = row['id']\n const hexColor = row['hex code']\n colors.set(colorId, hexColor)\n }\n\n return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles)\n}\n\nfunction configFilePath(configDir: string, name: string, ext: string): string {\n return joinPath(configDir, `${name}.${ext}`)\n}\n\nfunction readCsvFile(path: string): CsvRow[] {\n const data = readFileSync(path, 'utf8')\n return parseCsv(data, {\n columns: true,\n trim: true,\n skip_empty_lines: true,\n skip_records_with_empty_values: true\n })\n}\n\nfunction readConfigCsvFile(configDir: string, name: string): CsvRow[] {\n return readCsvFile(configFilePath(configDir, name, 'csv'))\n}\n\n/**\n * Initialize a `Strings` instance with the core strings from `strings.csv`.\n */\nfunction readStringsCsv(configDir: string): Strings {\n const strings = new Strings()\n\n // TODO: For now we use the same \"layout\" and \"context\" for all core strings\n const layout = 'default'\n const context = 'Core'\n\n const rows = readConfigCsvFile(configDir, 'strings')\n for (const row of rows) {\n const key = row['id']\n let str = row['string']\n str = str ? str.trim() : ''\n if (str) {\n strings.add(key, str, layout, context)\n }\n }\n\n return strings\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport sanitizeHtml from 'sanitize-html'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type { StringKey } from './spec-types'\n\ninterface StringRecord {\n key: StringKey\n str: string\n layout: string\n context: string\n grouping: string\n appendedStringKeys?: string[]\n}\n\ntype LangCode = string\ntype StringMap = Map<StringKey, string>\ntype XlatMap = Map<LangCode, StringMap>\n\nexport class Strings {\n private readonly records: Map<StringKey, StringRecord> = new Map()\n\n add(\n key: StringKey,\n str: string,\n layout: string,\n context: string,\n grouping?: string,\n appendedStringKeys?: string[]\n ): StringKey {\n checkInvisibleCharacters(str || '')\n\n if (!key) {\n throw new Error(`Must provide a key for the string: ${str}`)\n }\n\n const validKey = /^[0-9a-z_]+$/.test(key)\n if (!validKey) {\n throw new Error(`String key contains undesirable characters: ${key}`)\n }\n\n if (!layout) {\n throw new Error(`Must provide a layout (e.g., 'layout1' or 'not-translated')`)\n }\n\n if (!context) {\n throw new Error(`Must provide a context string: key=${key}, string=${str}`)\n }\n\n if (context === 'Core') {\n // For core strings from the `strings.csv` file, leave the context empty for now\n // (indicating this is a \"general\" string with no particular context)\n context = undefined\n }\n\n if (!grouping) {\n // Use 'primary' if grouping is not specified\n grouping = 'primary'\n }\n\n // Convert some HTML tags and entities to UTF-8\n if (str) {\n str = str.trim()\n str = htmlToUtf8(str)\n }\n\n // If the trimmed string is empty, do not create a new key, and return an empty string\n if (!str) {\n return ''\n }\n\n if (this.records.has(key)) {\n // TODO: For now, allow certain keys to appear more than once; we only add the string once\n const prefix = key.substring(0, key.indexOf('__'))\n switch (prefix) {\n case 'graph_dataset_label':\n case 'graph_xaxis_label':\n case 'graph_yaxis_label':\n case 'input_group_title':\n case 'input_range':\n case 'input_units':\n break\n default:\n throw new Error(`More than one string with key=${key}`)\n }\n }\n\n // Add the string record\n this.records.set(key, {\n key,\n str,\n layout,\n context,\n grouping,\n appendedStringKeys\n })\n\n return key\n }\n\n /**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param xlatLangs The set of languages that are configured for translation.\n */\n writeJsFiles(context: BuildContext, dstDir: string /*, xlatLangs: Map<LangCode, LangSpec>*/): void {\n writeLangJsFiles(context, dstDir, this.records /*, xlatLangs*/)\n }\n}\n\nfunction getSortedRecords(records: Map<StringKey, StringRecord>): StringRecord[] {\n // Sort records by string key\n return Array.from(records.values()).sort((a, b) => {\n return a.key > b.key ? 1 : b.key > a.key ? -1 : 0\n })\n}\n\nfunction checkInvisibleCharacters(s: string): void {\n if (s.includes('\\u00a0')) {\n const e = s.replace(/\\u00a0/g, 'HERE')\n throw new Error(\n `String contains one or more non-breaking space characters (to fix, replace \"HERE\" with a normal space):\\n ${e}`\n )\n }\n}\n\nfunction utf8SubscriptToHtml(key: StringKey, s: string): string {\n if (key.includes('graph_yaxis_label')) {\n // Chart.js doesn't support using HTML tags like subscripts or superscripts\n // in axis labels. For now, we will convert subscript literals in axis labels\n // to a simple number. (We could preserve the subscript literal, but it doesn't\n // render all that well.)\n s = s.replace(/₂/gi, '2')\n s = s.replace(/₃/gi, '3')\n s = s.replace(/₄/gi, '4')\n s = s.replace(/₆/gi, '6')\n return s\n }\n\n // Unicode subscript literals render differently in some browsers (very low\n // in Safari for example), so we will replace them with HTML `sub` tags\n s = s.replace(/₂/gi, '<sub>2</sub>')\n s = s.replace(/₃/gi, '<sub>3</sub>')\n s = s.replace(/₄/gi, '<sub>4</sub>')\n s = s.replace(/₆/gi, '<sub>6</sub>')\n\n // If the subscript tag is followed by a space, that space needs to be\n // replaced with a non-breaking space, otherwise the whitespace will be lost\n s = s.replace(/<\\/sub> /gi, '</sub>&nbsp;')\n\n return s\n}\n\nfunction htmlSubscriptAndSuperscriptToUtf8(s: string): string {\n // Subscripts have a straight mapping in Unicode (U+208x)\n s = s.replace(/<sub>(\\d)<\\/sub>/gi, (_match, p1) => String.fromCharCode(0x2080 + Number(p1)))\n\n // Superscripts don't have a straight mapping, so it's easier to just\n // replace the ones we care about. There are some others (12, 18, -5)\n // that don't render well when replaced with their Unicode superscript\n // equivalents, so we will leave those with HTML `sup` tags.\n s = s.replace(/<sup>6<\\/sup>/gi, '\\u2076')\n s = s.replace(/<sup>9<\\/sup>/gi, '\\u2079')\n\n return s\n}\n\nexport function htmlToUtf8(orig: string): string {\n // Replace common HTML tags and entities with UTF-8 characters\n let s = orig\n\n // Convert `sub` and `sup` tags\n s = htmlSubscriptAndSuperscriptToUtf8(s)\n\n let clean = sanitizeHtml(s, {\n allowedTags: ['a', 'b', 'br', 'i', 'em', 'li', 'p', 'strong', 'sub', 'sup', 'ul'],\n allowedAttributes: {\n a: ['href', 'target', 'rel']\n }\n })\n\n // XXX: The `sanitize-html` package converts `&nbsp;` to the Unicode\n // equivalent (`U+00A0`); we will convert it back to `&nbsp;` to make\n // it more obvious and easier to view in a translation tool\n clean = clean.replace(/\\u00a0/gi, '&nbsp;')\n\n // if (clean !== s) {\n // console.log(`IN: ${orig}`)\n // console.log(`O1: ${s}`)\n // console.log(`O2: ${clean}\\n`)\n // }\n\n return clean\n}\n\n/**\n * Generate a string key for the given string by replacing special characters with\n * underscores and converting other characters to lowercase.\n */\nexport function genStringKey(prefix: string, s: string): string {\n checkInvisibleCharacters(s)\n\n let key = s.toLowerCase()\n key = key.replace(/ – /g, '_') // e.g. 'Data – Satellite' (with emdash) -> 'data_satellite'\n key = key.replace(/ \\/ /g, '_per_') // e.g. 'CO2 / TJ' -> 'co2_per_tj'\n key = key.replace(/ /g, '_')\n key = key.replace('₂', '2') // e.g. 'CO₂' -> 'co2'\n key = key.replace('<sub>2</sub>', '2') // e.g. 'CO<sub>2</sub>' -> 'co2'\n key = key.replace(/\\$\\//g, 'dollars_per_') // e.g. '$/year' -> 'dollars_per_year'\n key = key.replace(/%\\//g, 'pct_per_') // e.g. '%/year' -> 'pct_per_year'\n key = key.replace(/\\//g, '_per_') // e.g. 'CO2/TJ' -> 'co2_per_tj'\n key = key.replace(/º/g, 'degrees_') // e.g. 'ºC' -> 'degrees_c'\n key = key.replace(/\\*/g, '_') // e.g. 'CO2*year' -> 'co2_year'\n key = key.replace(/%/g, 'pct') // e.g. '%' -> 'pct'\n key = key.replace(/\\$/g, 'dollars') // e.g. '$' -> 'dollars'\n key = key.replace(/&/g, 'and') // e.g. 'Actions & Outcomes' -> 'actions_and_outcomes'\n key = key.replace(/\\//g, 'per') // e.g. 'Gigatons CO2/year' -> 'gigatons_co2_per_year'\n key = key.replace(/:/g, '') // e.g. 'Net:' -> 'net'\n key = key.replace(/\\./g, '') // e.g. 'U.S. Units' -> 'us_units'\n key = key.replace(/-/g, '_') // e.g. 'some-thing' -> 'some_thing'\n key = key.replace(/—/g, '_') // endash to underscore\n key = key.replace(/–/g, '_') // emdash to underscore\n key = key.replace(/,/g, '')\n key = key.replace(/\\(/g, '')\n key = key.replace(/\\)/g, '')\n key = key.replace(/\\\\n/g, '')\n key = key.replace(/<br>/g, '_')\n return `${prefix}__${key}`\n}\n\n/**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * These files are currently saved as plain JS (ES6) files. The only difference compared\n * to JSON files is these JS files start with `export default`, so converting to JSON is\n * as trivial as stripping those two words, if needed.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param records The string records.\n * //@param xlatLangs The set of languages that are configured for translation.\n */\nfunction writeLangJsFiles(\n context: BuildContext,\n dstDir: string,\n records: Map<StringKey, StringRecord>\n // xlatLangs: Map<LangCode, LangSpec>\n): void {\n const xlatMap: XlatMap = new Map()\n const sortedRecords = getSortedRecords(records)\n\n // const baseStringForKey = (key: StringKey) => {\n // const record = records.get(key)\n // if (!record) {\n // throw new Error(`No base string found for key=${key}`)\n // }\n // return record.str\n // }\n\n // Add base (e.g., English) strings that were gathered from the config files\n const enStrings: StringMap = new Map()\n for (const record of sortedRecords) {\n const s = record.str\n enStrings.set(record.key, utf8SubscriptToHtml(record.key, s))\n }\n // TODO: Don't assume English, make the base language configurable\n xlatMap.set('en', enStrings)\n\n // TODO: Enable support for translation files (for now, we only write base strings)\n // const hasSecondary =\n // existsSync(projectFilePath('localization', 'graph-descriptions')) &&\n // existsSync(projectFilePath('localization', 'input-descriptions'))\n // for (const lang of xlatLangs.keys()) {\n // const langStrings: StringMap = new Map()\n\n // let poMsgs: Map<string, string>\n // const primaryMsgs = readXlatPoFile('primary', lang)\n // if (hasSecondary) {\n // const graphMsgs = readXlatPoFile('graph-descriptions', lang)\n // const inputMsgs = readXlatPoFile('input-descriptions', lang)\n // poMsgs = new Map([...primaryMsgs, ...graphMsgs, ...inputMsgs])\n // } else {\n // poMsgs = primaryMsgs\n // }\n\n // const xlatStringForKey = (key: StringKey) => {\n // return poMsgs.get(key)\n // }\n\n // // Add the translation of each English string.\n // for (const record of sortedRecords) {\n // if (record.grouping !== 'primary') {\n // // Only include secondary strings (graph and input descriptions) if they are\n // // explicitly requested for this language; if not included, the English\n // // descriptions will be used as a fallback\n // if (xlatLangs.get(lang).includeSecondary !== true) {\n // continue\n // }\n // }\n\n // const xlatStr = xlatStringForKey(record.key)\n // if (xlatStr) {\n // // Add the translated string\n // const s = xlatStr\n // langStrings.set(record.key, utf8SubscriptToHtml(record.key, s))\n // } else {\n // // No translation for this string. We don't add the English string to\n // // map as a fallback. Instead, we configure the i18n library to use\n // // English strings as a fallback at runtime.\n // // console.warn(`WARNING: No translated string for lang=${lang} id=${stringObj.id}`)\n // }\n // }\n\n // xlatMap.set(lang, langStrings)\n // }\n\n // Write a JS file for each language for use in the core package\n for (const lang of xlatMap.keys()) {\n const stringsForLang = xlatMap.get(lang)\n const stringsObj = Object.fromEntries(stringsForLang)\n const json = JSON.stringify(stringsObj, null, 2)\n context.writeStagedFile('strings', dstDir, `${lang}.js`, `export default ${json}`)\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport type InputVarId = string\nexport type OutputVarId = string\n\n/**\n * Helper function that converts a Vensim variable or subscript name\n * into a valid C identifier as used by SDE.\n * TODO: Import helper function from `sdeverywhere` package instead\n */\nfunction sdeNameForVensimName(name: string): string {\n return (\n '_' +\n name\n .trim()\n .replace(/\"/g, '_')\n .replace(/\\s+!$/g, '!')\n .replace(/\\s/g, '_')\n .replace(/,/g, '_')\n .replace(/-/g, '_')\n .replace(/\\./g, '_')\n .replace(/\\$/g, '_')\n .replace(/'/g, '_')\n .replace(/&/g, '_')\n .replace(/%/g, '_')\n .replace(/\\//g, '_')\n .replace(/\\|/g, '_')\n .toLowerCase()\n )\n}\n\n/**\n * Helper function that converts a Vensim variable name (possibly containing\n * subscripts) into a valid C identifier as used by SDE.\n * TODO: Import helper function from `sdeverywhere` package instead\n */\nexport function sdeNameForVensimVarName(varName: string): string {\n const m = varName.match(/([^[]+)(?:\\[([^\\]]+)\\])?/)\n if (!m) {\n throw new Error(`Invalid Vensim name: ${varName}`)\n }\n let id = sdeNameForVensimName(m[1])\n if (m[2]) {\n const subscripts = m[2].split(',').map(x => sdeNameForVensimName(x))\n id += `[${subscripts.join('][')}]`\n }\n\n return id\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext } from './context'\nimport { sdeNameForVensimVarName } from './var-names'\n\n/**\n * Write the `model-spec.ts` file used by the core package to initialize the model.\n */\nexport function writeModelSpec(context: ConfigContext, dstDir: string): void {\n // Create ordered arrays of inputs and outputs\n const inputVarIds = context.getOrderedInputs().map(i => sdeNameForVensimVarName(i.varName))\n const outputVarIds = context.getOrderedOutputs().map(o => sdeNameForVensimVarName(o.varName))\n\n // Generate the `model-spec.ts` file\n let tsContent = ''\n function emit(s: string): void {\n tsContent += s + '\\n'\n }\n\n emit('// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!')\n emit(`export const inputVarIds: string[] = ${JSON.stringify(inputVarIds, null, 2)}`)\n emit(`export const outputVarIds: string[] = ${JSON.stringify(outputVarIds, null, 2)}`)\n\n // Write the `model-spec.ts` file to the staged directory\n context.writeStagedFile('model', dstDir, 'model-spec.ts', tsContent)\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { dirname, resolve as resolvePath } from 'path'\nimport { fileURLToPath } from 'url'\n\nimport type { ConfigContext } from './context'\nimport { generateGraphSpecs } from './gen-graphs'\nimport { generateInputsConfig } from './gen-inputs'\nimport type { GraphId, GraphSpec, InputId, InputSpec } from './spec-types'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport interface ConfigSpecs {\n graphSpecs: Map<GraphId, GraphSpec>\n inputSpecs: Map<InputId, InputSpec>\n}\n\n/**\n * Convert the CSV files in the `config` directory to config specs that can be\n * used in the core package.\n */\nexport function generateConfigSpecs(context: ConfigContext): ConfigSpecs {\n // Convert `graphs.csv` to graph specs\n context.log('verbose', ' Reading graph specs')\n const graphSpecs = generateGraphSpecs(context)\n\n // Convert `inputs.csv` to input specs\n context.log('verbose', ' Reading input specs')\n const inputSpecs = generateInputsConfig(context)\n\n // Include extra output variables that should be included in the generated\n // model even though they are not referenced in any graph specs\n context.log('verbose', ' Reading extra output variables')\n const extraOutputsCsv = context.readConfigCsvFile('outputs')\n for (const row of extraOutputsCsv) {\n const varName = row['variable name']\n if (varName) {\n context.addOutputVariable(varName)\n }\n }\n\n return {\n graphSpecs,\n inputSpecs\n }\n}\n\n/**\n * Write the `config-specs.ts` file to the given destination directory.\n */\nexport function writeConfigSpecs(context: ConfigContext, config: ConfigSpecs, dstDir: string): void {\n // Generate one big string containing the TypeScript source that will be\n // loaded by `config.ts` at runtime\n let tsContent = ''\n function emit(s: string): void {\n tsContent += s + '\\n'\n }\n\n emit('// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!')\n emit('')\n emit(`import type { GraphSpec, InputSpec } from './spec-types'`)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n function emitArray(type: string, values: Iterable<any>): void {\n const varName = type.charAt(0).toLowerCase() + type.slice(1) + 's'\n const array = Array.from(values)\n const json = JSON.stringify(array, null, 2)\n emit('')\n emit(`export const ${varName}: ${type}[] = ${json}`)\n }\n\n emitArray('GraphSpec', config.graphSpecs.values())\n emitArray('InputSpec', config.inputSpecs.values())\n\n // Write the `config-specs.ts` file\n context.writeStagedFile('config', dstDir, 'config-specs.ts', tsContent)\n}\n\n/**\n * Write the `spec-types.ts` file to the given destination directory.\n */\nexport function writeSpecTypes(context: ConfigContext, dstDir: string): void {\n // Copy the `spec-types.ts` file. Currently we keep the full source of the file\n // in the `dist` directory for this package so that we can access it here.\n const tsFile = 'spec-types.ts'\n const tsPath = resolvePath(__dirname, tsFile)\n const tsContent = readFileSync(tsPath, 'utf8')\n context.writeStagedFile('config', dstDir, tsFile, tsContent)\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport function optionalString(stringValue?: string): string | undefined {\n if (stringValue !== undefined && stringValue.length > 0) {\n return stringValue\n } else {\n return undefined\n }\n}\n\nexport function optionalNumber(stringValue?: string): number | undefined {\n if (stringValue !== undefined && stringValue.length > 0) {\n return Number(stringValue)\n } else {\n return undefined\n }\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext, CsvRow } from './context'\nimport { optionalNumber, optionalString } from './read-config'\nimport type {\n GraphAlternateSpec,\n GraphDatasetSpec,\n GraphId,\n GraphKind,\n GraphLegendItemSpec,\n GraphSide,\n GraphSpec,\n LineStyle,\n LineStyleModifier,\n StringKey,\n UnitSystem\n} from './spec-types'\nimport { genStringKey, htmlToUtf8 } from './strings'\nimport { sdeNameForVensimVarName } from './var-names'\n\n/**\n * Convert the `config/graphs.csv` file to config specs that can be used in\n * the core package.\n */\nexport function generateGraphSpecs(context: ConfigContext): Map<GraphId, GraphSpec> {\n // TODO: Optionally read the graph descriptions from `graphs.md`\n // let descriptions: Map<GraphId, Description>\n // if (useDescriptions) {\n // descriptions = readGraphDescriptions(context)\n // } else {\n // descriptions = undefined\n // }\n\n // Convert `graphs.csv` to graph specs\n const graphsCsv = context.readConfigCsvFile('graphs')\n const graphSpecs: Map<GraphId, GraphSpec> = new Map()\n for (const row of graphsCsv) {\n const spec = graphSpecFromCsv(row, context)\n if (spec) {\n graphSpecs.set(spec.id, spec)\n }\n }\n\n return graphSpecs\n}\n\nfunction graphSpecFromCsv(g: CsvRow, context: ConfigContext): GraphSpec | undefined {\n const strings = context.strings\n\n // TODO: For now, all strings use the same \"layout\" specifier; this could be customized\n // to provide a \"maximum length\" hint for a group of strings to the translation tool\n const layout = 'default'\n\n function requiredString(key: string): string {\n const value = g[key]\n if (value === undefined || typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`Must specify '${key}' for graph ${g.id}`)\n }\n return value\n }\n\n // Extract required fields\n const graphIdParts = requiredString('id').split(';')\n const graphId = graphIdParts[0]\n const graphIdBaseParts = graphId.split('-')\n const graphBaseId = graphIdBaseParts[0]\n const title = requiredString('graph title')\n\n // Extract optional fields\n const menuTitle = optionalString(g['menu title'])\n const miniTitle = optionalString(g['mini title'])\n const parentMenu = optionalString(g['parent menu'])\n const description = optionalString(g['description'])\n const kindString = optionalString(g['kind'])\n\n // Skip rows that have an empty `parent menu` value; this can be used to omit graphs\n // from the product until they've been fully reviewed and approved\n if (!parentMenu) {\n context.log('info', `Skipping graph ${graphId} (${title})`)\n return undefined\n }\n\n // TODO: Check for a description\n // let desc: Description\n // if (descriptions) {\n // desc = descriptions.get(graphBaseId)\n // if (!desc) {\n // throw new Error(`Graph description for ${graphBaseId} not found in graphs.md`)\n // }\n // }\n\n // Helper that creates a string key prefix\n const key = (kind: string) => `graph_${graphBaseId.padStart(3, '0')}_${kind}`\n\n // Helper that creates a string context\n const strCtxt = (kind: string) => {\n const parent = htmlToUtf8(parentMenu).replace('&amp;', '&')\n const displayTitle = htmlToUtf8(menuTitle || title).replace('&amp;', '&')\n return `Graph ${kind}: ${parent} > ${displayTitle}`\n }\n\n const titleKey = strings.add(key('title'), title, layout, strCtxt('Title'))\n let menuTitleKey: StringKey\n if (menuTitle) {\n menuTitleKey = strings.add(key('menu_title'), menuTitle, layout, strCtxt('Menu Item'))\n }\n\n let miniTitleKey: StringKey\n if (miniTitle) {\n miniTitleKey = strings.add(key('mini_title'), miniTitle, layout, strCtxt('Title (for Mini View)'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'graph-descriptions')\n }\n\n // TODO: Validate kind?\n const kind: GraphKind = kindString\n\n // TODO: Validate graph side?\n const sideString = optionalString(g['side'])\n const side: GraphSide = sideString\n\n // Determine if this graph is associated with a particular unit system and\n // has an alternate version\n const unitsString = optionalString(g['units'])\n const altIdString = optionalString(g['alternate'])\n let unitSystem: UnitSystem\n let alternates: GraphAlternateSpec[]\n if (unitsString && altIdString) {\n if (unitsString === 'metric') {\n // This graph is metric with a U.S. alternate\n unitSystem = 'metric'\n alternates = [\n {\n id: altIdString,\n unitSystem: 'us'\n }\n ]\n } else if (unitsString === 'us') {\n // This graph is U.S. with a metric alternate\n unitSystem = 'us'\n alternates = [\n {\n id: altIdString,\n unitSystem: 'metric'\n }\n ]\n }\n }\n\n const xMin = optionalNumber(g['x axis min']) || context.graphDefaultMinTime\n const xMax = optionalNumber(g['x axis max']) || context.graphDefaultMaxTime\n const xAxisLabel = optionalString(g['x axis label'])\n let xAxisLabelKey: StringKey\n if (xAxisLabel) {\n xAxisLabelKey = strings.add(genStringKey('graph_xaxis_label', xAxisLabel), xAxisLabel, layout, 'Graph X-Axis Label')\n }\n\n const yMin = optionalNumber(g['y axis min']) || 0\n const yMax = optionalNumber(g['y axis max'])\n const ySoftMax = optionalNumber(g['y axis soft max'])\n const yFormat = optionalString(g['y axis format']) || '.0f'\n const yAxisLabel = optionalString(g['y axis label'])\n let yAxisLabelKey: StringKey\n if (yAxisLabel) {\n yAxisLabelKey = strings.add(genStringKey('graph_yaxis_label', yAxisLabel), yAxisLabel, layout, 'Graph Y-Axis Label')\n }\n\n const datasets: GraphDatasetSpec[] = []\n interface Overrides {\n sourceName?: string\n colorId?: string\n }\n function addDataset(index: number, overrides?: Overrides): void {\n const plotKey = (name: string) => `plot ${index} ${name}`\n const varName = g[plotKey('variable')]\n if (!varName) {\n return\n }\n\n const varId = sdeNameForVensimVarName(varName)\n const externalSourceName = overrides?.sourceName || optionalString(g[plotKey('source')])\n const datasetLabel = optionalString(g[plotKey('label')])\n let labelKey: StringKey\n if (datasetLabel) {\n labelKey = strings.add(\n genStringKey('graph_dataset_label', datasetLabel),\n datasetLabel,\n layout,\n 'Graph Dataset Label'\n )\n }\n\n const colorId = overrides?.colorId || requiredString(plotKey('color'))\n const hexColor = context.getHexColorForId(colorId)\n if (!hexColor) {\n throw new Error(`Graph ${graphId} references an unknown color ${colorId}`)\n }\n\n const lineStyleAndModString = optionalString(g[plotKey('style')]) || 'line'\n const lineStyleParts = lineStyleAndModString.split(';')\n const lineStyleString = lineStyleParts[0]\n const lineStyleModifierString = lineStyleParts.length > 1 ? lineStyleParts[1] : undefined\n\n // TODO: Validate line style and modifiers?\n const lineStyle: LineStyle = lineStyleString\n let lineStyleModifiers: ReadonlyArray<LineStyleModifier>\n if (lineStyleModifierString) {\n // TODO: For now, we assume at most one modifier; should change this to allow > 1\n lineStyleModifiers = [lineStyleModifierString]\n }\n\n if (externalSourceName && externalSourceName !== 'Ref') {\n // Add the variable to the set of vars to be included in the static data file\n context.addStaticVariable(externalSourceName, varName)\n } else {\n // Add the variable if this is a normal model output (in which case the source\n // name is undefined) or if it will be captured as \"Ref\" (baseline) values\n context.addOutputVariable(varName)\n }\n\n const datasetSpec: GraphDatasetSpec = {\n varId,\n varName,\n externalSourceName,\n labelKey,\n color: hexColor,\n lineStyle,\n lineStyleModifiers\n }\n datasets.push(datasetSpec)\n }\n\n // Add each dataset configured in graphs.csv\n for (let i = 1; i <= 11; i++) {\n addDataset(i)\n }\n\n // Only show legend items for datasets that have a label (i.e., ignore\n // some special ones, like the ones used to show dotted reference lines)\n const legendItems: GraphLegendItemSpec[] = datasets\n .filter(dataset => dataset.labelKey?.length > 0)\n .map(dataset => {\n return {\n color: dataset.color,\n labelKey: dataset.labelKey\n }\n })\n\n const graphSpec: GraphSpec = {\n id: graphId,\n kind,\n titleKey,\n miniTitleKey,\n menuTitleKey,\n descriptionKey,\n side,\n unitSystem,\n alternates,\n xMin,\n xMax,\n xAxisLabelKey,\n yMin,\n yMax,\n ySoftMax,\n yAxisLabelKey,\n yFormat,\n datasets,\n legendItems\n }\n\n // Add the graph to the menu\n // context.addGraphMenuItem(graphSpec, parentMenu)\n\n return graphSpec\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport type { ConfigContext, CsvRow } from './context'\nimport { optionalNumber, optionalString } from './read-config'\nimport type { InputId, InputSpec, SliderSpec, StringKey, SwitchSpec } from './spec-types'\nimport { genStringKey, htmlToUtf8 } from './strings'\nimport { sdeNameForVensimVarName } from './var-names'\n\n// TODO: For now, all strings use the same \"layout\" specifier; this could be customized\n// to provide a \"maximum length\" hint for a group of strings to the translation tool\nconst layout = 'default'\n\n/**\n * Convert the `config/inputs.csv` file to config specs that can be used in\n * the core package.\n */\nexport function generateInputsConfig(context: ConfigContext): Map<InputId, InputSpec> {\n // Convert `inputs.csv` to input specs\n const inputsCsv = context.readConfigCsvFile('inputs')\n const inputSpecs: Map<InputId, InputSpec> = new Map()\n for (const row of inputsCsv) {\n const spec = inputSpecFromCsv(row, context)\n if (spec) {\n inputSpecs.set(spec.id, spec)\n }\n }\n\n return inputSpecs\n}\n\nfunction inputSpecFromCsv(r: CsvRow, context: ConfigContext): InputSpec | undefined {\n const strings = context.strings\n\n function requiredString(key: string): string {\n const value = r[key]\n if (value === undefined || typeof value !== 'string' || value.trim().length === 0) {\n throw new Error(`Must specify '${key}' for input ${r.id}`)\n }\n return value\n }\n\n function requiredNumber(key: string): number {\n const stringValue = requiredString(key)\n const numValue = Number(stringValue)\n if (numValue === undefined) {\n throw new Error(`Must specify numeric '${key}' for input ${r.id}`)\n }\n return numValue\n }\n\n // Extract required fields\n const inputIdParts = requiredString('id').split(';')\n const inputId = inputIdParts[0]\n const viewId = optionalString(r['viewid'])\n const label = optionalString(r['label']) || ''\n const inputType = requiredString('input type')\n\n // Skip rows that have an empty `viewid` value; this can be used to omit inputs\n // from the product until they've been fully reviewed and approved\n if (!viewId) {\n context.log('info', `Skipping input ${inputId} (${label})`)\n return undefined\n }\n\n // Extract optional fields\n const description = optionalString(r['description'])\n\n // Helper that creates a string key prefix\n const key = (kind: string) => `input_${inputId.padStart(3, '0')}_${kind}`\n\n // For now, use the group name defined in `inputs.csv`\n const groupTitle = optionalString(r['group name'])\n if (!groupTitle) {\n throw new Error(`Must specify 'group name' for input ${inputId}`)\n }\n const groupTitleKey = genStringKey('input_group_title', groupTitle)\n strings.add(groupTitleKey, groupTitle, layout, 'Input Group Title')\n\n let typeLabel: string\n switch (inputType) {\n case 'slider':\n typeLabel = 'Slider'\n break\n case 'switch':\n typeLabel = 'Switch'\n break\n case 'checkbox':\n typeLabel = 'Checkbox'\n break\n case 'checkbox group':\n typeLabel = 'Checkbox Group'\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n // Helper that creates a string context\n const strCtxt = (kind: string) => {\n const labelText = htmlToUtf8(label).replace('&amp;', '&')\n return `${typeLabel} ${kind}: ${groupTitle} > ${labelText}`\n }\n\n const labelKey = strings.add(key('label'), label, layout, strCtxt('Label'))\n\n const listingLabel = optionalString(r['listing label'])\n let listingLabelKey: StringKey\n if (listingLabel) {\n listingLabelKey = strings.add(key('action_label'), listingLabel, layout, strCtxt('Action Label'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'input-descriptions')\n }\n\n // Converts a slider row in `inputs.csv` to a `SliderSpec`\n function sliderSpecFromCsv(): SliderSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const defaultValue = requiredNumber('slider/switch default')\n const minValue = requiredNumber('slider min')\n const maxValue = requiredNumber('slider max')\n const step = requiredNumber('slider step')\n const reversed = optionalString(r['reversed']) === 'yes'\n\n if (defaultValue < minValue || defaultValue > maxValue) {\n let e = `Default value for slider ${inputId} is out of range: `\n e += `default=${defaultValue} min=${minValue} max=${maxValue}`\n throw new Error(e)\n }\n context.addInputVariable(inputId, varName, defaultValue, minValue, maxValue)\n\n const format = optionalString(r['format']) || '.0f'\n\n const units = optionalString(r['units'])\n let unitsKey: StringKey\n if (units) {\n unitsKey = strings.add(genStringKey('input_units', units), units, layout, 'Slider Units')\n }\n\n const rangeInfo = getSliderRangeInfo(r, maxValue, context)\n const rangeLabelKeys = rangeInfo.labelKeys\n const rangeDividers = rangeInfo.dividers\n\n return {\n kind: 'slider',\n id: inputId,\n varId,\n varName,\n defaultValue,\n minValue,\n maxValue,\n step,\n reversed,\n labelKey,\n listingLabelKey,\n descriptionKey,\n unitsKey,\n rangeLabelKeys,\n rangeDividers,\n format\n }\n }\n\n // Converts a switch row in `inputs.csv` to a `SwitchSpec`\n function switchSpecFromCsv(): SwitchSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const onValue = requiredNumber('enabled value')\n const offValue = requiredNumber('disabled value')\n const defaultValue = requiredNumber('slider/switch default')\n if (defaultValue !== onValue && defaultValue !== offValue) {\n throw new Error(\n `Invalid default value for switch ${inputId}: off=${offValue} on=${onValue} default=${defaultValue}`\n )\n }\n\n const minValue = Math.min(offValue, onValue)\n const maxValue = Math.max(offValue, onValue)\n context.addInputVariable(inputId, varName, defaultValue, minValue, maxValue)\n\n // The `controlled input ids` field dictates which rows are active\n // when this switch is on or off. Examples of the format of this field:\n // 1;2;3|4;5;6\n // 1|2;3;4;5\n // |1\n // On the left side of the '|' are the rows that are active when the\n // switch is in an off position, and on the right side are the rows\n // that are active when the switch is in an on position. Usually the\n // \"active when off\" rows are above the switch in the UI, and the\n // \"active when on\" rows are below the switch.\n const controlledInputIds = requiredString('controlled input ids')\n const controlledParts = controlledInputIds.split('|')\n const rowsActiveWhenOff = controlledParts[0].split(';').filter(id => id.trim().length > 0)\n const rowsActiveWhenOn = controlledParts[1].split(';').filter(id => id.trim().length > 0)\n\n return {\n kind: 'switch',\n id: inputId,\n varId,\n varName,\n labelKey,\n listingLabelKey,\n descriptionKey,\n defaultValue,\n offValue,\n onValue,\n slidersActiveWhenOff: rowsActiveWhenOff,\n slidersActiveWhenOn: rowsActiveWhenOn\n }\n }\n\n // Call a different converter function depending on the input type\n let inputSpec: SliderSpec | SwitchSpec\n switch (inputType) {\n case 'slider': {\n inputSpec = sliderSpecFromCsv()\n break\n }\n case 'switch':\n case 'checkbox': {\n inputSpec = switchSpecFromCsv()\n break\n }\n case 'checkbox group':\n // TODO\n // XXX: For now, we specify the checkbox IDs as a semicolon separated list\n // in the \"varname\" cell\n // const checkboxIds = row['varname'].split(';')\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n return inputSpec\n}\n\ninterface SliderRangeInfo {\n labelKeys: StringKey[]\n dividers: number[]\n}\n\nfunction getSliderRangeInfo(r: CsvRow, maxValue: number, context: ConfigContext): SliderRangeInfo {\n const strings = context.strings\n const labelKeys: StringKey[] = []\n const dividers: number[] = []\n\n // Get all labels to determine the number of ranges\n let rangeNum = 1\n while (rangeNum <= 5) {\n const label = optionalString(r[`range ${rangeNum} label`])\n if (!label) {\n break\n }\n const labelKey = strings.add(genStringKey('input_range', label), label, layout, 'Slider Range Label')\n if (!labelKey) {\n break\n }\n labelKeys.push(labelKey)\n rangeNum++\n }\n\n // Find dividing points between ranges; the absence of a final dividing point\n // indicates the use of discrete values\n const numRanges = rangeNum - 1\n for (rangeNum = 2; rangeNum <= numRanges; rangeNum++) {\n let divider = optionalNumber(r[`range ${rangeNum} start`])\n if (divider === undefined) {\n // Fall back on the slider max value when a divider is missing\n divider = maxValue\n }\n dividers.push(divider)\n }\n\n return {\n labelKeys,\n dividers\n }\n}\n"],"mappings":"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","names":["joinPath","layout","layout","readFileSync","layout","key","kind","key","__dirname","readFileSync","joinPath"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/plugin-config",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "files": [
5
5
  "dist/**",
6
6
  "template-config/**"
@@ -22,14 +22,14 @@
22
22
  "sanitize-html": "^2.7.1"
23
23
  },
24
24
  "peerDependencies": {
25
- "@sdeverywhere/build": "^0.3.0"
25
+ "@sdeverywhere/build": "^0.3.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@sdeverywhere/build": "*",
29
29
  "@types/byline": "^4.2.33",
30
30
  "@types/dedent": "^0.7.0",
31
31
  "@types/marked": "^4.0.1",
32
- "@types/node": "^16.11.7",
32
+ "@types/node": "^20.5.7",
33
33
  "@types/sanitize-html": "^2.6.2",
34
34
  "@types/temp": "^0.9.1",
35
35
  "dedent": "^0.7.0",
@@ -59,6 +59,7 @@
59
59
  "bundle": "tsup",
60
60
  "copy-types": "cp src/spec-types.ts dist",
61
61
  "build": "run-s bundle copy-types",
62
- "ci:build": "run-s clean lint prettier:check test:ci type-check build"
62
+ "docs": "../../scripts/gen-docs.js",
63
+ "ci:build": "run-s clean lint prettier:check test:ci type-check build docs"
63
64
  }
64
65
  }