@sdeverywhere/plugin-config 0.1.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 ADDED
@@ -0,0 +1,857 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/index.ts
23
+ var src_exports = {};
24
+ __export(src_exports, {
25
+ configProcessor: () => configProcessor
26
+ });
27
+ module.exports = __toCommonJS(src_exports);
28
+
29
+ // src/processor.ts
30
+ var import_fs3 = require("fs");
31
+ var import_path3 = require("path");
32
+
33
+ // src/context.ts
34
+ var import_fs = require("fs");
35
+ var import_path = require("path");
36
+ var import_sync = __toESM(require("csv-parse/lib/sync.js"), 1);
37
+
38
+ // src/strings.ts
39
+ var import_sanitize_html = __toESM(require("sanitize-html"), 1);
40
+ var Strings = class {
41
+ constructor() {
42
+ this.records = /* @__PURE__ */ new Map();
43
+ }
44
+ add(key, str, layout2, context, grouping, appendedStringKeys) {
45
+ checkInvisibleCharacters(str || "");
46
+ if (!key) {
47
+ throw new Error(`Must provide a key for the string: ${str}`);
48
+ }
49
+ const validKey = /^[0-9a-z_]+$/.test(key);
50
+ if (!validKey) {
51
+ throw new Error(`String key contains undesirable characters: ${key}`);
52
+ }
53
+ if (!layout2) {
54
+ throw new Error(`Must provide a layout (e.g., 'layout1' or 'not-translated')`);
55
+ }
56
+ if (!context) {
57
+ throw new Error(`Must provide a context string: key=${key}, string=${str}`);
58
+ }
59
+ if (context === "Core") {
60
+ context = void 0;
61
+ }
62
+ if (!grouping) {
63
+ grouping = "primary";
64
+ }
65
+ if (str) {
66
+ str = str.trim();
67
+ str = htmlToUtf8(str);
68
+ }
69
+ if (!str) {
70
+ return "";
71
+ }
72
+ if (this.records.has(key)) {
73
+ const prefix = key.substring(0, key.indexOf("__"));
74
+ switch (prefix) {
75
+ case "graph_dataset_label":
76
+ case "graph_xaxis_label":
77
+ case "graph_yaxis_label":
78
+ case "input_group_title":
79
+ case "input_range":
80
+ case "input_units":
81
+ break;
82
+ default:
83
+ throw new Error(`More than one string with key=${key}`);
84
+ }
85
+ }
86
+ this.records.set(key, {
87
+ key,
88
+ str,
89
+ layout: layout2,
90
+ context,
91
+ grouping,
92
+ appendedStringKeys
93
+ });
94
+ return key;
95
+ }
96
+ writeJsFiles(context, dstDir) {
97
+ writeLangJsFiles(context, dstDir, this.records);
98
+ }
99
+ };
100
+ function getSortedRecords(records) {
101
+ return Array.from(records.values()).sort((a, b) => {
102
+ return a.key > b.key ? 1 : b.key > a.key ? -1 : 0;
103
+ });
104
+ }
105
+ function checkInvisibleCharacters(s) {
106
+ if (s.includes("\xA0")) {
107
+ const e = s.replace(/\u00a0/g, "HERE");
108
+ throw new Error(`String contains one or more non-breaking space characters (to fix, replace "HERE" with a normal space):
109
+ ${e}`);
110
+ }
111
+ }
112
+ function utf8SubscriptToHtml(key, s) {
113
+ if (key.includes("graph_yaxis_label")) {
114
+ s = s.replace(/₂/gi, "2");
115
+ s = s.replace(/₃/gi, "3");
116
+ s = s.replace(/₄/gi, "4");
117
+ s = s.replace(/₆/gi, "6");
118
+ return s;
119
+ }
120
+ s = s.replace(/₂/gi, "<sub>2</sub>");
121
+ s = s.replace(/₃/gi, "<sub>3</sub>");
122
+ s = s.replace(/₄/gi, "<sub>4</sub>");
123
+ s = s.replace(/₆/gi, "<sub>6</sub>");
124
+ s = s.replace(/<\/sub> /gi, "</sub>&nbsp;");
125
+ return s;
126
+ }
127
+ function htmlSubscriptAndSuperscriptToUtf8(s) {
128
+ s = s.replace(/<sub>(\d)<\/sub>/gi, (_match, p1) => String.fromCharCode(8320 + Number(p1)));
129
+ s = s.replace(/<sup>6<\/sup>/gi, "\u2076");
130
+ s = s.replace(/<sup>9<\/sup>/gi, "\u2079");
131
+ return s;
132
+ }
133
+ function htmlToUtf8(orig) {
134
+ let s = orig;
135
+ s = htmlSubscriptAndSuperscriptToUtf8(s);
136
+ let clean = (0, import_sanitize_html.default)(s, {
137
+ allowedTags: ["a", "b", "br", "i", "em", "li", "p", "strong", "sub", "sup", "ul"],
138
+ allowedAttributes: {
139
+ a: ["href", "target", "rel"]
140
+ }
141
+ });
142
+ clean = clean.replace(/\u00a0/gi, "&nbsp;");
143
+ return clean;
144
+ }
145
+ function genStringKey(prefix, s) {
146
+ checkInvisibleCharacters(s);
147
+ let key = s.toLowerCase();
148
+ key = key.replace(/ – /g, "_");
149
+ key = key.replace(/ \/ /g, "_per_");
150
+ key = key.replace(/ /g, "_");
151
+ key = key.replace("\u2082", "2");
152
+ key = key.replace("<sub>2</sub>", "2");
153
+ key = key.replace(/\$\//g, "dollars_per_");
154
+ key = key.replace(/%\//g, "pct_per_");
155
+ key = key.replace(/\//g, "_per_");
156
+ key = key.replace(/º/g, "degrees_");
157
+ key = key.replace(/\*/g, "_");
158
+ key = key.replace(/%/g, "pct");
159
+ key = key.replace(/\$/g, "dollars");
160
+ key = key.replace(/&/g, "and");
161
+ key = key.replace(/\//g, "per");
162
+ key = key.replace(/:/g, "");
163
+ key = key.replace(/\./g, "");
164
+ key = key.replace(/-/g, "_");
165
+ key = key.replace(/—/g, "_");
166
+ key = key.replace(/–/g, "_");
167
+ key = key.replace(/,/g, "");
168
+ key = key.replace(/\(/g, "");
169
+ key = key.replace(/\)/g, "");
170
+ key = key.replace(/\\n/g, "");
171
+ key = key.replace(/<br>/g, "_");
172
+ return `${prefix}__${key}`;
173
+ }
174
+ function writeLangJsFiles(context, dstDir, records) {
175
+ const xlatMap = /* @__PURE__ */ new Map();
176
+ const sortedRecords = getSortedRecords(records);
177
+ const enStrings = /* @__PURE__ */ new Map();
178
+ for (const record of sortedRecords) {
179
+ const s = record.str;
180
+ enStrings.set(record.key, utf8SubscriptToHtml(record.key, s));
181
+ }
182
+ xlatMap.set("en", enStrings);
183
+ for (const lang of xlatMap.keys()) {
184
+ const stringsForLang = xlatMap.get(lang);
185
+ const stringsObj = Object.fromEntries(stringsForLang);
186
+ const json = JSON.stringify(stringsObj, null, 2);
187
+ context.writeStagedFile("strings", dstDir, `${lang}.js`, `export default ${json}`);
188
+ }
189
+ }
190
+
191
+ // src/var-names.ts
192
+ function sdeNameForVensimName(name) {
193
+ return "_" + name.trim().replace(/"/g, "_").replace(/\s+!$/g, "!").replace(/\s/g, "_").replace(/,/g, "_").replace(/-/g, "_").replace(/\./g, "_").replace(/\$/g, "_").replace(/'/g, "_").replace(/&/g, "_").replace(/%/g, "_").replace(/\//g, "_").replace(/\|/g, "_").toLowerCase();
194
+ }
195
+ function sdeNameForVensimVarName(varName) {
196
+ const m = varName.match(/([^[]+)(?:\[([^\]]+)\])?/);
197
+ if (!m) {
198
+ throw new Error(`Invalid Vensim name: ${varName}`);
199
+ }
200
+ let id = sdeNameForVensimName(m[1]);
201
+ if (m[2]) {
202
+ const subscripts = m[2].split(",").map((x) => sdeNameForVensimName(x));
203
+ id += `[${subscripts.join("][")}]`;
204
+ }
205
+ return id;
206
+ }
207
+
208
+ // src/context.ts
209
+ var ConfigContext = class {
210
+ constructor(buildContext, configDir, strings, colorMap, modelStartTime, modelEndTime, graphDefaultMinTime, graphDefaultMaxTime, datFiles) {
211
+ this.buildContext = buildContext;
212
+ this.configDir = configDir;
213
+ this.strings = strings;
214
+ this.colorMap = colorMap;
215
+ this.modelStartTime = modelStartTime;
216
+ this.modelEndTime = modelEndTime;
217
+ this.graphDefaultMinTime = graphDefaultMinTime;
218
+ this.graphDefaultMaxTime = graphDefaultMaxTime;
219
+ this.datFiles = datFiles;
220
+ this.inputSpecs = /* @__PURE__ */ new Map();
221
+ this.outputVarNames = /* @__PURE__ */ new Map();
222
+ this.staticVarNames = /* @__PURE__ */ new Map();
223
+ }
224
+ readConfigCsvFile(name) {
225
+ return readConfigCsvFile(this.configDir, name);
226
+ }
227
+ log(level, msg) {
228
+ this.buildContext.log(level, msg);
229
+ }
230
+ writeStagedFile(srcDir, dstDir, filename, content) {
231
+ this.buildContext.writeStagedFile(srcDir, dstDir, filename, content);
232
+ }
233
+ addInputVariable(inputVarName, defaultValue, minValue, maxValue) {
234
+ const varId = sdeNameForVensimVarName(inputVarName);
235
+ if (this.inputSpecs.get(varId)) {
236
+ console.error(`ERROR: Input variable ${inputVarName} was already added`);
237
+ }
238
+ this.inputSpecs.set(varId, {
239
+ varName: inputVarName,
240
+ defaultValue,
241
+ minValue,
242
+ maxValue
243
+ });
244
+ }
245
+ addOutputVariable(outputVarName) {
246
+ const varId = sdeNameForVensimVarName(outputVarName);
247
+ this.outputVarNames.set(varId, outputVarName);
248
+ }
249
+ addStaticVariable(sourceName, varName) {
250
+ const sourceVarNames = this.staticVarNames.get(sourceName);
251
+ if (sourceVarNames) {
252
+ sourceVarNames.add(varName);
253
+ } else {
254
+ const varNames = /* @__PURE__ */ new Set();
255
+ varNames.add(varName);
256
+ this.staticVarNames.set(sourceName, varNames);
257
+ }
258
+ }
259
+ getHexColorForId(colorId) {
260
+ return this.colorMap.get(colorId);
261
+ }
262
+ getOrderedInputs() {
263
+ return Array.from(this.inputSpecs.values());
264
+ }
265
+ getOrderedOutputs() {
266
+ const alphabetical = (a, b) => a > b ? 1 : b > a ? -1 : 0;
267
+ const varNames = Array.from(this.outputVarNames.values()).sort(alphabetical);
268
+ return varNames.map((varName) => {
269
+ return {
270
+ varName
271
+ };
272
+ });
273
+ }
274
+ writeStringsFiles(dstDir) {
275
+ this.strings.writeJsFiles(this.buildContext, dstDir);
276
+ }
277
+ };
278
+ function createConfigContext(buildContext, configDir) {
279
+ const modelCsv = readConfigCsvFile(configDir, "model")[0];
280
+ const modelStartTime = Number(modelCsv["model start time"]);
281
+ const modelEndTime = Number(modelCsv["model end time"]);
282
+ const graphDefaultMinTime = Number(modelCsv["graph default min time"]);
283
+ const graphDefaultMaxTime = Number(modelCsv["graph default max time"]);
284
+ const datFilesString = modelCsv["model dat files"];
285
+ const origDatFiles = datFilesString.length > 0 ? datFilesString.split(";") : [];
286
+ const prepDir = buildContext.config.prepDir;
287
+ const projDir = buildContext.config.rootDir;
288
+ const datFiles = origDatFiles.map((f) => (0, import_path.join)((0, import_path.relative)(prepDir, projDir), f));
289
+ const strings = readStringsCsv(configDir);
290
+ const colorsCsv = readConfigCsvFile(configDir, "colors");
291
+ const colors = /* @__PURE__ */ new Map();
292
+ for (const row of colorsCsv) {
293
+ const colorId = row["id"];
294
+ const hexColor = row["hex code"];
295
+ colors.set(colorId, hexColor);
296
+ }
297
+ return new ConfigContext(buildContext, configDir, strings, colors, modelStartTime, modelEndTime, graphDefaultMinTime, graphDefaultMaxTime, datFiles);
298
+ }
299
+ function configFilePath(configDir, name, ext) {
300
+ return (0, import_path.join)(configDir, `${name}.${ext}`);
301
+ }
302
+ function readCsvFile(path) {
303
+ const data = (0, import_fs.readFileSync)(path, "utf8");
304
+ return (0, import_sync.default)(data, {
305
+ columns: true,
306
+ trim: true,
307
+ skip_empty_lines: true,
308
+ skip_lines_with_empty_values: true
309
+ });
310
+ }
311
+ function readConfigCsvFile(configDir, name) {
312
+ return readCsvFile(configFilePath(configDir, name, "csv"));
313
+ }
314
+ function readStringsCsv(configDir) {
315
+ const strings = new Strings();
316
+ const layout2 = "default";
317
+ const context = "Core";
318
+ const rows = readConfigCsvFile(configDir, "strings");
319
+ for (const row of rows) {
320
+ const key = row["id"];
321
+ let str = row["string"];
322
+ str = str ? str.trim() : "";
323
+ if (str) {
324
+ strings.add(key, str, layout2, context);
325
+ }
326
+ }
327
+ return strings;
328
+ }
329
+
330
+ // src/gen-model-spec.ts
331
+ function writeModelSpec(context, dstDir) {
332
+ const inputVarIds = context.getOrderedInputs().map((i) => sdeNameForVensimVarName(i.varName));
333
+ const outputVarIds = context.getOrderedOutputs().map((o) => sdeNameForVensimVarName(o.varName));
334
+ let tsContent = "";
335
+ function emit(s) {
336
+ tsContent += s + "\n";
337
+ }
338
+ emit("// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!");
339
+ emit(`export const startTime = ${context.modelStartTime}`);
340
+ emit(`export const endTime = ${context.modelEndTime}`);
341
+ emit(`export const inputVarIds: string[] = ${JSON.stringify(inputVarIds, null, 2)}`);
342
+ emit(`export const outputVarIds: string[] = ${JSON.stringify(outputVarIds, null, 2)}`);
343
+ context.writeStagedFile("model", dstDir, "model-spec.ts", tsContent);
344
+ }
345
+
346
+ // src/gen-config-specs.ts
347
+ var import_fs2 = require("fs");
348
+ var import_path2 = require("path");
349
+ var import_url = require("url");
350
+
351
+ // src/read-config.ts
352
+ function optionalString(stringValue) {
353
+ if (stringValue !== void 0 && stringValue.length > 0) {
354
+ return stringValue;
355
+ } else {
356
+ return void 0;
357
+ }
358
+ }
359
+ function optionalNumber(stringValue) {
360
+ if (stringValue !== void 0 && stringValue.length > 0) {
361
+ return Number(stringValue);
362
+ } else {
363
+ return void 0;
364
+ }
365
+ }
366
+
367
+ // src/gen-graphs.ts
368
+ function generateGraphSpecs(context) {
369
+ const graphsCsv = context.readConfigCsvFile("graphs");
370
+ const graphSpecs = /* @__PURE__ */ new Map();
371
+ for (const row of graphsCsv) {
372
+ const spec = graphSpecFromCsv(row, context);
373
+ if (spec) {
374
+ graphSpecs.set(spec.id, spec);
375
+ }
376
+ }
377
+ return graphSpecs;
378
+ }
379
+ function graphSpecFromCsv(g, context) {
380
+ const strings = context.strings;
381
+ const layout2 = "default";
382
+ function requiredString(key2) {
383
+ const value = g[key2];
384
+ if (value === void 0 || typeof value !== "string" || value.trim().length === 0) {
385
+ throw new Error(`Must specify '${key2}' for graph ${g.id}`);
386
+ }
387
+ return value;
388
+ }
389
+ const graphIdParts = requiredString("id").split(";");
390
+ const graphId = graphIdParts[0];
391
+ const graphIdBaseParts = graphId.split("-");
392
+ const graphBaseId = graphIdBaseParts[0];
393
+ const title = requiredString("graph title");
394
+ const menuTitle = optionalString(g["menu title"]);
395
+ const miniTitle = optionalString(g["mini title"]);
396
+ const parentMenu = optionalString(g["parent menu"]);
397
+ const description = optionalString(g["description"]);
398
+ const kindString = optionalString(g["kind"]);
399
+ if (!parentMenu) {
400
+ context.log("info", `Skipping graph ${graphId} (${title})`);
401
+ return void 0;
402
+ }
403
+ const key = (kind2) => `graph_${graphBaseId.padStart(3, "0")}_${kind2}`;
404
+ const strCtxt = (kind2) => {
405
+ const parent = htmlToUtf8(parentMenu).replace("&amp;", "&");
406
+ const displayTitle = htmlToUtf8(menuTitle || title).replace("&amp;", "&");
407
+ return `Graph ${kind2}: ${parent} > ${displayTitle}`;
408
+ };
409
+ const titleKey = strings.add(key("title"), title, layout2, strCtxt("Title"));
410
+ let menuTitleKey;
411
+ if (menuTitle) {
412
+ menuTitleKey = strings.add(key("menu_title"), menuTitle, layout2, strCtxt("Menu Item"));
413
+ }
414
+ let miniTitleKey;
415
+ if (miniTitle) {
416
+ miniTitleKey = strings.add(key("mini_title"), miniTitle, layout2, strCtxt("Title (for Mini View)"));
417
+ }
418
+ let descriptionKey;
419
+ if (description) {
420
+ descriptionKey = strings.add(key("description"), description, layout2, strCtxt("Description"), "graph-descriptions");
421
+ }
422
+ const kind = kindString;
423
+ const sideString = optionalString(g["side"]);
424
+ const side = sideString;
425
+ const unitsString = optionalString(g["units"]);
426
+ const altIdString = optionalString(g["alternate"]);
427
+ let unitSystem;
428
+ let alternates;
429
+ if (unitsString && altIdString) {
430
+ if (unitsString === "metric") {
431
+ unitSystem = "metric";
432
+ alternates = [
433
+ {
434
+ id: altIdString,
435
+ unitSystem: "us"
436
+ }
437
+ ];
438
+ } else if (unitsString === "us") {
439
+ unitSystem = "us";
440
+ alternates = [
441
+ {
442
+ id: altIdString,
443
+ unitSystem: "metric"
444
+ }
445
+ ];
446
+ }
447
+ }
448
+ const xMin = optionalNumber(g["x axis min"]) || context.graphDefaultMinTime;
449
+ const xMax = optionalNumber(g["x axis max"]) || context.graphDefaultMaxTime;
450
+ const xAxisLabel = optionalString(g["x axis label"]);
451
+ let xAxisLabelKey;
452
+ if (xAxisLabel) {
453
+ xAxisLabelKey = strings.add(genStringKey("graph_xaxis_label", xAxisLabel), xAxisLabel, layout2, "Graph X-Axis Label");
454
+ }
455
+ const yMin = optionalNumber(g["y axis min"]) || 0;
456
+ const yMax = optionalNumber(g["y axis max"]);
457
+ const ySoftMax = optionalNumber(g["y axis soft max"]);
458
+ const yFormat = optionalString(g["y axis format"]) || ".0f";
459
+ const yAxisLabel = optionalString(g["y axis label"]);
460
+ let yAxisLabelKey;
461
+ if (yAxisLabel) {
462
+ yAxisLabelKey = strings.add(genStringKey("graph_yaxis_label", yAxisLabel), yAxisLabel, layout2, "Graph Y-Axis Label");
463
+ }
464
+ const datasets = [];
465
+ function addDataset(index, overrides) {
466
+ const plotKey = (name) => `plot ${index} ${name}`;
467
+ const varName = g[plotKey("variable")];
468
+ if (!varName) {
469
+ return;
470
+ }
471
+ const varId = sdeNameForVensimVarName(varName);
472
+ const externalSourceName = (overrides == null ? void 0 : overrides.sourceName) || optionalString(g[plotKey("source")]);
473
+ const datasetLabel = optionalString(g[plotKey("label")]);
474
+ let labelKey;
475
+ if (datasetLabel) {
476
+ labelKey = strings.add(genStringKey("graph_dataset_label", datasetLabel), datasetLabel, layout2, "Graph Dataset Label");
477
+ }
478
+ const colorId = (overrides == null ? void 0 : overrides.colorId) || requiredString(plotKey("color"));
479
+ const hexColor = context.getHexColorForId(colorId);
480
+ if (!hexColor) {
481
+ throw new Error(`Graph ${graphId} references an unknown color ${colorId}`);
482
+ }
483
+ const lineStyleAndModString = optionalString(g[plotKey("style")]) || "line";
484
+ const lineStyleParts = lineStyleAndModString.split(";");
485
+ const lineStyleString = lineStyleParts[0];
486
+ const lineStyleModifierString = lineStyleParts.length > 1 ? lineStyleParts[1] : void 0;
487
+ const lineStyle = lineStyleString;
488
+ let lineStyleModifiers;
489
+ if (lineStyleModifierString) {
490
+ lineStyleModifiers = [lineStyleModifierString];
491
+ }
492
+ if (externalSourceName && externalSourceName !== "Ref") {
493
+ context.addStaticVariable(externalSourceName, varName);
494
+ } else {
495
+ context.addOutputVariable(varName);
496
+ }
497
+ const datasetSpec = {
498
+ varId,
499
+ varName,
500
+ externalSourceName,
501
+ labelKey,
502
+ color: hexColor,
503
+ lineStyle,
504
+ lineStyleModifiers
505
+ };
506
+ datasets.push(datasetSpec);
507
+ }
508
+ for (let i = 1; i <= 11; i++) {
509
+ addDataset(i);
510
+ }
511
+ const legendItems = datasets.filter((dataset) => {
512
+ var _a;
513
+ return ((_a = dataset.labelKey) == null ? void 0 : _a.length) > 0;
514
+ }).map((dataset) => {
515
+ return {
516
+ color: dataset.color,
517
+ labelKey: dataset.labelKey
518
+ };
519
+ });
520
+ const graphSpec = {
521
+ id: graphId,
522
+ kind,
523
+ titleKey,
524
+ miniTitleKey,
525
+ menuTitleKey,
526
+ descriptionKey,
527
+ side,
528
+ unitSystem,
529
+ alternates,
530
+ xMin,
531
+ xMax,
532
+ xAxisLabelKey,
533
+ yMin,
534
+ yMax,
535
+ ySoftMax,
536
+ yAxisLabelKey,
537
+ yFormat,
538
+ datasets,
539
+ legendItems
540
+ };
541
+ return graphSpec;
542
+ }
543
+
544
+ // src/gen-inputs.ts
545
+ var layout = "default";
546
+ function generateInputsConfig(context) {
547
+ const inputsCsv = context.readConfigCsvFile("inputs");
548
+ const inputSpecs = /* @__PURE__ */ new Map();
549
+ for (const row of inputsCsv) {
550
+ const spec = inputSpecFromCsv(row, context);
551
+ if (spec) {
552
+ inputSpecs.set(spec.id, spec);
553
+ }
554
+ }
555
+ return inputSpecs;
556
+ }
557
+ function inputSpecFromCsv(r, context) {
558
+ const strings = context.strings;
559
+ function requiredString(key2) {
560
+ const value = r[key2];
561
+ if (value === void 0 || typeof value !== "string" || value.trim().length === 0) {
562
+ throw new Error(`Must specify '${key2}' for input ${r.id}`);
563
+ }
564
+ return value;
565
+ }
566
+ function requiredNumber(key2) {
567
+ const stringValue = requiredString(key2);
568
+ const numValue = Number(stringValue);
569
+ if (numValue === void 0) {
570
+ throw new Error(`Must specify numeric '${key2}' for input ${r.id}`);
571
+ }
572
+ return numValue;
573
+ }
574
+ const inputIdParts = requiredString("id").split(";");
575
+ const inputId = inputIdParts[0];
576
+ const viewId = optionalString(r["viewid"]);
577
+ const label = optionalString(r["label"]) || "";
578
+ const inputType = requiredString("input type");
579
+ if (!viewId) {
580
+ context.log("info", `Skipping input ${inputId} (${label})`);
581
+ return void 0;
582
+ }
583
+ const description = optionalString(r["description"]);
584
+ const key = (kind) => `input_${inputId.padStart(3, "0")}_${kind}`;
585
+ const groupTitle = optionalString(r["group name"]);
586
+ if (!groupTitle) {
587
+ throw new Error(`Must specify 'group name' for input ${inputId}`);
588
+ }
589
+ const groupTitleKey = genStringKey("input_group_title", groupTitle);
590
+ strings.add(groupTitleKey, groupTitle, layout, "Input Group Title");
591
+ let typeLabel;
592
+ switch (inputType) {
593
+ case "slider":
594
+ typeLabel = "Slider";
595
+ break;
596
+ case "switch":
597
+ typeLabel = "Switch";
598
+ break;
599
+ case "checkbox":
600
+ typeLabel = "Checkbox";
601
+ break;
602
+ case "checkbox group":
603
+ typeLabel = "Checkbox Group";
604
+ break;
605
+ default:
606
+ throw new Error(`Unexpected input type ${inputType}`);
607
+ }
608
+ const strCtxt = (kind) => {
609
+ const labelText = htmlToUtf8(label).replace("&amp;", "&");
610
+ return `${typeLabel} ${kind}: ${groupTitle} > ${labelText}`;
611
+ };
612
+ const labelKey = strings.add(key("label"), label, layout, strCtxt("Label"));
613
+ const listingLabel = optionalString(r["listing label"]);
614
+ let listingLabelKey;
615
+ if (listingLabel) {
616
+ listingLabelKey = strings.add(key("action_label"), listingLabel, layout, strCtxt("Action Label"));
617
+ }
618
+ let descriptionKey;
619
+ if (description) {
620
+ descriptionKey = strings.add(key("description"), description, layout, strCtxt("Description"), "input-descriptions");
621
+ }
622
+ function sliderSpecFromCsv() {
623
+ const varName = requiredString("varname");
624
+ const varId = sdeNameForVensimVarName(varName);
625
+ const defaultValue = requiredNumber("slider/switch default");
626
+ const minValue = requiredNumber("slider min");
627
+ const maxValue = requiredNumber("slider max");
628
+ const step = requiredNumber("slider step");
629
+ const reversed = optionalString(r["reversed"]) === "yes";
630
+ if (defaultValue < minValue || defaultValue > maxValue) {
631
+ let e = `Default value for slider ${inputId} is out of range: `;
632
+ e += `default=${defaultValue} min=${minValue} max=${maxValue}`;
633
+ throw new Error(e);
634
+ }
635
+ context.addInputVariable(varName, defaultValue, minValue, maxValue);
636
+ const format = optionalString(r["format"]) || ".0f";
637
+ const units = optionalString(r["units"]);
638
+ let unitsKey;
639
+ if (units) {
640
+ unitsKey = strings.add(genStringKey("input_units", units), units, layout, "Slider Units");
641
+ }
642
+ const rangeInfo = getSliderRangeInfo(r, maxValue, context);
643
+ const rangeLabelKeys = rangeInfo.labelKeys;
644
+ const rangeDividers = rangeInfo.dividers;
645
+ return {
646
+ kind: "slider",
647
+ id: inputId,
648
+ varId,
649
+ varName,
650
+ defaultValue,
651
+ minValue,
652
+ maxValue,
653
+ step,
654
+ reversed,
655
+ labelKey,
656
+ listingLabelKey,
657
+ descriptionKey,
658
+ unitsKey,
659
+ rangeLabelKeys,
660
+ rangeDividers,
661
+ format
662
+ };
663
+ }
664
+ function switchSpecFromCsv() {
665
+ const varName = requiredString("varname");
666
+ const varId = sdeNameForVensimVarName(varName);
667
+ const onValue = requiredNumber("enabled value");
668
+ const offValue = requiredNumber("disabled value");
669
+ const defaultValue = requiredNumber("slider/switch default");
670
+ if (defaultValue !== onValue && defaultValue !== offValue) {
671
+ throw new Error(`Invalid default value for switch ${inputId}: off=${offValue} on=${onValue} default=${defaultValue}`);
672
+ }
673
+ const minValue = Math.min(offValue, onValue);
674
+ const maxValue = Math.max(offValue, onValue);
675
+ context.addInputVariable(varName, defaultValue, minValue, maxValue);
676
+ const controlledInputIds = requiredString("controlled input ids");
677
+ const controlledParts = controlledInputIds.split("|");
678
+ const rowsActiveWhenOff = controlledParts[0].split(";").filter((id) => id.trim().length > 0);
679
+ const rowsActiveWhenOn = controlledParts[1].split(";").filter((id) => id.trim().length > 0);
680
+ return {
681
+ kind: "switch",
682
+ id: inputId,
683
+ varId,
684
+ varName,
685
+ labelKey,
686
+ listingLabelKey,
687
+ descriptionKey,
688
+ defaultValue,
689
+ offValue,
690
+ onValue,
691
+ slidersActiveWhenOff: rowsActiveWhenOff,
692
+ slidersActiveWhenOn: rowsActiveWhenOn
693
+ };
694
+ }
695
+ let inputSpec;
696
+ switch (inputType) {
697
+ case "slider": {
698
+ inputSpec = sliderSpecFromCsv();
699
+ break;
700
+ }
701
+ case "switch":
702
+ case "checkbox": {
703
+ inputSpec = switchSpecFromCsv();
704
+ break;
705
+ }
706
+ case "checkbox group":
707
+ break;
708
+ default:
709
+ throw new Error(`Unexpected input type ${inputType}`);
710
+ }
711
+ return inputSpec;
712
+ }
713
+ function getSliderRangeInfo(r, maxValue, context) {
714
+ const strings = context.strings;
715
+ const labelKeys = [];
716
+ const dividers = [];
717
+ let rangeNum = 1;
718
+ while (rangeNum <= 5) {
719
+ const label = optionalString(r[`range ${rangeNum} label`]);
720
+ if (!label) {
721
+ break;
722
+ }
723
+ const labelKey = strings.add(genStringKey("input_range", label), label, layout, "Slider Range Label");
724
+ if (!labelKey) {
725
+ break;
726
+ }
727
+ labelKeys.push(labelKey);
728
+ rangeNum++;
729
+ }
730
+ const numRanges = rangeNum - 1;
731
+ for (rangeNum = 2; rangeNum <= numRanges; rangeNum++) {
732
+ let divider = optionalNumber(r[`range ${rangeNum} start`]);
733
+ if (divider === void 0) {
734
+ divider = maxValue;
735
+ }
736
+ dividers.push(divider);
737
+ }
738
+ return {
739
+ labelKeys,
740
+ dividers
741
+ };
742
+ }
743
+
744
+ // src/gen-config-specs.ts
745
+ var import_meta = {};
746
+ var __dirname = (0, import_path2.dirname)((0, import_url.fileURLToPath)(import_meta.url));
747
+ function generateConfigSpecs(context) {
748
+ context.log("verbose", " Reading graph specs");
749
+ const graphSpecs = generateGraphSpecs(context);
750
+ context.log("verbose", " Reading input specs");
751
+ const inputSpecs = generateInputsConfig(context);
752
+ context.log("verbose", " Reading extra output variables");
753
+ const extraOutputsCsv = context.readConfigCsvFile("outputs");
754
+ for (const row of extraOutputsCsv) {
755
+ const varName = row["variable name"];
756
+ if (varName) {
757
+ context.addOutputVariable(varName);
758
+ }
759
+ }
760
+ return {
761
+ graphSpecs,
762
+ inputSpecs
763
+ };
764
+ }
765
+ function writeConfigSpecs(context, config, dstDir) {
766
+ let tsContent = "";
767
+ function emit(s) {
768
+ tsContent += s + "\n";
769
+ }
770
+ emit("// This file is generated by `@sdeverywhere/plugin-config`; do not edit manually!");
771
+ emit("");
772
+ emit(`import type { GraphSpec, InputSpec } from './spec-types'`);
773
+ function emitArray(type, values) {
774
+ const varName = type.charAt(0).toLowerCase() + type.slice(1) + "s";
775
+ const array = Array.from(values);
776
+ const json = JSON.stringify(array, null, 2);
777
+ emit("");
778
+ emit(`export const ${varName}: ${type}[] = ${json}`);
779
+ }
780
+ emitArray("GraphSpec", config.graphSpecs.values());
781
+ emitArray("InputSpec", config.inputSpecs.values());
782
+ context.writeStagedFile("config", dstDir, "config-specs.ts", tsContent);
783
+ }
784
+ function writeSpecTypes(context, dstDir) {
785
+ const tsFile = "spec-types.ts";
786
+ const tsPath = (0, import_path2.resolve)(__dirname, tsFile);
787
+ const tsContent = (0, import_fs2.readFileSync)(tsPath, "utf8");
788
+ context.writeStagedFile("config", dstDir, tsFile, tsContent);
789
+ }
790
+
791
+ // src/processor.ts
792
+ function configProcessor(options) {
793
+ return (buildContext) => {
794
+ return processModelConfig(buildContext, options);
795
+ };
796
+ }
797
+ async function processModelConfig(buildContext, options) {
798
+ const t0 = performance.now();
799
+ if (!(0, import_fs3.existsSync)(options.config)) {
800
+ throw new Error(`The provided config dir '${options.config}' does not exist`);
801
+ }
802
+ let outModelSpecsDir;
803
+ if (options.out) {
804
+ if (typeof options.out === "string") {
805
+ outModelSpecsDir = (0, import_path3.join)(options.out, "src", "model", "generated");
806
+ } else {
807
+ outModelSpecsDir = options.out.modelSpecsDir;
808
+ }
809
+ }
810
+ let outConfigSpecsDir;
811
+ if (options.out) {
812
+ if (typeof options.out === "string") {
813
+ outConfigSpecsDir = (0, import_path3.join)(options.out, "src", "config", "generated");
814
+ } else {
815
+ outConfigSpecsDir = options.out.configSpecsDir;
816
+ }
817
+ }
818
+ let outStringsDir;
819
+ if (options.out) {
820
+ if (typeof options.out === "string") {
821
+ outStringsDir = (0, import_path3.join)(options.out, "strings");
822
+ } else {
823
+ outStringsDir = options.out.stringsDir;
824
+ }
825
+ }
826
+ const context = createConfigContext(buildContext, options.config);
827
+ context.log("info", "Generating files...");
828
+ const configSpecs = generateConfigSpecs(context);
829
+ if (outConfigSpecsDir) {
830
+ context.log("verbose", " Writing config specs");
831
+ writeConfigSpecs(context, configSpecs, outConfigSpecsDir);
832
+ writeSpecTypes(context, outConfigSpecsDir);
833
+ }
834
+ if (outModelSpecsDir) {
835
+ context.log("verbose", " Writing model specs");
836
+ writeModelSpec(context, outModelSpecsDir);
837
+ }
838
+ if (outStringsDir) {
839
+ context.log("verbose", " Writing strings");
840
+ context.writeStringsFiles(outStringsDir);
841
+ }
842
+ const t1 = performance.now();
843
+ const elapsed = ((t1 - t0) / 1e3).toFixed(1);
844
+ context.log("info", `Done generating files (${elapsed}s)`);
845
+ return {
846
+ startTime: context.modelStartTime,
847
+ endTime: context.modelEndTime,
848
+ inputs: context.getOrderedInputs(),
849
+ outputs: context.getOrderedOutputs(),
850
+ datFiles: context.datFiles
851
+ };
852
+ }
853
+ // Annotate the CommonJS export names for ESM import in node:
854
+ 0 && (module.exports = {
855
+ configProcessor
856
+ });
857
+ //# sourceMappingURL=index.cjs.map