@sdeverywhere/plugin-config 0.1.1 → 0.2.0
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 +5 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -22
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/template-config/model.csv +2 -2
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ var import_path3 = require("path");
|
|
|
36
36
|
// src/context.ts
|
|
37
37
|
var import_fs = require("fs");
|
|
38
38
|
var import_path = require("path");
|
|
39
|
-
var import_sync =
|
|
39
|
+
var import_sync = require("csv-parse/sync");
|
|
40
40
|
|
|
41
41
|
// src/strings.ts
|
|
42
42
|
var import_sanitize_html = __toESM(require("sanitize-html"), 1);
|
|
@@ -212,13 +212,11 @@ function sdeNameForVensimVarName(varName) {
|
|
|
212
212
|
|
|
213
213
|
// src/context.ts
|
|
214
214
|
var ConfigContext = class {
|
|
215
|
-
constructor(buildContext, configDir, strings, colorMap,
|
|
215
|
+
constructor(buildContext, configDir, strings, colorMap, graphDefaultMinTime, graphDefaultMaxTime, datFiles) {
|
|
216
216
|
this.buildContext = buildContext;
|
|
217
217
|
this.configDir = configDir;
|
|
218
218
|
this.strings = strings;
|
|
219
219
|
this.colorMap = colorMap;
|
|
220
|
-
this.modelStartTime = modelStartTime;
|
|
221
|
-
this.modelEndTime = modelEndTime;
|
|
222
220
|
this.graphDefaultMinTime = graphDefaultMinTime;
|
|
223
221
|
this.graphDefaultMaxTime = graphDefaultMaxTime;
|
|
224
222
|
this.datFiles = datFiles;
|
|
@@ -282,8 +280,6 @@ var ConfigContext = class {
|
|
|
282
280
|
};
|
|
283
281
|
function createConfigContext(buildContext, configDir) {
|
|
284
282
|
const modelCsv = readConfigCsvFile(configDir, "model")[0];
|
|
285
|
-
const modelStartTime = Number(modelCsv["model start time"]);
|
|
286
|
-
const modelEndTime = Number(modelCsv["model end time"]);
|
|
287
283
|
const graphDefaultMinTime = Number(modelCsv["graph default min time"]);
|
|
288
284
|
const graphDefaultMaxTime = Number(modelCsv["graph default max time"]);
|
|
289
285
|
const datFilesString = modelCsv["model dat files"];
|
|
@@ -299,28 +295,18 @@ function createConfigContext(buildContext, configDir) {
|
|
|
299
295
|
const hexColor = row["hex code"];
|
|
300
296
|
colors.set(colorId, hexColor);
|
|
301
297
|
}
|
|
302
|
-
return new ConfigContext(
|
|
303
|
-
buildContext,
|
|
304
|
-
configDir,
|
|
305
|
-
strings,
|
|
306
|
-
colors,
|
|
307
|
-
modelStartTime,
|
|
308
|
-
modelEndTime,
|
|
309
|
-
graphDefaultMinTime,
|
|
310
|
-
graphDefaultMaxTime,
|
|
311
|
-
datFiles
|
|
312
|
-
);
|
|
298
|
+
return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles);
|
|
313
299
|
}
|
|
314
300
|
function configFilePath(configDir, name, ext) {
|
|
315
301
|
return (0, import_path.join)(configDir, `${name}.${ext}`);
|
|
316
302
|
}
|
|
317
303
|
function readCsvFile(path) {
|
|
318
304
|
const data = (0, import_fs.readFileSync)(path, "utf8");
|
|
319
|
-
return (0, import_sync.
|
|
305
|
+
return (0, import_sync.parse)(data, {
|
|
320
306
|
columns: true,
|
|
321
307
|
trim: true,
|
|
322
308
|
skip_empty_lines: true,
|
|
323
|
-
|
|
309
|
+
skip_records_with_empty_values: true
|
|
324
310
|
});
|
|
325
311
|
}
|
|
326
312
|
function readConfigCsvFile(configDir, name) {
|
|
@@ -351,8 +337,6 @@ function writeModelSpec(context, dstDir) {
|
|
|
351
337
|
tsContent += s + "\n";
|
|
352
338
|
}
|
|
353
339
|
emit("// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!");
|
|
354
|
-
emit(`export const startTime = ${context.modelStartTime}`);
|
|
355
|
-
emit(`export const endTime = ${context.modelEndTime}`);
|
|
356
340
|
emit(`export const inputVarIds: string[] = ${JSON.stringify(inputVarIds, null, 2)}`);
|
|
357
341
|
emit(`export const outputVarIds: string[] = ${JSON.stringify(outputVarIds, null, 2)}`);
|
|
358
342
|
context.writeStagedFile("model", dstDir, "model-spec.ts", tsContent);
|
|
@@ -865,8 +849,6 @@ async function processModelConfig(buildContext, options) {
|
|
|
865
849
|
const elapsed = ((t1 - t0) / 1e3).toFixed(1);
|
|
866
850
|
context.log("info", `Done generating files (${elapsed}s)`);
|
|
867
851
|
return {
|
|
868
|
-
startTime: context.modelStartTime,
|
|
869
|
-
endTime: context.modelEndTime,
|
|
870
852
|
inputs: context.getOrderedInputs(),
|
|
871
853
|
outputs: context.getOrderedOutputs(),
|
|
872
854
|
datFiles: context.datFiles
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/processor.ts","../src/context.ts","../src/strings.ts","../src/var-names.ts","../src/gen-model-spec.ts","../src/gen-config-specs.ts","../src/read-config.ts","../src/gen-graphs.ts","../src/gen-inputs.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport { configProcessor } from './processor'\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport type { BuildContext, ModelSpec } from '@sdeverywhere/build'\n\nimport { createConfigContext } from './context'\nimport { writeModelSpec } from './gen-model-spec'\nimport { generateConfigSpecs, writeConfigSpecs, writeSpecTypes } from './gen-config-specs'\n\nexport interface ConfigOutputPaths {\n /** The absolute path to the directory where model spec files will be written. */\n modelSpecsDir?: string\n\n /** The absolute path to the directory where config spec files will be written. */\n configSpecsDir?: string\n\n /** The absolute path to the directory where translated strings will be written. */\n stringsDir?: string\n}\n\nexport interface ConfigOptions {\n /**\n * The absolute path to the directory containing the CSV config files.\n */\n config: string\n\n /**\n * Either a single path to a base output directory (in which case, the recommended\n * directory structure will be used) or a `ConfigOutputPaths` containing specific paths.\n * If a single string is provided, the following subdirectories will be used:\n * <out-dir>/\n * src/\n * config/\n * generated/\n * model/\n * generated/\n * strings/\n */\n out?: string | ConfigOutputPaths\n}\n\n/**\n * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere\n * `UserConfig`. The returned function:\n * - reads CSV files from a `config` directory\n * - writes JS files to the configured output directories\n * - returns a `ModelSpec` that guides the rest of the `sde` build process\n */\nexport function configProcessor(options: ConfigOptions): (buildContext: BuildContext) => Promise<ModelSpec> {\n return buildContext => {\n return processModelConfig(buildContext, options)\n }\n}\n\nasync function processModelConfig(buildContext: BuildContext, options: ConfigOptions): Promise<ModelSpec> {\n const t0 = performance.now()\n\n // Resolve source (config) directory\n if (!existsSync(options.config)) {\n throw new Error(`The provided config dir '${options.config}' does not exist`)\n }\n\n // Resolve output directories\n let outModelSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outModelSpecsDir = joinPath(options.out, 'src', 'model', 'generated')\n } else {\n outModelSpecsDir = options.out.modelSpecsDir\n }\n }\n\n let outConfigSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outConfigSpecsDir = joinPath(options.out, 'src', 'config', 'generated')\n } else {\n outConfigSpecsDir = options.out.configSpecsDir\n }\n }\n\n let outStringsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outStringsDir = joinPath(options.out, 'strings')\n } else {\n outStringsDir = options.out.stringsDir\n }\n }\n\n // Create a container for strings, variables, etc\n const context = createConfigContext(buildContext, options.config)\n\n // Write the generated files\n context.log('info', 'Generating files...')\n\n const configSpecs = generateConfigSpecs(context)\n if (outConfigSpecsDir) {\n context.log('verbose', ' Writing config specs')\n writeConfigSpecs(context, configSpecs, outConfigSpecsDir)\n writeSpecTypes(context, outConfigSpecsDir)\n }\n\n if (outModelSpecsDir) {\n context.log('verbose', ' Writing model specs')\n writeModelSpec(context, outModelSpecsDir)\n }\n\n if (outStringsDir) {\n context.log('verbose', ' Writing strings')\n context.writeStringsFiles(outStringsDir)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `Done generating files (${elapsed}s)`)\n\n return {\n startTime: context.modelStartTime,\n endTime: context.modelEndTime,\n inputs: context.getOrderedInputs(),\n outputs: context.getOrderedOutputs(),\n datFiles: context.datFiles\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { join as joinPath, relative } from 'path'\n\nimport parseCsv from 'csv-parse/lib/sync.js'\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 modelStartTime: number,\n public readonly modelEndTime: number,\n public readonly graphDefaultMinTime: number,\n public readonly graphDefaultMaxTime: number,\n public readonly datFiles: string[]\n ) {}\n\n /**\n * Read a CSV file of the given name from the config directory.\n *\n * @param name The base name of the CSV file.\n */\n readConfigCsvFile(name: string): CsvRow[] {\n return readConfigCsvFile(this.configDir, name)\n }\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n this.buildContext.log(level, msg)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.buildContext.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n addInputVariable(inputVarName: string, defaultValue: number, minValue: number, maxValue: number): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(inputVarName)\n if (this.inputSpecs.get(varId)) {\n // Fail if the variable was already added (there should only be one spec\n // per input variable)\n console.error(`ERROR: Input variable ${inputVarName} was already added`)\n }\n this.inputSpecs.set(varId, {\n varName: inputVarName,\n defaultValue,\n minValue,\n maxValue\n })\n }\n\n addOutputVariable(outputVarName: string): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(outputVarName)\n this.outputVarNames.set(varId, outputVarName)\n }\n\n addStaticVariable(sourceName: string, varName: string): void {\n const sourceVarNames = this.staticVarNames.get(sourceName)\n if (sourceVarNames) {\n sourceVarNames.add(varName)\n } else {\n const varNames: Set<string> = new Set()\n varNames.add(varName)\n this.staticVarNames.set(sourceName, varNames)\n }\n }\n\n getHexColorForId(colorId: ColorId): HexColor {\n return this.colorMap.get(colorId)\n }\n\n getOrderedInputs(): InputSpec[] {\n // TODO: It would be nice to alphabetize the inputs, but currently we have\n // code that assumes that the InputSpecs in the map have the same order\n // as the variables in the spec file and model config, so preserve the\n // existing order here for now\n return Array.from(this.inputSpecs.values())\n }\n\n getOrderedOutputs(): OutputSpec[] {\n // Sort the output variable names alphabetically\n const alphabetical = (a: string, b: string) => (a > b ? 1 : b > a ? -1 : 0)\n const varNames = Array.from(this.outputVarNames.values()).sort(alphabetical)\n return varNames.map(varName => {\n return {\n varName\n }\n })\n }\n\n writeStringsFiles(dstDir: string): void {\n this.strings.writeJsFiles(this.buildContext, dstDir /*, xlatLangs*/)\n }\n}\n\nexport function createConfigContext(buildContext: BuildContext, configDir: string): ConfigContext {\n // Read basic model configuration from `model.csv`\n const modelCsv = readConfigCsvFile(configDir, 'model')[0]\n const modelStartTime = Number(modelCsv['model start time'])\n const modelEndTime = Number(modelCsv['model end time'])\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(\n buildContext,\n configDir,\n strings,\n colors,\n modelStartTime,\n modelEndTime,\n graphDefaultMinTime,\n graphDefaultMaxTime,\n datFiles\n )\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_lines_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> ')\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 ` ` to the Unicode\n // equivalent (`U+00A0`); we will convert it back to ` ` to make\n // it more obvious and easier to view in a translation tool\n clean = clean.replace(/\\u00a0/gi, ' ')\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 startTime = ${context.modelStartTime}`)\n emit(`export const endTime = ${context.modelEndTime}`)\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('&', '&')\n const displayTitle = htmlToUtf8(menuTitle || title).replace('&', '&')\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('&', '&')\n return `${typeLabel} ${kind}: ${groupTitle} > ${labelText}`\n }\n\n const labelKey = strings.add(key('label'), label, layout, strCtxt('Label'))\n\n const listingLabel = optionalString(r['listing label'])\n let listingLabelKey: StringKey\n if (listingLabel) {\n listingLabelKey = strings.add(key('action_label'), listingLabel, layout, strCtxt('Action Label'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'input-descriptions')\n }\n\n // Converts a slider row in `inputs.csv` to a `SliderSpec`\n function sliderSpecFromCsv(): SliderSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const defaultValue = requiredNumber('slider/switch default')\n const minValue = requiredNumber('slider min')\n const maxValue = requiredNumber('slider max')\n const step = requiredNumber('slider step')\n const reversed = optionalString(r['reversed']) === 'yes'\n\n if (defaultValue < minValue || defaultValue > maxValue) {\n let e = `Default value for slider ${inputId} is out of range: `\n e += `default=${defaultValue} min=${minValue} max=${maxValue}`\n throw new Error(e)\n }\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n const format = optionalString(r['format']) || '.0f'\n\n const units = optionalString(r['units'])\n let unitsKey: StringKey\n if (units) {\n unitsKey = strings.add(genStringKey('input_units', units), units, layout, 'Slider Units')\n }\n\n const rangeInfo = getSliderRangeInfo(r, maxValue, context)\n const rangeLabelKeys = rangeInfo.labelKeys\n const rangeDividers = rangeInfo.dividers\n\n return {\n kind: 'slider',\n id: inputId,\n varId,\n varName,\n defaultValue,\n minValue,\n maxValue,\n step,\n reversed,\n labelKey,\n listingLabelKey,\n descriptionKey,\n unitsKey,\n rangeLabelKeys,\n rangeDividers,\n format\n }\n }\n\n // Converts a switch row in `inputs.csv` to a `SwitchSpec`\n function switchSpecFromCsv(): SwitchSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const onValue = requiredNumber('enabled value')\n const offValue = requiredNumber('disabled value')\n const defaultValue = requiredNumber('slider/switch default')\n if (defaultValue !== onValue && defaultValue !== offValue) {\n throw new Error(\n `Invalid default value for switch ${inputId}: off=${offValue} on=${onValue} default=${defaultValue}`\n )\n }\n\n const minValue = Math.min(offValue, onValue)\n const maxValue = Math.max(offValue, onValue)\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n // The `controlled input ids` field dictates which rows are active\n // when this switch is on or off. Examples of the format of this field:\n // 1;2;3|4;5;6\n // 1|2;3;4;5\n // |1\n // On the left side of the '|' are the rows that are active when the\n // switch is in an off position, and on the right side are the rows\n // that are active when the switch is in an on position. Usually the\n // \"active when off\" rows are above the switch in the UI, and the\n // \"active when on\" rows are below the switch.\n const controlledInputIds = requiredString('controlled input ids')\n const controlledParts = controlledInputIds.split('|')\n const rowsActiveWhenOff = controlledParts[0].split(';').filter(id => id.trim().length > 0)\n const rowsActiveWhenOn = controlledParts[1].split(';').filter(id => id.trim().length > 0)\n\n return {\n kind: 'switch',\n id: inputId,\n varId,\n varName,\n labelKey,\n listingLabelKey,\n descriptionKey,\n defaultValue,\n offValue,\n onValue,\n slidersActiveWhenOff: rowsActiveWhenOff,\n slidersActiveWhenOn: rowsActiveWhenOn\n }\n }\n\n // Call a different converter function depending on the input type\n let inputSpec: SliderSpec | SwitchSpec\n switch (inputType) {\n case 'slider': {\n inputSpec = sliderSpecFromCsv()\n break\n }\n case 'switch':\n case 'checkbox': {\n inputSpec = switchSpecFromCsv()\n break\n }\n case 'checkbox group':\n // TODO\n // XXX: For now, we specify the checkbox IDs as a semicolon separated list\n // in the \"varname\" cell\n // const checkboxIds = row['varname'].split(';')\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n return inputSpec\n}\n\ninterface SliderRangeInfo {\n labelKeys: StringKey[]\n dividers: number[]\n}\n\nfunction getSliderRangeInfo(r: CsvRow, maxValue: number, context: ConfigContext): SliderRangeInfo {\n const strings = context.strings\n const labelKeys: StringKey[] = []\n const dividers: number[] = []\n\n // Get all labels to determine the number of ranges\n let rangeNum = 1\n while (rangeNum <= 5) {\n const label = optionalString(r[`range ${rangeNum} label`])\n if (!label) {\n break\n }\n const labelKey = strings.add(genStringKey('input_range', label), label, layout, 'Slider Range Label')\n if (!labelKey) {\n break\n }\n labelKeys.push(labelKey)\n rangeNum++\n }\n\n // Find dividing points between ranges; the absence of a final dividing point\n // indicates the use of discrete values\n const numRanges = rangeNum - 1\n for (rangeNum = 2; rangeNum <= numRanges; rangeNum++) {\n let divider = optionalNumber(r[`range ${rangeNum} start`])\n if (divider === undefined) {\n // Fall back on the slider max value when a divider is missing\n divider = maxValue\n }\n dividers.push(divider)\n }\n\n return {\n labelKeys,\n dividers\n }\n}\n"],"mappings":"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","names":["import_fs","import_path","layout","sanitizeHtml","joinPath","parseCsv","layout","import_fs","import_path","layout","key","kind","key","resolvePath","joinPath"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/processor.ts","../src/context.ts","../src/strings.ts","../src/var-names.ts","../src/gen-model-spec.ts","../src/gen-config-specs.ts","../src/read-config.ts","../src/gen-graphs.ts","../src/gen-inputs.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nexport { configProcessor } from './processor'\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport type { BuildContext, ModelSpec } from '@sdeverywhere/build'\n\nimport { createConfigContext } from './context'\nimport { writeModelSpec } from './gen-model-spec'\nimport { generateConfigSpecs, writeConfigSpecs, writeSpecTypes } from './gen-config-specs'\n\nexport interface ConfigOutputPaths {\n /** The absolute path to the directory where model spec files will be written. */\n modelSpecsDir?: string\n\n /** The absolute path to the directory where config spec files will be written. */\n configSpecsDir?: string\n\n /** The absolute path to the directory where translated strings will be written. */\n stringsDir?: string\n}\n\nexport interface ConfigOptions {\n /**\n * The absolute path to the directory containing the CSV config files.\n */\n config: string\n\n /**\n * Either a single path to a base output directory (in which case, the recommended\n * directory structure will be used) or a `ConfigOutputPaths` containing specific paths.\n * If a single string is provided, the following subdirectories will be used:\n * <out-dir>/\n * src/\n * config/\n * generated/\n * model/\n * generated/\n * strings/\n */\n out?: string | ConfigOutputPaths\n}\n\n/**\n * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere\n * `UserConfig`. The returned function:\n * - reads CSV files from a `config` directory\n * - writes JS files to the configured output directories\n * - returns a `ModelSpec` that guides the rest of the `sde` build process\n */\nexport function configProcessor(options: ConfigOptions): (buildContext: BuildContext) => Promise<ModelSpec> {\n return buildContext => {\n return processModelConfig(buildContext, options)\n }\n}\n\nasync function processModelConfig(buildContext: BuildContext, options: ConfigOptions): Promise<ModelSpec> {\n const t0 = performance.now()\n\n // Resolve source (config) directory\n if (!existsSync(options.config)) {\n throw new Error(`The provided config dir '${options.config}' does not exist`)\n }\n\n // Resolve output directories\n let outModelSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outModelSpecsDir = joinPath(options.out, 'src', 'model', 'generated')\n } else {\n outModelSpecsDir = options.out.modelSpecsDir\n }\n }\n\n let outConfigSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outConfigSpecsDir = joinPath(options.out, 'src', 'config', 'generated')\n } else {\n outConfigSpecsDir = options.out.configSpecsDir\n }\n }\n\n let outStringsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outStringsDir = joinPath(options.out, 'strings')\n } else {\n outStringsDir = options.out.stringsDir\n }\n }\n\n // Create a container for strings, variables, etc\n const context = createConfigContext(buildContext, options.config)\n\n // Write the generated files\n context.log('info', 'Generating files...')\n\n const configSpecs = generateConfigSpecs(context)\n if (outConfigSpecsDir) {\n context.log('verbose', ' Writing config specs')\n writeConfigSpecs(context, configSpecs, outConfigSpecsDir)\n writeSpecTypes(context, outConfigSpecsDir)\n }\n\n if (outModelSpecsDir) {\n context.log('verbose', ' Writing model specs')\n writeModelSpec(context, outModelSpecsDir)\n }\n\n if (outStringsDir) {\n context.log('verbose', ' Writing strings')\n context.writeStringsFiles(outStringsDir)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `Done generating files (${elapsed}s)`)\n\n return {\n inputs: context.getOrderedInputs(),\n outputs: context.getOrderedOutputs(),\n datFiles: context.datFiles\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { join as joinPath, relative } from 'path'\n\nimport { parse as parseCsv } from 'csv-parse/sync'\n\nimport type { BuildContext, InputSpec, LogLevel, OutputSpec } from '@sdeverywhere/build'\n\nimport type { HexColor } from './spec-types'\nimport { Strings } from './strings'\nimport type { InputVarId, OutputVarId } from './var-names'\nimport { sdeNameForVensimVarName } from './var-names'\n\nexport type CsvRow = { [key: string]: string }\nexport type ColorId = string\n\nexport class ConfigContext {\n private readonly inputSpecs: Map<InputVarId, InputSpec> = new Map()\n private readonly outputVarNames: Map<OutputVarId, string> = new Map()\n private readonly staticVarNames: Map<string, Set<string>> = new Map()\n\n constructor(\n private readonly buildContext: BuildContext,\n private readonly configDir: string,\n public readonly strings: Strings,\n private readonly colorMap: Map<ColorId, HexColor>,\n public readonly graphDefaultMinTime: number,\n public readonly graphDefaultMaxTime: number,\n public readonly datFiles: string[]\n ) {}\n\n /**\n * Read a CSV file of the given name from the config directory.\n *\n * @param name The base name of the CSV file.\n */\n readConfigCsvFile(name: string): CsvRow[] {\n return readConfigCsvFile(this.configDir, name)\n }\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n this.buildContext.log(level, msg)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.buildContext.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n addInputVariable(inputVarName: string, defaultValue: number, minValue: number, maxValue: number): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(inputVarName)\n if (this.inputSpecs.get(varId)) {\n // Fail if the variable was already added (there should only be one spec\n // per input variable)\n console.error(`ERROR: Input variable ${inputVarName} was already added`)\n }\n this.inputSpecs.set(varId, {\n varName: inputVarName,\n defaultValue,\n minValue,\n maxValue\n })\n }\n\n addOutputVariable(outputVarName: string): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(outputVarName)\n this.outputVarNames.set(varId, outputVarName)\n }\n\n addStaticVariable(sourceName: string, varName: string): void {\n const sourceVarNames = this.staticVarNames.get(sourceName)\n if (sourceVarNames) {\n sourceVarNames.add(varName)\n } else {\n const varNames: Set<string> = new Set()\n varNames.add(varName)\n this.staticVarNames.set(sourceName, varNames)\n }\n }\n\n getHexColorForId(colorId: ColorId): HexColor {\n return this.colorMap.get(colorId)\n }\n\n getOrderedInputs(): InputSpec[] {\n // TODO: It would be nice to alphabetize the inputs, but currently we have\n // code that assumes that the InputSpecs in the map have the same order\n // as the variables in the spec file and model config, so preserve the\n // existing order here for now\n return Array.from(this.inputSpecs.values())\n }\n\n getOrderedOutputs(): OutputSpec[] {\n // Sort the output variable names alphabetically\n const alphabetical = (a: string, b: string) => (a > b ? 1 : b > a ? -1 : 0)\n const varNames = Array.from(this.outputVarNames.values()).sort(alphabetical)\n return varNames.map(varName => {\n return {\n varName\n }\n })\n }\n\n writeStringsFiles(dstDir: string): void {\n this.strings.writeJsFiles(this.buildContext, dstDir /*, xlatLangs*/)\n }\n}\n\nexport function createConfigContext(buildContext: BuildContext, configDir: string): ConfigContext {\n // Read basic model configuration from `model.csv`\n const modelCsv = readConfigCsvFile(configDir, 'model')[0]\n const graphDefaultMinTime = Number(modelCsv['graph default min time'])\n const graphDefaultMaxTime = Number(modelCsv['graph default max time'])\n const datFilesString = modelCsv['model dat files']\n const origDatFiles = datFilesString.length > 0 ? datFilesString.split(';') : []\n\n // The dat file paths in the config file are assumed to be relative to\n // the project directory (i.e., the directory where the `sde.config.js`\n // file resides), so we need to convert to paths that are relative to\n // the `sde-prep` directory (since that is the \"model\" directory from\n // the perspective of the compile package).\n const prepDir = buildContext.config.prepDir\n const projDir = buildContext.config.rootDir\n const datFiles = origDatFiles.map(f => joinPath(relative(prepDir, projDir), f))\n\n // Read the static strings from `strings.csv`\n const strings = readStringsCsv(configDir)\n\n // Read color configuration from `colors.csv`\n const colorsCsv = readConfigCsvFile(configDir, 'colors')\n const colors = new Map()\n for (const row of colorsCsv) {\n const colorId = row['id']\n const hexColor = row['hex code']\n colors.set(colorId, hexColor)\n }\n\n return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles)\n}\n\nfunction configFilePath(configDir: string, name: string, ext: string): string {\n return joinPath(configDir, `${name}.${ext}`)\n}\n\nfunction readCsvFile(path: string): CsvRow[] {\n const data = readFileSync(path, 'utf8')\n return parseCsv(data, {\n columns: true,\n trim: true,\n skip_empty_lines: true,\n skip_records_with_empty_values: true\n })\n}\n\nfunction readConfigCsvFile(configDir: string, name: string): CsvRow[] {\n return readCsvFile(configFilePath(configDir, name, 'csv'))\n}\n\n/**\n * Initialize a `Strings` instance with the core strings from `strings.csv`.\n */\nfunction readStringsCsv(configDir: string): Strings {\n const strings = new Strings()\n\n // TODO: For now we use the same \"layout\" and \"context\" for all core strings\n const layout = 'default'\n const context = 'Core'\n\n const rows = readConfigCsvFile(configDir, 'strings')\n for (const row of rows) {\n const key = row['id']\n let str = row['string']\n str = str ? str.trim() : ''\n if (str) {\n strings.add(key, str, layout, context)\n }\n }\n\n return strings\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport sanitizeHtml from 'sanitize-html'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type { StringKey } from './spec-types'\n\ninterface StringRecord {\n key: StringKey\n str: string\n layout: string\n context: string\n grouping: string\n appendedStringKeys?: string[]\n}\n\ntype LangCode = string\ntype StringMap = Map<StringKey, string>\ntype XlatMap = Map<LangCode, StringMap>\n\nexport class Strings {\n private readonly records: Map<StringKey, StringRecord> = new Map()\n\n add(\n key: StringKey,\n str: string,\n layout: string,\n context: string,\n grouping?: string,\n appendedStringKeys?: string[]\n ): StringKey {\n checkInvisibleCharacters(str || '')\n\n if (!key) {\n throw new Error(`Must provide a key for the string: ${str}`)\n }\n\n const validKey = /^[0-9a-z_]+$/.test(key)\n if (!validKey) {\n throw new Error(`String key contains undesirable characters: ${key}`)\n }\n\n if (!layout) {\n throw new Error(`Must provide a layout (e.g., 'layout1' or 'not-translated')`)\n }\n\n if (!context) {\n throw new Error(`Must provide a context string: key=${key}, string=${str}`)\n }\n\n if (context === 'Core') {\n // For core strings from the `strings.csv` file, leave the context empty for now\n // (indicating this is a \"general\" string with no particular context)\n context = undefined\n }\n\n if (!grouping) {\n // Use 'primary' if grouping is not specified\n grouping = 'primary'\n }\n\n // Convert some HTML tags and entities to UTF-8\n if (str) {\n str = str.trim()\n str = htmlToUtf8(str)\n }\n\n // If the trimmed string is empty, do not create a new key, and return an empty string\n if (!str) {\n return ''\n }\n\n if (this.records.has(key)) {\n // TODO: For now, allow certain keys to appear more than once; we only add the string once\n const prefix = key.substring(0, key.indexOf('__'))\n switch (prefix) {\n case 'graph_dataset_label':\n case 'graph_xaxis_label':\n case 'graph_yaxis_label':\n case 'input_group_title':\n case 'input_range':\n case 'input_units':\n break\n default:\n throw new Error(`More than one string with key=${key}`)\n }\n }\n\n // Add the string record\n this.records.set(key, {\n key,\n str,\n layout,\n context,\n grouping,\n appendedStringKeys\n })\n\n return key\n }\n\n /**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param xlatLangs The set of languages that are configured for translation.\n */\n writeJsFiles(context: BuildContext, dstDir: string /*, xlatLangs: Map<LangCode, LangSpec>*/): void {\n writeLangJsFiles(context, dstDir, this.records /*, xlatLangs*/)\n }\n}\n\nfunction getSortedRecords(records: Map<StringKey, StringRecord>): StringRecord[] {\n // Sort records by string key\n return Array.from(records.values()).sort((a, b) => {\n return a.key > b.key ? 1 : b.key > a.key ? -1 : 0\n })\n}\n\nfunction checkInvisibleCharacters(s: string): void {\n if (s.includes('\\u00a0')) {\n const e = s.replace(/\\u00a0/g, 'HERE')\n throw new Error(\n `String contains one or more non-breaking space characters (to fix, replace \"HERE\" with a normal space):\\n ${e}`\n )\n }\n}\n\nfunction utf8SubscriptToHtml(key: StringKey, s: string): string {\n if (key.includes('graph_yaxis_label')) {\n // Chart.js doesn't support using HTML tags like subscripts or superscripts\n // in axis labels. For now, we will convert subscript literals in axis labels\n // to a simple number. (We could preserve the subscript literal, but it doesn't\n // render all that well.)\n s = s.replace(/₂/gi, '2')\n s = s.replace(/₃/gi, '3')\n s = s.replace(/₄/gi, '4')\n s = s.replace(/₆/gi, '6')\n return s\n }\n\n // Unicode subscript literals render differently in some browsers (very low\n // in Safari for example), so we will replace them with HTML `sub` tags\n s = s.replace(/₂/gi, '<sub>2</sub>')\n s = s.replace(/₃/gi, '<sub>3</sub>')\n s = s.replace(/₄/gi, '<sub>4</sub>')\n s = s.replace(/₆/gi, '<sub>6</sub>')\n\n // If the subscript tag is followed by a space, that space needs to be\n // replaced with a non-breaking space, otherwise the whitespace will be lost\n s = s.replace(/<\\/sub> /gi, '</sub> ')\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 ` ` to the Unicode\n // equivalent (`U+00A0`); we will convert it back to ` ` to make\n // it more obvious and easier to view in a translation tool\n clean = clean.replace(/\\u00a0/gi, ' ')\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('&', '&')\n const displayTitle = htmlToUtf8(menuTitle || title).replace('&', '&')\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('&', '&')\n return `${typeLabel} ${kind}: ${groupTitle} > ${labelText}`\n }\n\n const labelKey = strings.add(key('label'), label, layout, strCtxt('Label'))\n\n const listingLabel = optionalString(r['listing label'])\n let listingLabelKey: StringKey\n if (listingLabel) {\n listingLabelKey = strings.add(key('action_label'), listingLabel, layout, strCtxt('Action Label'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'input-descriptions')\n }\n\n // Converts a slider row in `inputs.csv` to a `SliderSpec`\n function sliderSpecFromCsv(): SliderSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const defaultValue = requiredNumber('slider/switch default')\n const minValue = requiredNumber('slider min')\n const maxValue = requiredNumber('slider max')\n const step = requiredNumber('slider step')\n const reversed = optionalString(r['reversed']) === 'yes'\n\n if (defaultValue < minValue || defaultValue > maxValue) {\n let e = `Default value for slider ${inputId} is out of range: `\n e += `default=${defaultValue} min=${minValue} max=${maxValue}`\n throw new Error(e)\n }\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n const format = optionalString(r['format']) || '.0f'\n\n const units = optionalString(r['units'])\n let unitsKey: StringKey\n if (units) {\n unitsKey = strings.add(genStringKey('input_units', units), units, layout, 'Slider Units')\n }\n\n const rangeInfo = getSliderRangeInfo(r, maxValue, context)\n const rangeLabelKeys = rangeInfo.labelKeys\n const rangeDividers = rangeInfo.dividers\n\n return {\n kind: 'slider',\n id: inputId,\n varId,\n varName,\n defaultValue,\n minValue,\n maxValue,\n step,\n reversed,\n labelKey,\n listingLabelKey,\n descriptionKey,\n unitsKey,\n rangeLabelKeys,\n rangeDividers,\n format\n }\n }\n\n // Converts a switch row in `inputs.csv` to a `SwitchSpec`\n function switchSpecFromCsv(): SwitchSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const onValue = requiredNumber('enabled value')\n const offValue = requiredNumber('disabled value')\n const defaultValue = requiredNumber('slider/switch default')\n if (defaultValue !== onValue && defaultValue !== offValue) {\n throw new Error(\n `Invalid default value for switch ${inputId}: off=${offValue} on=${onValue} default=${defaultValue}`\n )\n }\n\n const minValue = Math.min(offValue, onValue)\n const maxValue = Math.max(offValue, onValue)\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n // The `controlled input ids` field dictates which rows are active\n // when this switch is on or off. Examples of the format of this field:\n // 1;2;3|4;5;6\n // 1|2;3;4;5\n // |1\n // On the left side of the '|' are the rows that are active when the\n // switch is in an off position, and on the right side are the rows\n // that are active when the switch is in an on position. Usually the\n // \"active when off\" rows are above the switch in the UI, and the\n // \"active when on\" rows are below the switch.\n const controlledInputIds = requiredString('controlled input ids')\n const controlledParts = controlledInputIds.split('|')\n const rowsActiveWhenOff = controlledParts[0].split(';').filter(id => id.trim().length > 0)\n const rowsActiveWhenOn = controlledParts[1].split(';').filter(id => id.trim().length > 0)\n\n return {\n kind: 'switch',\n id: inputId,\n varId,\n varName,\n labelKey,\n listingLabelKey,\n descriptionKey,\n defaultValue,\n offValue,\n onValue,\n slidersActiveWhenOff: rowsActiveWhenOff,\n slidersActiveWhenOn: rowsActiveWhenOn\n }\n }\n\n // Call a different converter function depending on the input type\n let inputSpec: SliderSpec | SwitchSpec\n switch (inputType) {\n case 'slider': {\n inputSpec = sliderSpecFromCsv()\n break\n }\n case 'switch':\n case 'checkbox': {\n inputSpec = switchSpecFromCsv()\n break\n }\n case 'checkbox group':\n // TODO\n // XXX: For now, we specify the checkbox IDs as a semicolon separated list\n // in the \"varname\" cell\n // const checkboxIds = row['varname'].split(';')\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n return inputSpec\n}\n\ninterface SliderRangeInfo {\n labelKeys: StringKey[]\n dividers: number[]\n}\n\nfunction getSliderRangeInfo(r: CsvRow, maxValue: number, context: ConfigContext): SliderRangeInfo {\n const strings = context.strings\n const labelKeys: StringKey[] = []\n const dividers: number[] = []\n\n // Get all labels to determine the number of ranges\n let rangeNum = 1\n while (rangeNum <= 5) {\n const label = optionalString(r[`range ${rangeNum} label`])\n if (!label) {\n break\n }\n const labelKey = strings.add(genStringKey('input_range', label), label, layout, 'Slider Range Label')\n if (!labelKey) {\n break\n }\n labelKeys.push(labelKey)\n rangeNum++\n }\n\n // Find dividing points between ranges; the absence of a final dividing point\n // indicates the use of discrete values\n const numRanges = rangeNum - 1\n for (rangeNum = 2; rangeNum <= numRanges; rangeNum++) {\n let divider = optionalNumber(r[`range ${rangeNum} start`])\n if (divider === undefined) {\n // Fall back on the slider max value when a divider is missing\n divider = maxValue\n }\n dividers.push(divider)\n }\n\n return {\n labelKeys,\n dividers\n }\n}\n"],"mappings":"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","names":["import_fs","import_path","layout","sanitizeHtml","joinPath","parseCsv","layout","import_fs","import_path","layout","key","kind","key","resolvePath","joinPath"]}
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { join as joinPath2 } from "path";
|
|
|
5
5
|
// src/context.ts
|
|
6
6
|
import { readFileSync } from "fs";
|
|
7
7
|
import { join as joinPath, relative } from "path";
|
|
8
|
-
import parseCsv from "csv-parse/
|
|
8
|
+
import { parse as parseCsv } from "csv-parse/sync";
|
|
9
9
|
|
|
10
10
|
// src/strings.ts
|
|
11
11
|
import sanitizeHtml from "sanitize-html";
|
|
@@ -181,13 +181,11 @@ function sdeNameForVensimVarName(varName) {
|
|
|
181
181
|
|
|
182
182
|
// src/context.ts
|
|
183
183
|
var ConfigContext = class {
|
|
184
|
-
constructor(buildContext, configDir, strings, colorMap,
|
|
184
|
+
constructor(buildContext, configDir, strings, colorMap, graphDefaultMinTime, graphDefaultMaxTime, datFiles) {
|
|
185
185
|
this.buildContext = buildContext;
|
|
186
186
|
this.configDir = configDir;
|
|
187
187
|
this.strings = strings;
|
|
188
188
|
this.colorMap = colorMap;
|
|
189
|
-
this.modelStartTime = modelStartTime;
|
|
190
|
-
this.modelEndTime = modelEndTime;
|
|
191
189
|
this.graphDefaultMinTime = graphDefaultMinTime;
|
|
192
190
|
this.graphDefaultMaxTime = graphDefaultMaxTime;
|
|
193
191
|
this.datFiles = datFiles;
|
|
@@ -251,8 +249,6 @@ var ConfigContext = class {
|
|
|
251
249
|
};
|
|
252
250
|
function createConfigContext(buildContext, configDir) {
|
|
253
251
|
const modelCsv = readConfigCsvFile(configDir, "model")[0];
|
|
254
|
-
const modelStartTime = Number(modelCsv["model start time"]);
|
|
255
|
-
const modelEndTime = Number(modelCsv["model end time"]);
|
|
256
252
|
const graphDefaultMinTime = Number(modelCsv["graph default min time"]);
|
|
257
253
|
const graphDefaultMaxTime = Number(modelCsv["graph default max time"]);
|
|
258
254
|
const datFilesString = modelCsv["model dat files"];
|
|
@@ -268,17 +264,7 @@ function createConfigContext(buildContext, configDir) {
|
|
|
268
264
|
const hexColor = row["hex code"];
|
|
269
265
|
colors.set(colorId, hexColor);
|
|
270
266
|
}
|
|
271
|
-
return new ConfigContext(
|
|
272
|
-
buildContext,
|
|
273
|
-
configDir,
|
|
274
|
-
strings,
|
|
275
|
-
colors,
|
|
276
|
-
modelStartTime,
|
|
277
|
-
modelEndTime,
|
|
278
|
-
graphDefaultMinTime,
|
|
279
|
-
graphDefaultMaxTime,
|
|
280
|
-
datFiles
|
|
281
|
-
);
|
|
267
|
+
return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles);
|
|
282
268
|
}
|
|
283
269
|
function configFilePath(configDir, name, ext) {
|
|
284
270
|
return joinPath(configDir, `${name}.${ext}`);
|
|
@@ -289,7 +275,7 @@ function readCsvFile(path) {
|
|
|
289
275
|
columns: true,
|
|
290
276
|
trim: true,
|
|
291
277
|
skip_empty_lines: true,
|
|
292
|
-
|
|
278
|
+
skip_records_with_empty_values: true
|
|
293
279
|
});
|
|
294
280
|
}
|
|
295
281
|
function readConfigCsvFile(configDir, name) {
|
|
@@ -320,8 +306,6 @@ function writeModelSpec(context, dstDir) {
|
|
|
320
306
|
tsContent += s + "\n";
|
|
321
307
|
}
|
|
322
308
|
emit("// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!");
|
|
323
|
-
emit(`export const startTime = ${context.modelStartTime}`);
|
|
324
|
-
emit(`export const endTime = ${context.modelEndTime}`);
|
|
325
309
|
emit(`export const inputVarIds: string[] = ${JSON.stringify(inputVarIds, null, 2)}`);
|
|
326
310
|
emit(`export const outputVarIds: string[] = ${JSON.stringify(outputVarIds, null, 2)}`);
|
|
327
311
|
context.writeStagedFile("model", dstDir, "model-spec.ts", tsContent);
|
|
@@ -833,8 +817,6 @@ async function processModelConfig(buildContext, options) {
|
|
|
833
817
|
const elapsed = ((t1 - t0) / 1e3).toFixed(1);
|
|
834
818
|
context.log("info", `Done generating files (${elapsed}s)`);
|
|
835
819
|
return {
|
|
836
|
-
startTime: context.modelStartTime,
|
|
837
|
-
endTime: context.modelEndTime,
|
|
838
820
|
inputs: context.getOrderedInputs(),
|
|
839
821
|
outputs: context.getOrderedOutputs(),
|
|
840
822
|
datFiles: context.datFiles
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/processor.ts","../src/context.ts","../src/strings.ts","../src/var-names.ts","../src/gen-model-spec.ts","../src/gen-config-specs.ts","../src/read-config.ts","../src/gen-graphs.ts","../src/gen-inputs.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport type { BuildContext, ModelSpec } from '@sdeverywhere/build'\n\nimport { createConfigContext } from './context'\nimport { writeModelSpec } from './gen-model-spec'\nimport { generateConfigSpecs, writeConfigSpecs, writeSpecTypes } from './gen-config-specs'\n\nexport interface ConfigOutputPaths {\n /** The absolute path to the directory where model spec files will be written. */\n modelSpecsDir?: string\n\n /** The absolute path to the directory where config spec files will be written. */\n configSpecsDir?: string\n\n /** The absolute path to the directory where translated strings will be written. */\n stringsDir?: string\n}\n\nexport interface ConfigOptions {\n /**\n * The absolute path to the directory containing the CSV config files.\n */\n config: string\n\n /**\n * Either a single path to a base output directory (in which case, the recommended\n * directory structure will be used) or a `ConfigOutputPaths` containing specific paths.\n * If a single string is provided, the following subdirectories will be used:\n * <out-dir>/\n * src/\n * config/\n * generated/\n * model/\n * generated/\n * strings/\n */\n out?: string | ConfigOutputPaths\n}\n\n/**\n * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere\n * `UserConfig`. The returned function:\n * - reads CSV files from a `config` directory\n * - writes JS files to the configured output directories\n * - returns a `ModelSpec` that guides the rest of the `sde` build process\n */\nexport function configProcessor(options: ConfigOptions): (buildContext: BuildContext) => Promise<ModelSpec> {\n return buildContext => {\n return processModelConfig(buildContext, options)\n }\n}\n\nasync function processModelConfig(buildContext: BuildContext, options: ConfigOptions): Promise<ModelSpec> {\n const t0 = performance.now()\n\n // Resolve source (config) directory\n if (!existsSync(options.config)) {\n throw new Error(`The provided config dir '${options.config}' does not exist`)\n }\n\n // Resolve output directories\n let outModelSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outModelSpecsDir = joinPath(options.out, 'src', 'model', 'generated')\n } else {\n outModelSpecsDir = options.out.modelSpecsDir\n }\n }\n\n let outConfigSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outConfigSpecsDir = joinPath(options.out, 'src', 'config', 'generated')\n } else {\n outConfigSpecsDir = options.out.configSpecsDir\n }\n }\n\n let outStringsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outStringsDir = joinPath(options.out, 'strings')\n } else {\n outStringsDir = options.out.stringsDir\n }\n }\n\n // Create a container for strings, variables, etc\n const context = createConfigContext(buildContext, options.config)\n\n // Write the generated files\n context.log('info', 'Generating files...')\n\n const configSpecs = generateConfigSpecs(context)\n if (outConfigSpecsDir) {\n context.log('verbose', ' Writing config specs')\n writeConfigSpecs(context, configSpecs, outConfigSpecsDir)\n writeSpecTypes(context, outConfigSpecsDir)\n }\n\n if (outModelSpecsDir) {\n context.log('verbose', ' Writing model specs')\n writeModelSpec(context, outModelSpecsDir)\n }\n\n if (outStringsDir) {\n context.log('verbose', ' Writing strings')\n context.writeStringsFiles(outStringsDir)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `Done generating files (${elapsed}s)`)\n\n return {\n startTime: context.modelStartTime,\n endTime: context.modelEndTime,\n inputs: context.getOrderedInputs(),\n outputs: context.getOrderedOutputs(),\n datFiles: context.datFiles\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { join as joinPath, relative } from 'path'\n\nimport parseCsv from 'csv-parse/lib/sync.js'\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 modelStartTime: number,\n public readonly modelEndTime: number,\n public readonly graphDefaultMinTime: number,\n public readonly graphDefaultMaxTime: number,\n public readonly datFiles: string[]\n ) {}\n\n /**\n * Read a CSV file of the given name from the config directory.\n *\n * @param name The base name of the CSV file.\n */\n readConfigCsvFile(name: string): CsvRow[] {\n return readConfigCsvFile(this.configDir, name)\n }\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n this.buildContext.log(level, msg)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.buildContext.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n addInputVariable(inputVarName: string, defaultValue: number, minValue: number, maxValue: number): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(inputVarName)\n if (this.inputSpecs.get(varId)) {\n // Fail if the variable was already added (there should only be one spec\n // per input variable)\n console.error(`ERROR: Input variable ${inputVarName} was already added`)\n }\n this.inputSpecs.set(varId, {\n varName: inputVarName,\n defaultValue,\n minValue,\n maxValue\n })\n }\n\n addOutputVariable(outputVarName: string): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(outputVarName)\n this.outputVarNames.set(varId, outputVarName)\n }\n\n addStaticVariable(sourceName: string, varName: string): void {\n const sourceVarNames = this.staticVarNames.get(sourceName)\n if (sourceVarNames) {\n sourceVarNames.add(varName)\n } else {\n const varNames: Set<string> = new Set()\n varNames.add(varName)\n this.staticVarNames.set(sourceName, varNames)\n }\n }\n\n getHexColorForId(colorId: ColorId): HexColor {\n return this.colorMap.get(colorId)\n }\n\n getOrderedInputs(): InputSpec[] {\n // TODO: It would be nice to alphabetize the inputs, but currently we have\n // code that assumes that the InputSpecs in the map have the same order\n // as the variables in the spec file and model config, so preserve the\n // existing order here for now\n return Array.from(this.inputSpecs.values())\n }\n\n getOrderedOutputs(): OutputSpec[] {\n // Sort the output variable names alphabetically\n const alphabetical = (a: string, b: string) => (a > b ? 1 : b > a ? -1 : 0)\n const varNames = Array.from(this.outputVarNames.values()).sort(alphabetical)\n return varNames.map(varName => {\n return {\n varName\n }\n })\n }\n\n writeStringsFiles(dstDir: string): void {\n this.strings.writeJsFiles(this.buildContext, dstDir /*, xlatLangs*/)\n }\n}\n\nexport function createConfigContext(buildContext: BuildContext, configDir: string): ConfigContext {\n // Read basic model configuration from `model.csv`\n const modelCsv = readConfigCsvFile(configDir, 'model')[0]\n const modelStartTime = Number(modelCsv['model start time'])\n const modelEndTime = Number(modelCsv['model end time'])\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(\n buildContext,\n configDir,\n strings,\n colors,\n modelStartTime,\n modelEndTime,\n graphDefaultMinTime,\n graphDefaultMaxTime,\n datFiles\n )\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_lines_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> ')\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 ` ` to the Unicode\n // equivalent (`U+00A0`); we will convert it back to ` ` to make\n // it more obvious and easier to view in a translation tool\n clean = clean.replace(/\\u00a0/gi, ' ')\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 startTime = ${context.modelStartTime}`)\n emit(`export const endTime = ${context.modelEndTime}`)\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('&', '&')\n const displayTitle = htmlToUtf8(menuTitle || title).replace('&', '&')\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('&', '&')\n return `${typeLabel} ${kind}: ${groupTitle} > ${labelText}`\n }\n\n const labelKey = strings.add(key('label'), label, layout, strCtxt('Label'))\n\n const listingLabel = optionalString(r['listing label'])\n let listingLabelKey: StringKey\n if (listingLabel) {\n listingLabelKey = strings.add(key('action_label'), listingLabel, layout, strCtxt('Action Label'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'input-descriptions')\n }\n\n // Converts a slider row in `inputs.csv` to a `SliderSpec`\n function sliderSpecFromCsv(): SliderSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const defaultValue = requiredNumber('slider/switch default')\n const minValue = requiredNumber('slider min')\n const maxValue = requiredNumber('slider max')\n const step = requiredNumber('slider step')\n const reversed = optionalString(r['reversed']) === 'yes'\n\n if (defaultValue < minValue || defaultValue > maxValue) {\n let e = `Default value for slider ${inputId} is out of range: `\n e += `default=${defaultValue} min=${minValue} max=${maxValue}`\n throw new Error(e)\n }\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n const format = optionalString(r['format']) || '.0f'\n\n const units = optionalString(r['units'])\n let unitsKey: StringKey\n if (units) {\n unitsKey = strings.add(genStringKey('input_units', units), units, layout, 'Slider Units')\n }\n\n const rangeInfo = getSliderRangeInfo(r, maxValue, context)\n const rangeLabelKeys = rangeInfo.labelKeys\n const rangeDividers = rangeInfo.dividers\n\n return {\n kind: 'slider',\n id: inputId,\n varId,\n varName,\n defaultValue,\n minValue,\n maxValue,\n step,\n reversed,\n labelKey,\n listingLabelKey,\n descriptionKey,\n unitsKey,\n rangeLabelKeys,\n rangeDividers,\n format\n }\n }\n\n // Converts a switch row in `inputs.csv` to a `SwitchSpec`\n function switchSpecFromCsv(): SwitchSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const onValue = requiredNumber('enabled value')\n const offValue = requiredNumber('disabled value')\n const defaultValue = requiredNumber('slider/switch default')\n if (defaultValue !== onValue && defaultValue !== offValue) {\n throw new Error(\n `Invalid default value for switch ${inputId}: off=${offValue} on=${onValue} default=${defaultValue}`\n )\n }\n\n const minValue = Math.min(offValue, onValue)\n const maxValue = Math.max(offValue, onValue)\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n // The `controlled input ids` field dictates which rows are active\n // when this switch is on or off. Examples of the format of this field:\n // 1;2;3|4;5;6\n // 1|2;3;4;5\n // |1\n // On the left side of the '|' are the rows that are active when the\n // switch is in an off position, and on the right side are the rows\n // that are active when the switch is in an on position. Usually the\n // \"active when off\" rows are above the switch in the UI, and the\n // \"active when on\" rows are below the switch.\n const controlledInputIds = requiredString('controlled input ids')\n const controlledParts = controlledInputIds.split('|')\n const rowsActiveWhenOff = controlledParts[0].split(';').filter(id => id.trim().length > 0)\n const rowsActiveWhenOn = controlledParts[1].split(';').filter(id => id.trim().length > 0)\n\n return {\n kind: 'switch',\n id: inputId,\n varId,\n varName,\n labelKey,\n listingLabelKey,\n descriptionKey,\n defaultValue,\n offValue,\n onValue,\n slidersActiveWhenOff: rowsActiveWhenOff,\n slidersActiveWhenOn: rowsActiveWhenOn\n }\n }\n\n // Call a different converter function depending on the input type\n let inputSpec: SliderSpec | SwitchSpec\n switch (inputType) {\n case 'slider': {\n inputSpec = sliderSpecFromCsv()\n break\n }\n case 'switch':\n case 'checkbox': {\n inputSpec = switchSpecFromCsv()\n break\n }\n case 'checkbox group':\n // TODO\n // XXX: For now, we specify the checkbox IDs as a semicolon separated list\n // in the \"varname\" cell\n // const checkboxIds = row['varname'].split(';')\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n return inputSpec\n}\n\ninterface SliderRangeInfo {\n labelKeys: StringKey[]\n dividers: number[]\n}\n\nfunction getSliderRangeInfo(r: CsvRow, maxValue: number, context: ConfigContext): SliderRangeInfo {\n const strings = context.strings\n const labelKeys: StringKey[] = []\n const dividers: number[] = []\n\n // Get all labels to determine the number of ranges\n let rangeNum = 1\n while (rangeNum <= 5) {\n const label = optionalString(r[`range ${rangeNum} label`])\n if (!label) {\n break\n }\n const labelKey = strings.add(genStringKey('input_range', label), label, layout, 'Slider Range Label')\n if (!labelKey) {\n break\n }\n labelKeys.push(labelKey)\n rangeNum++\n }\n\n // Find dividing points between ranges; the absence of a final dividing point\n // indicates the use of discrete values\n const numRanges = rangeNum - 1\n for (rangeNum = 2; rangeNum <= numRanges; rangeNum++) {\n let divider = optionalNumber(r[`range ${rangeNum} start`])\n if (divider === undefined) {\n // Fall back on the slider max value when a divider is missing\n divider = maxValue\n }\n dividers.push(divider)\n }\n\n return {\n labelKeys,\n dividers\n }\n}\n"],"mappings":"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","names":["joinPath","layout","layout","readFileSync","layout","key","kind","key","readFileSync","joinPath"]}
|
|
1
|
+
{"version":3,"sources":["../src/processor.ts","../src/context.ts","../src/strings.ts","../src/var-names.ts","../src/gen-model-spec.ts","../src/gen-config-specs.ts","../src/read-config.ts","../src/gen-graphs.ts","../src/gen-inputs.ts"],"sourcesContent":["// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { existsSync } from 'fs'\nimport { join as joinPath } from 'path'\n\nimport type { BuildContext, ModelSpec } from '@sdeverywhere/build'\n\nimport { createConfigContext } from './context'\nimport { writeModelSpec } from './gen-model-spec'\nimport { generateConfigSpecs, writeConfigSpecs, writeSpecTypes } from './gen-config-specs'\n\nexport interface ConfigOutputPaths {\n /** The absolute path to the directory where model spec files will be written. */\n modelSpecsDir?: string\n\n /** The absolute path to the directory where config spec files will be written. */\n configSpecsDir?: string\n\n /** The absolute path to the directory where translated strings will be written. */\n stringsDir?: string\n}\n\nexport interface ConfigOptions {\n /**\n * The absolute path to the directory containing the CSV config files.\n */\n config: string\n\n /**\n * Either a single path to a base output directory (in which case, the recommended\n * directory structure will be used) or a `ConfigOutputPaths` containing specific paths.\n * If a single string is provided, the following subdirectories will be used:\n * <out-dir>/\n * src/\n * config/\n * generated/\n * model/\n * generated/\n * strings/\n */\n out?: string | ConfigOutputPaths\n}\n\n/**\n * Returns a function that can be passed as the `modelSpec` function for the SDEverywhere\n * `UserConfig`. The returned function:\n * - reads CSV files from a `config` directory\n * - writes JS files to the configured output directories\n * - returns a `ModelSpec` that guides the rest of the `sde` build process\n */\nexport function configProcessor(options: ConfigOptions): (buildContext: BuildContext) => Promise<ModelSpec> {\n return buildContext => {\n return processModelConfig(buildContext, options)\n }\n}\n\nasync function processModelConfig(buildContext: BuildContext, options: ConfigOptions): Promise<ModelSpec> {\n const t0 = performance.now()\n\n // Resolve source (config) directory\n if (!existsSync(options.config)) {\n throw new Error(`The provided config dir '${options.config}' does not exist`)\n }\n\n // Resolve output directories\n let outModelSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outModelSpecsDir = joinPath(options.out, 'src', 'model', 'generated')\n } else {\n outModelSpecsDir = options.out.modelSpecsDir\n }\n }\n\n let outConfigSpecsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outConfigSpecsDir = joinPath(options.out, 'src', 'config', 'generated')\n } else {\n outConfigSpecsDir = options.out.configSpecsDir\n }\n }\n\n let outStringsDir: string\n if (options.out) {\n if (typeof options.out === 'string') {\n outStringsDir = joinPath(options.out, 'strings')\n } else {\n outStringsDir = options.out.stringsDir\n }\n }\n\n // Create a container for strings, variables, etc\n const context = createConfigContext(buildContext, options.config)\n\n // Write the generated files\n context.log('info', 'Generating files...')\n\n const configSpecs = generateConfigSpecs(context)\n if (outConfigSpecsDir) {\n context.log('verbose', ' Writing config specs')\n writeConfigSpecs(context, configSpecs, outConfigSpecsDir)\n writeSpecTypes(context, outConfigSpecsDir)\n }\n\n if (outModelSpecsDir) {\n context.log('verbose', ' Writing model specs')\n writeModelSpec(context, outModelSpecsDir)\n }\n\n if (outStringsDir) {\n context.log('verbose', ' Writing strings')\n context.writeStringsFiles(outStringsDir)\n }\n\n const t1 = performance.now()\n const elapsed = ((t1 - t0) / 1000).toFixed(1)\n context.log('info', `Done generating files (${elapsed}s)`)\n\n return {\n inputs: context.getOrderedInputs(),\n outputs: context.getOrderedOutputs(),\n datFiles: context.datFiles\n }\n}\n","// Copyright (c) 2022 Climate Interactive / New Venture Fund\n\nimport { readFileSync } from 'fs'\nimport { join as joinPath, relative } from 'path'\n\nimport { parse as parseCsv } from 'csv-parse/sync'\n\nimport type { BuildContext, InputSpec, LogLevel, OutputSpec } from '@sdeverywhere/build'\n\nimport type { HexColor } from './spec-types'\nimport { Strings } from './strings'\nimport type { InputVarId, OutputVarId } from './var-names'\nimport { sdeNameForVensimVarName } from './var-names'\n\nexport type CsvRow = { [key: string]: string }\nexport type ColorId = string\n\nexport class ConfigContext {\n private readonly inputSpecs: Map<InputVarId, InputSpec> = new Map()\n private readonly outputVarNames: Map<OutputVarId, string> = new Map()\n private readonly staticVarNames: Map<string, Set<string>> = new Map()\n\n constructor(\n private readonly buildContext: BuildContext,\n private readonly configDir: string,\n public readonly strings: Strings,\n private readonly colorMap: Map<ColorId, HexColor>,\n public readonly graphDefaultMinTime: number,\n public readonly graphDefaultMaxTime: number,\n public readonly datFiles: string[]\n ) {}\n\n /**\n * Read a CSV file of the given name from the config directory.\n *\n * @param name The base name of the CSV file.\n */\n readConfigCsvFile(name: string): CsvRow[] {\n return readConfigCsvFile(this.configDir, name)\n }\n\n /**\n * Log a message to the console and/or the in-browser overlay panel.\n *\n * @param level The log level (verbose, info, error).\n * @param msg The message.\n */\n log(level: LogLevel, msg: string): void {\n this.buildContext.log(level, msg)\n }\n\n /**\n * Write a file to the staged directory.\n *\n * This file will be copied (along with other staged files) into the destination\n * directory only after the build process has completed. Copying all staged files\n * at once helps improve the local development experience by making it so that\n * live reloading tools only need to refresh once instead of every time a build\n * file is written.\n *\n * @param srcDir The directory underneath the configured `staged` directory where\n * the file will be written (this must be a relative path).\n * @param dstDir The absolute path to the destination directory where the staged\n * file will be copied when the build has completed.\n * @param filename The name of the file.\n * @param content The file content.\n */\n writeStagedFile(srcDir: string, dstDir: string, filename: string, content: string): void {\n this.buildContext.writeStagedFile(srcDir, dstDir, filename, content)\n }\n\n addInputVariable(inputVarName: string, defaultValue: number, minValue: number, maxValue: number): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(inputVarName)\n if (this.inputSpecs.get(varId)) {\n // Fail if the variable was already added (there should only be one spec\n // per input variable)\n console.error(`ERROR: Input variable ${inputVarName} was already added`)\n }\n this.inputSpecs.set(varId, {\n varName: inputVarName,\n defaultValue,\n minValue,\n maxValue\n })\n }\n\n addOutputVariable(outputVarName: string): void {\n // We use the C name as the key to avoid redundant entries in cases where\n // the csv file refers to variables with different capitalization\n const varId = sdeNameForVensimVarName(outputVarName)\n this.outputVarNames.set(varId, outputVarName)\n }\n\n addStaticVariable(sourceName: string, varName: string): void {\n const sourceVarNames = this.staticVarNames.get(sourceName)\n if (sourceVarNames) {\n sourceVarNames.add(varName)\n } else {\n const varNames: Set<string> = new Set()\n varNames.add(varName)\n this.staticVarNames.set(sourceName, varNames)\n }\n }\n\n getHexColorForId(colorId: ColorId): HexColor {\n return this.colorMap.get(colorId)\n }\n\n getOrderedInputs(): InputSpec[] {\n // TODO: It would be nice to alphabetize the inputs, but currently we have\n // code that assumes that the InputSpecs in the map have the same order\n // as the variables in the spec file and model config, so preserve the\n // existing order here for now\n return Array.from(this.inputSpecs.values())\n }\n\n getOrderedOutputs(): OutputSpec[] {\n // Sort the output variable names alphabetically\n const alphabetical = (a: string, b: string) => (a > b ? 1 : b > a ? -1 : 0)\n const varNames = Array.from(this.outputVarNames.values()).sort(alphabetical)\n return varNames.map(varName => {\n return {\n varName\n }\n })\n }\n\n writeStringsFiles(dstDir: string): void {\n this.strings.writeJsFiles(this.buildContext, dstDir /*, xlatLangs*/)\n }\n}\n\nexport function createConfigContext(buildContext: BuildContext, configDir: string): ConfigContext {\n // Read basic model configuration from `model.csv`\n const modelCsv = readConfigCsvFile(configDir, 'model')[0]\n const graphDefaultMinTime = Number(modelCsv['graph default min time'])\n const graphDefaultMaxTime = Number(modelCsv['graph default max time'])\n const datFilesString = modelCsv['model dat files']\n const origDatFiles = datFilesString.length > 0 ? datFilesString.split(';') : []\n\n // The dat file paths in the config file are assumed to be relative to\n // the project directory (i.e., the directory where the `sde.config.js`\n // file resides), so we need to convert to paths that are relative to\n // the `sde-prep` directory (since that is the \"model\" directory from\n // the perspective of the compile package).\n const prepDir = buildContext.config.prepDir\n const projDir = buildContext.config.rootDir\n const datFiles = origDatFiles.map(f => joinPath(relative(prepDir, projDir), f))\n\n // Read the static strings from `strings.csv`\n const strings = readStringsCsv(configDir)\n\n // Read color configuration from `colors.csv`\n const colorsCsv = readConfigCsvFile(configDir, 'colors')\n const colors = new Map()\n for (const row of colorsCsv) {\n const colorId = row['id']\n const hexColor = row['hex code']\n colors.set(colorId, hexColor)\n }\n\n return new ConfigContext(buildContext, configDir, strings, colors, graphDefaultMinTime, graphDefaultMaxTime, datFiles)\n}\n\nfunction configFilePath(configDir: string, name: string, ext: string): string {\n return joinPath(configDir, `${name}.${ext}`)\n}\n\nfunction readCsvFile(path: string): CsvRow[] {\n const data = readFileSync(path, 'utf8')\n return parseCsv(data, {\n columns: true,\n trim: true,\n skip_empty_lines: true,\n skip_records_with_empty_values: true\n })\n}\n\nfunction readConfigCsvFile(configDir: string, name: string): CsvRow[] {\n return readCsvFile(configFilePath(configDir, name, 'csv'))\n}\n\n/**\n * Initialize a `Strings` instance with the core strings from `strings.csv`.\n */\nfunction readStringsCsv(configDir: string): Strings {\n const strings = new Strings()\n\n // TODO: For now we use the same \"layout\" and \"context\" for all core strings\n const layout = 'default'\n const context = 'Core'\n\n const rows = readConfigCsvFile(configDir, 'strings')\n for (const row of rows) {\n const key = row['id']\n let str = row['string']\n str = str ? str.trim() : ''\n if (str) {\n strings.add(key, str, layout, context)\n }\n }\n\n return strings\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nimport sanitizeHtml from 'sanitize-html'\n\nimport type { BuildContext } from '@sdeverywhere/build'\n\nimport type { StringKey } from './spec-types'\n\ninterface StringRecord {\n key: StringKey\n str: string\n layout: string\n context: string\n grouping: string\n appendedStringKeys?: string[]\n}\n\ntype LangCode = string\ntype StringMap = Map<StringKey, string>\ntype XlatMap = Map<LangCode, StringMap>\n\nexport class Strings {\n private readonly records: Map<StringKey, StringRecord> = new Map()\n\n add(\n key: StringKey,\n str: string,\n layout: string,\n context: string,\n grouping?: string,\n appendedStringKeys?: string[]\n ): StringKey {\n checkInvisibleCharacters(str || '')\n\n if (!key) {\n throw new Error(`Must provide a key for the string: ${str}`)\n }\n\n const validKey = /^[0-9a-z_]+$/.test(key)\n if (!validKey) {\n throw new Error(`String key contains undesirable characters: ${key}`)\n }\n\n if (!layout) {\n throw new Error(`Must provide a layout (e.g., 'layout1' or 'not-translated')`)\n }\n\n if (!context) {\n throw new Error(`Must provide a context string: key=${key}, string=${str}`)\n }\n\n if (context === 'Core') {\n // For core strings from the `strings.csv` file, leave the context empty for now\n // (indicating this is a \"general\" string with no particular context)\n context = undefined\n }\n\n if (!grouping) {\n // Use 'primary' if grouping is not specified\n grouping = 'primary'\n }\n\n // Convert some HTML tags and entities to UTF-8\n if (str) {\n str = str.trim()\n str = htmlToUtf8(str)\n }\n\n // If the trimmed string is empty, do not create a new key, and return an empty string\n if (!str) {\n return ''\n }\n\n if (this.records.has(key)) {\n // TODO: For now, allow certain keys to appear more than once; we only add the string once\n const prefix = key.substring(0, key.indexOf('__'))\n switch (prefix) {\n case 'graph_dataset_label':\n case 'graph_xaxis_label':\n case 'graph_yaxis_label':\n case 'input_group_title':\n case 'input_range':\n case 'input_units':\n break\n default:\n throw new Error(`More than one string with key=${key}`)\n }\n }\n\n // Add the string record\n this.records.set(key, {\n key,\n str,\n layout,\n context,\n grouping,\n appendedStringKeys\n })\n\n return key\n }\n\n /**\n * Write a `<lang>.js` file containing translated strings for each supported language.\n *\n * @param context The build context.\n * @param dstDir The `strings` directory in the core package.\n * @param xlatLangs The set of languages that are configured for translation.\n */\n writeJsFiles(context: BuildContext, dstDir: string /*, xlatLangs: Map<LangCode, LangSpec>*/): void {\n writeLangJsFiles(context, dstDir, this.records /*, xlatLangs*/)\n }\n}\n\nfunction getSortedRecords(records: Map<StringKey, StringRecord>): StringRecord[] {\n // Sort records by string key\n return Array.from(records.values()).sort((a, b) => {\n return a.key > b.key ? 1 : b.key > a.key ? -1 : 0\n })\n}\n\nfunction checkInvisibleCharacters(s: string): void {\n if (s.includes('\\u00a0')) {\n const e = s.replace(/\\u00a0/g, 'HERE')\n throw new Error(\n `String contains one or more non-breaking space characters (to fix, replace \"HERE\" with a normal space):\\n ${e}`\n )\n }\n}\n\nfunction utf8SubscriptToHtml(key: StringKey, s: string): string {\n if (key.includes('graph_yaxis_label')) {\n // Chart.js doesn't support using HTML tags like subscripts or superscripts\n // in axis labels. For now, we will convert subscript literals in axis labels\n // to a simple number. (We could preserve the subscript literal, but it doesn't\n // render all that well.)\n s = s.replace(/₂/gi, '2')\n s = s.replace(/₃/gi, '3')\n s = s.replace(/₄/gi, '4')\n s = s.replace(/₆/gi, '6')\n return s\n }\n\n // Unicode subscript literals render differently in some browsers (very low\n // in Safari for example), so we will replace them with HTML `sub` tags\n s = s.replace(/₂/gi, '<sub>2</sub>')\n s = s.replace(/₃/gi, '<sub>3</sub>')\n s = s.replace(/₄/gi, '<sub>4</sub>')\n s = s.replace(/₆/gi, '<sub>6</sub>')\n\n // If the subscript tag is followed by a space, that space needs to be\n // replaced with a non-breaking space, otherwise the whitespace will be lost\n s = s.replace(/<\\/sub> /gi, '</sub> ')\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 ` ` to the Unicode\n // equivalent (`U+00A0`); we will convert it back to ` ` to make\n // it more obvious and easier to view in a translation tool\n clean = clean.replace(/\\u00a0/gi, ' ')\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('&', '&')\n const displayTitle = htmlToUtf8(menuTitle || title).replace('&', '&')\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('&', '&')\n return `${typeLabel} ${kind}: ${groupTitle} > ${labelText}`\n }\n\n const labelKey = strings.add(key('label'), label, layout, strCtxt('Label'))\n\n const listingLabel = optionalString(r['listing label'])\n let listingLabelKey: StringKey\n if (listingLabel) {\n listingLabelKey = strings.add(key('action_label'), listingLabel, layout, strCtxt('Action Label'))\n }\n\n let descriptionKey: StringKey\n if (description) {\n descriptionKey = strings.add(key('description'), description, layout, strCtxt('Description'), 'input-descriptions')\n }\n\n // Converts a slider row in `inputs.csv` to a `SliderSpec`\n function sliderSpecFromCsv(): SliderSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const defaultValue = requiredNumber('slider/switch default')\n const minValue = requiredNumber('slider min')\n const maxValue = requiredNumber('slider max')\n const step = requiredNumber('slider step')\n const reversed = optionalString(r['reversed']) === 'yes'\n\n if (defaultValue < minValue || defaultValue > maxValue) {\n let e = `Default value for slider ${inputId} is out of range: `\n e += `default=${defaultValue} min=${minValue} max=${maxValue}`\n throw new Error(e)\n }\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n const format = optionalString(r['format']) || '.0f'\n\n const units = optionalString(r['units'])\n let unitsKey: StringKey\n if (units) {\n unitsKey = strings.add(genStringKey('input_units', units), units, layout, 'Slider Units')\n }\n\n const rangeInfo = getSliderRangeInfo(r, maxValue, context)\n const rangeLabelKeys = rangeInfo.labelKeys\n const rangeDividers = rangeInfo.dividers\n\n return {\n kind: 'slider',\n id: inputId,\n varId,\n varName,\n defaultValue,\n minValue,\n maxValue,\n step,\n reversed,\n labelKey,\n listingLabelKey,\n descriptionKey,\n unitsKey,\n rangeLabelKeys,\n rangeDividers,\n format\n }\n }\n\n // Converts a switch row in `inputs.csv` to a `SwitchSpec`\n function switchSpecFromCsv(): SwitchSpec {\n const varName = requiredString('varname')\n const varId = sdeNameForVensimVarName(varName)\n\n const onValue = requiredNumber('enabled value')\n const offValue = requiredNumber('disabled value')\n const defaultValue = requiredNumber('slider/switch default')\n if (defaultValue !== onValue && defaultValue !== offValue) {\n throw new Error(\n `Invalid default value for switch ${inputId}: off=${offValue} on=${onValue} default=${defaultValue}`\n )\n }\n\n const minValue = Math.min(offValue, onValue)\n const maxValue = Math.max(offValue, onValue)\n context.addInputVariable(varName, defaultValue, minValue, maxValue)\n\n // The `controlled input ids` field dictates which rows are active\n // when this switch is on or off. Examples of the format of this field:\n // 1;2;3|4;5;6\n // 1|2;3;4;5\n // |1\n // On the left side of the '|' are the rows that are active when the\n // switch is in an off position, and on the right side are the rows\n // that are active when the switch is in an on position. Usually the\n // \"active when off\" rows are above the switch in the UI, and the\n // \"active when on\" rows are below the switch.\n const controlledInputIds = requiredString('controlled input ids')\n const controlledParts = controlledInputIds.split('|')\n const rowsActiveWhenOff = controlledParts[0].split(';').filter(id => id.trim().length > 0)\n const rowsActiveWhenOn = controlledParts[1].split(';').filter(id => id.trim().length > 0)\n\n return {\n kind: 'switch',\n id: inputId,\n varId,\n varName,\n labelKey,\n listingLabelKey,\n descriptionKey,\n defaultValue,\n offValue,\n onValue,\n slidersActiveWhenOff: rowsActiveWhenOff,\n slidersActiveWhenOn: rowsActiveWhenOn\n }\n }\n\n // Call a different converter function depending on the input type\n let inputSpec: SliderSpec | SwitchSpec\n switch (inputType) {\n case 'slider': {\n inputSpec = sliderSpecFromCsv()\n break\n }\n case 'switch':\n case 'checkbox': {\n inputSpec = switchSpecFromCsv()\n break\n }\n case 'checkbox group':\n // TODO\n // XXX: For now, we specify the checkbox IDs as a semicolon separated list\n // in the \"varname\" cell\n // const checkboxIds = row['varname'].split(';')\n break\n default:\n throw new Error(`Unexpected input type ${inputType}`)\n }\n\n return inputSpec\n}\n\ninterface SliderRangeInfo {\n labelKeys: StringKey[]\n dividers: number[]\n}\n\nfunction getSliderRangeInfo(r: CsvRow, maxValue: number, context: ConfigContext): SliderRangeInfo {\n const strings = context.strings\n const labelKeys: StringKey[] = []\n const dividers: number[] = []\n\n // Get all labels to determine the number of ranges\n let rangeNum = 1\n while (rangeNum <= 5) {\n const label = optionalString(r[`range ${rangeNum} label`])\n if (!label) {\n break\n }\n const labelKey = strings.add(genStringKey('input_range', label), label, layout, 'Slider Range Label')\n if (!labelKey) {\n break\n }\n labelKeys.push(labelKey)\n rangeNum++\n }\n\n // Find dividing points between ranges; the absence of a final dividing point\n // indicates the use of discrete values\n const numRanges = rangeNum - 1\n for (rangeNum = 2; rangeNum <= numRanges; rangeNum++) {\n let divider = optionalNumber(r[`range ${rangeNum} start`])\n if (divider === undefined) {\n // Fall back on the slider max value when a divider is missing\n divider = maxValue\n }\n dividers.push(divider)\n }\n\n return {\n labelKeys,\n dividers\n }\n}\n"],"mappings":"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","names":["joinPath","layout","layout","readFileSync","layout","key","kind","key","readFileSync","joinPath"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdeverywhere/plugin-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist/**",
|
|
6
6
|
"template-config/**"
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"byline": "^5.0.0",
|
|
21
|
-
"csv-parse": "^
|
|
21
|
+
"csv-parse": "^5.3.3",
|
|
22
22
|
"sanitize-html": "^2.7.1"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@sdeverywhere/build": "^0.
|
|
25
|
+
"@sdeverywhere/build": "^0.3.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@sdeverywhere/build": "*",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
0,100,
|
|
1
|
+
graph default min time,graph default max time,model dat files
|
|
2
|
+
0,100,Data1.dat;Data2.dat
|