@sdeverywhere/plugin-config 0.2.4 → 0.2.5

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/dist/index.cjs CHANGED
@@ -225,21 +225,19 @@ function sdeNameForVensimVarName(varName) {
225
225
  let id = sdeNameForVensimName(m[1]);
226
226
  if (m[2]) {
227
227
  const subscripts = m[2].split(",").map((x) => sdeNameForVensimName(x));
228
- id += `[${subscripts.join("][")}]`;
228
+ id += `[${subscripts.join(",")}]`;
229
229
  }
230
230
  return id;
231
231
  }
232
232
 
233
233
  // src/context.ts
234
234
  var ConfigContext = class {
235
- constructor(buildContext, configDir, strings, colorMap, graphDefaultMinTime, graphDefaultMaxTime, datFiles) {
235
+ constructor(buildContext, configDir, strings, colorMap, modelOptions) {
236
236
  this.buildContext = buildContext;
237
237
  this.configDir = configDir;
238
238
  this.strings = strings;
239
239
  this.colorMap = colorMap;
240
- this.graphDefaultMinTime = graphDefaultMinTime;
241
- this.graphDefaultMaxTime = graphDefaultMaxTime;
242
- this.datFiles = datFiles;
240
+ this.modelOptions = modelOptions;
243
241
  this.inputSpecs = /* @__PURE__ */ new Map();
244
242
  this.outputVarNames = /* @__PURE__ */ new Map();
245
243
  this.staticVarNames = /* @__PURE__ */ new Map();
@@ -339,6 +337,17 @@ function createConfigContext(buildContext, configDir) {
339
337
  const prepDir = buildContext.config.prepDir;
340
338
  const projDir = buildContext.config.rootDir;
341
339
  const datFiles = origDatFiles.map((f) => (0, import_path.join)((0, import_path.relative)(prepDir, projDir), f));
340
+ const bundleListing = modelCsv["bundle listing"] === "true";
341
+ const customLookups = modelCsv["custom lookups"] === "true";
342
+ const customOutputs = modelCsv["custom outputs"] === "true";
343
+ const modelOptions = {
344
+ graphDefaultMinTime,
345
+ graphDefaultMaxTime,
346
+ datFiles,
347
+ bundleListing,
348
+ customLookups,
349
+ customOutputs
350
+ };
342
351
  const strings = readStringsCsv(configDir);
343
352
  const colorsCsv = readConfigCsvFile(configDir, "colors");
344
353
  const colors = /* @__PURE__ */ new Map();
@@ -347,7 +356,7 @@ function createConfigContext(buildContext, configDir) {
347
356
  const hexColor = row["hex code"];
348
357
  colors.set(colorId, hexColor);
349
358
  }
350
- return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles);
359
+ return new ConfigContext(buildContext, configDir, strings, colors, modelOptions);
351
360
  }
352
361
  function configFilePath(configDir, name, ext) {
353
362
  return (0, import_path.join)(configDir, `${name}.${ext}`);
@@ -496,8 +505,9 @@ function graphSpecFromCsv(g, context) {
496
505
  ];
497
506
  }
498
507
  }
499
- const xMin = optionalNumber(g["x axis min"]) || context.graphDefaultMinTime;
500
- const xMax = optionalNumber(g["x axis max"]) || context.graphDefaultMaxTime;
508
+ const modelOptions = context.modelOptions;
509
+ const xMin = optionalNumber(g["x axis min"]) || modelOptions.graphDefaultMinTime;
510
+ const xMax = optionalNumber(g["x axis max"]) || modelOptions.graphDefaultMaxTime;
501
511
  const xAxisLabel = optionalString(g["x axis label"]);
502
512
  let xAxisLabelKey;
503
513
  if (xAxisLabel) {
@@ -878,6 +888,7 @@ async function processModelConfig(buildContext, options) {
878
888
  }
879
889
  }
880
890
  const context = createConfigContext(buildContext, options.config);
891
+ const modelOptions = context.modelOptions;
881
892
  context.log("info", "Generating files...");
882
893
  const configSpecs = generateConfigSpecs(context);
883
894
  if (outConfigSpecsDir) {
@@ -899,7 +910,10 @@ async function processModelConfig(buildContext, options) {
899
910
  return {
900
911
  inputs: context.getOrderedInputs(),
901
912
  outputs: context.getOrderedOutputs(),
902
- datFiles: context.datFiles,
913
+ datFiles: modelOptions.datFiles,
914
+ bundleListing: modelOptions.bundleListing,
915
+ customLookups: modelOptions.customLookups,
916
+ customOutputs: modelOptions.customOutputs,
903
917
  options: options.spec
904
918
  };
905
919
  }
@@ -1 +1 @@
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 * Additional options included with the SDE `spec.json` file.\n * @hidden This is not part of the public API because we are aiming to merge\n * the `spec.json` file format with the `sde.config.js` format. This is exposed\n * temporarily to allow for configuring additional settings like `directData`\n * for which we don't currently have a way to configure via config files.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec?: { [key: string]: any }\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 options: options.spec\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"]}
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 * Additional options included with the SDE `spec.json` file.\n * @hidden This is not part of the public API because we are aiming to merge\n * the `spec.json` file format with the `sde.config.js` format. This is exposed\n * temporarily to allow for configuring additional settings like `directData`\n * for which we don't currently have a way to configure via config files.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec?: { [key: string]: any }\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 const modelOptions = context.modelOptions\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: modelOptions.datFiles,\n bundleListing: modelOptions.bundleListing,\n customLookups: modelOptions.customLookups,\n customOutputs: modelOptions.customOutputs,\n options: options.spec\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 interface ModelOptions {\n readonly graphDefaultMinTime: number\n readonly graphDefaultMaxTime: number\n readonly datFiles: string[]\n readonly bundleListing: boolean\n readonly customLookups: boolean\n readonly customOutputs: boolean\n}\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 modelOptions: ModelOptions\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 other boolean properties from `model.csv`\n // TODO: If customLookups is true, see if there is a `config/custom-lookups.csv` file\n // and if so, make an array of variable names instead of setting a boolean in `spec.json`.\n // (Same thing for customOutputs.)\n const bundleListing = modelCsv['bundle listing'] === 'true'\n const customLookups = modelCsv['custom lookups'] === 'true'\n const customOutputs = modelCsv['custom outputs'] === 'true'\n\n const modelOptions: ModelOptions = {\n graphDefaultMinTime,\n graphDefaultMaxTime,\n datFiles,\n bundleListing,\n customLookups,\n customOutputs\n }\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, modelOptions)\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 modelOptions = context.modelOptions\n const xMin = optionalNumber(g['x axis min']) || modelOptions.graphDefaultMinTime\n const xMax = optionalNumber(g['x axis max']) || modelOptions.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"]}
package/dist/index.js CHANGED
@@ -186,21 +186,19 @@ function sdeNameForVensimVarName(varName) {
186
186
  let id = sdeNameForVensimName(m[1]);
187
187
  if (m[2]) {
188
188
  const subscripts = m[2].split(",").map((x) => sdeNameForVensimName(x));
189
- id += `[${subscripts.join("][")}]`;
189
+ id += `[${subscripts.join(",")}]`;
190
190
  }
191
191
  return id;
192
192
  }
193
193
 
194
194
  // src/context.ts
195
195
  var ConfigContext = class {
196
- constructor(buildContext, configDir, strings, colorMap, graphDefaultMinTime, graphDefaultMaxTime, datFiles) {
196
+ constructor(buildContext, configDir, strings, colorMap, modelOptions) {
197
197
  this.buildContext = buildContext;
198
198
  this.configDir = configDir;
199
199
  this.strings = strings;
200
200
  this.colorMap = colorMap;
201
- this.graphDefaultMinTime = graphDefaultMinTime;
202
- this.graphDefaultMaxTime = graphDefaultMaxTime;
203
- this.datFiles = datFiles;
201
+ this.modelOptions = modelOptions;
204
202
  this.inputSpecs = /* @__PURE__ */ new Map();
205
203
  this.outputVarNames = /* @__PURE__ */ new Map();
206
204
  this.staticVarNames = /* @__PURE__ */ new Map();
@@ -300,6 +298,17 @@ function createConfigContext(buildContext, configDir) {
300
298
  const prepDir = buildContext.config.prepDir;
301
299
  const projDir = buildContext.config.rootDir;
302
300
  const datFiles = origDatFiles.map((f) => joinPath(relative(prepDir, projDir), f));
301
+ const bundleListing = modelCsv["bundle listing"] === "true";
302
+ const customLookups = modelCsv["custom lookups"] === "true";
303
+ const customOutputs = modelCsv["custom outputs"] === "true";
304
+ const modelOptions = {
305
+ graphDefaultMinTime,
306
+ graphDefaultMaxTime,
307
+ datFiles,
308
+ bundleListing,
309
+ customLookups,
310
+ customOutputs
311
+ };
303
312
  const strings = readStringsCsv(configDir);
304
313
  const colorsCsv = readConfigCsvFile(configDir, "colors");
305
314
  const colors = /* @__PURE__ */ new Map();
@@ -308,7 +317,7 @@ function createConfigContext(buildContext, configDir) {
308
317
  const hexColor = row["hex code"];
309
318
  colors.set(colorId, hexColor);
310
319
  }
311
- return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles);
320
+ return new ConfigContext(buildContext, configDir, strings, colors, modelOptions);
312
321
  }
313
322
  function configFilePath(configDir, name, ext) {
314
323
  return joinPath(configDir, `${name}.${ext}`);
@@ -457,8 +466,9 @@ function graphSpecFromCsv(g, context) {
457
466
  ];
458
467
  }
459
468
  }
460
- const xMin = optionalNumber(g["x axis min"]) || context.graphDefaultMinTime;
461
- const xMax = optionalNumber(g["x axis max"]) || context.graphDefaultMaxTime;
469
+ const modelOptions = context.modelOptions;
470
+ const xMin = optionalNumber(g["x axis min"]) || modelOptions.graphDefaultMinTime;
471
+ const xMax = optionalNumber(g["x axis max"]) || modelOptions.graphDefaultMaxTime;
462
472
  const xAxisLabel = optionalString(g["x axis label"]);
463
473
  let xAxisLabelKey;
464
474
  if (xAxisLabel) {
@@ -839,6 +849,7 @@ async function processModelConfig(buildContext, options) {
839
849
  }
840
850
  }
841
851
  const context = createConfigContext(buildContext, options.config);
852
+ const modelOptions = context.modelOptions;
842
853
  context.log("info", "Generating files...");
843
854
  const configSpecs = generateConfigSpecs(context);
844
855
  if (outConfigSpecsDir) {
@@ -860,7 +871,10 @@ async function processModelConfig(buildContext, options) {
860
871
  return {
861
872
  inputs: context.getOrderedInputs(),
862
873
  outputs: context.getOrderedOutputs(),
863
- datFiles: context.datFiles,
874
+ datFiles: modelOptions.datFiles,
875
+ bundleListing: modelOptions.bundleListing,
876
+ customLookups: modelOptions.customLookups,
877
+ customOutputs: modelOptions.customOutputs,
864
878
  options: options.spec
865
879
  };
866
880
  }
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 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 * Additional options included with the SDE `spec.json` file.\n * @hidden This is not part of the public API because we are aiming to merge\n * the `spec.json` file format with the `sde.config.js` format. This is exposed\n * temporarily to allow for configuring additional settings like `directData`\n * for which we don't currently have a way to configure via config files.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec?: { [key: string]: any }\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 options: options.spec\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"]}
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 * Additional options included with the SDE `spec.json` file.\n * @hidden This is not part of the public API because we are aiming to merge\n * the `spec.json` file format with the `sde.config.js` format. This is exposed\n * temporarily to allow for configuring additional settings like `directData`\n * for which we don't currently have a way to configure via config files.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec?: { [key: string]: any }\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 const modelOptions = context.modelOptions\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: modelOptions.datFiles,\n bundleListing: modelOptions.bundleListing,\n customLookups: modelOptions.customLookups,\n customOutputs: modelOptions.customOutputs,\n options: options.spec\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 interface ModelOptions {\n readonly graphDefaultMinTime: number\n readonly graphDefaultMaxTime: number\n readonly datFiles: string[]\n readonly bundleListing: boolean\n readonly customLookups: boolean\n readonly customOutputs: boolean\n}\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 modelOptions: ModelOptions\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 other boolean properties from `model.csv`\n // TODO: If customLookups is true, see if there is a `config/custom-lookups.csv` file\n // and if so, make an array of variable names instead of setting a boolean in `spec.json`.\n // (Same thing for customOutputs.)\n const bundleListing = modelCsv['bundle listing'] === 'true'\n const customLookups = modelCsv['custom lookups'] === 'true'\n const customOutputs = modelCsv['custom outputs'] === 'true'\n\n const modelOptions: ModelOptions = {\n graphDefaultMinTime,\n graphDefaultMaxTime,\n datFiles,\n bundleListing,\n customLookups,\n customOutputs\n }\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, modelOptions)\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 modelOptions = context.modelOptions\n const xMin = optionalNumber(g['x axis min']) || modelOptions.graphDefaultMinTime\n const xMax = optionalNumber(g['x axis max']) || modelOptions.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.4",
3
+ "version": "0.2.5",
4
4
  "files": [
5
5
  "dist/**",
6
6
  "template-config/**"
@@ -22,7 +22,7 @@
22
22
  "sanitize-html": "^2.7.1"
23
23
  },
24
24
  "peerDependencies": {
25
- "@sdeverywhere/build": "^0.3.1"
25
+ "@sdeverywhere/build": "^0.3.5"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@sdeverywhere/build": "*",
@@ -55,7 +55,7 @@
55
55
  "test": "vitest run",
56
56
  "test:watch": "vitest",
57
57
  "test:ci": "vitest run",
58
- "type-check": "tsc --noEmit -p tsconfig-build.json",
58
+ "type-check": "tsc --noEmit -p tsconfig-test.json",
59
59
  "bundle": "tsup",
60
60
  "copy-types": "cp src/spec-types.ts dist",
61
61
  "build": "run-s bundle copy-types",
@@ -1,2 +1,2 @@
1
- graph default min time,graph default max time,model dat files
2
- 0,100,Data1.dat;Data2.dat
1
+ graph default min time,graph default max time,model dat files,bundle listing,custom lookups,custom outputs
2
+ 0,100,Data1.dat;Data2.dat,false,false,false