@sdeverywhere/cli 0.7.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.
@@ -0,0 +1,54 @@
1
+ // Copyright (c) 2022 Climate Interactive / New Venture Fund
2
+
3
+ import path from 'path'
4
+
5
+ import { build as runBuild } from '@sdeverywhere/build'
6
+
7
+ export let command = 'bundle [options]'
8
+ export let describe = 'build and bundle a model as specified by a config file'
9
+ export let builder = {
10
+ config: {
11
+ describe: 'path to the config file (defaults to `sde.config.js` in the current directory)',
12
+ type: 'string',
13
+ alias: 'c'
14
+ },
15
+ verbose: {
16
+ describe: 'enable verbose log messages',
17
+ type: 'boolean'
18
+ }
19
+ }
20
+ export let handler = argv => {
21
+ bundle(argv.config, argv.verbose)
22
+ }
23
+ export let bundle = async (configPath, verbose) => {
24
+ const logLevels = ['error', 'info']
25
+ if (verbose) {
26
+ logLevels.push('verbose')
27
+ }
28
+ const srcDir = new URL('.', import.meta.url).pathname
29
+ const sdeDir = path.resolve(srcDir, '..')
30
+ const sdeCmdPath = path.resolve(srcDir, 'main.js')
31
+ const result = await runBuild('production', {
32
+ config: configPath,
33
+ logLevels,
34
+ sdeDir,
35
+ sdeCmdPath
36
+ })
37
+ if (result.isOk()) {
38
+ // Exit with the specified code
39
+ console.log()
40
+ process.exit(result.exitCode)
41
+ } else {
42
+ // Exit with a non-zero code if any step failed
43
+ console.error(`ERROR: ${result.error.message}\n`)
44
+ process.exit(1)
45
+ }
46
+ }
47
+
48
+ export default {
49
+ command,
50
+ describe,
51
+ builder,
52
+ handler,
53
+ bundle
54
+ }
@@ -0,0 +1,35 @@
1
+ import { generateCode, parseModel, preprocessModel } from '@sdeverywhere/compile'
2
+
3
+ import { modelPathProps, parseSpec } from './utils.js'
4
+
5
+ let command = 'causes [options] <model> <c_varname>'
6
+ let describe = 'print dependencies for a C variable name'
7
+ let builder = {
8
+ spec: {
9
+ describe: 'pathname of the I/O specification JSON file',
10
+ type: 'string',
11
+ alias: 's'
12
+ }
13
+ }
14
+ let handler = argv => {
15
+ causes(argv.model, argv.c_varname, argv)
16
+ }
17
+ let causes = (model, varname, opts) => {
18
+ // Get the model name and directory from the model argument.
19
+ let { modelDirname, modelPathname } = modelPathProps(model)
20
+ let extData = new Map()
21
+ let directData = new Map()
22
+ let spec = parseSpec(opts.spec)
23
+ // Preprocess model text into parser input.
24
+ let input = preprocessModel(modelPathname, spec)
25
+ // Parse the model to get variable and subscript information.
26
+ let parseTree = parseModel(input)
27
+ let operation = 'printRefGraph'
28
+ generateCode(parseTree, { spec, operation, extData, directData, modelDirname, varname })
29
+ }
30
+ export default {
31
+ command,
32
+ describe,
33
+ builder,
34
+ handler
35
+ }
@@ -0,0 +1,33 @@
1
+ import path from 'path'
2
+ import sh from 'shelljs'
3
+
4
+ let command = 'clean [options]'
5
+ let describe = 'clean out the build and output directories for a model'
6
+ let builder = {
7
+ modeldir: {
8
+ describe: 'model directory',
9
+ type: 'string',
10
+ alias: 'm'
11
+ }
12
+ }
13
+ let handler = argv => {
14
+ clean(argv)
15
+ }
16
+ let clean = opts => {
17
+ // Remove the directories generated by SDEverywhere.
18
+ // Default to the current directory if no model directory is given.
19
+ let buildDirname = path.join(opts.modeldir || '.', 'build')
20
+ let outputDirname = path.join(opts.modeldir || '.', 'output')
21
+ let silentState = sh.config.silent
22
+ sh.config.silent = true
23
+ sh.rm('-r', buildDirname)
24
+ sh.rm('-r', outputDirname)
25
+ sh.config.silent = silentState
26
+ }
27
+ export default {
28
+ command,
29
+ describe,
30
+ builder,
31
+ handler,
32
+ clean
33
+ }
@@ -0,0 +1,106 @@
1
+ import { existsSync } from 'fs'
2
+
3
+ import { pr } from 'bufx'
4
+ import R from 'ramda'
5
+
6
+ import { readDat } from '@sdeverywhere/compile'
7
+
8
+ // The epsilon value determines the required precision for value comparisons.
9
+ let ε = 1e-5
10
+
11
+ export let command = 'compare [options] <vensimlog> <sdelog>'
12
+ export let describe = 'compare Vensim and SDEverywhere log files in DAT format'
13
+ export let builder = {
14
+ precision: {
15
+ describe: 'precision to which values must agree (default 1e-5)',
16
+ type: 'number',
17
+ alias: 'p'
18
+ },
19
+ name: {
20
+ describe: 'single Vensim variable name to compare',
21
+ type: 'string',
22
+ alias: 'n'
23
+ },
24
+ times: {
25
+ describe: 'limit comparisons to times separated by spaces',
26
+ type: 'array',
27
+ alias: 't'
28
+ }
29
+ }
30
+ export let handler = argv => {
31
+ compare(argv.vensimlog, argv.sdelog, argv)
32
+ }
33
+ export let compare = async (vensimfile, sdefile, opts) => {
34
+ if (!existsSync(vensimfile)) {
35
+ throw new Error(`Vensim DAT file not found: ${vensimfile}`)
36
+ }
37
+ if (!existsSync(sdefile)) {
38
+ throw new Error(`SDEverywhere DAT file not found: ${sdefile}`)
39
+ }
40
+
41
+ let vensimLog = await readDat(vensimfile)
42
+ let sdeLog = await readDat(sdefile)
43
+
44
+ if (vensimLog.size === 0) {
45
+ throw new Error(`Vensim DAT file did not contain data: ${vensimfile}`)
46
+ }
47
+ if (sdeLog.size === 0) {
48
+ throw new Error(`SDEverywhere DAT file did not contain data: ${sdefile}`)
49
+ }
50
+
51
+ if (opts.precision) {
52
+ ε = opts.precision
53
+ }
54
+
55
+ let noDATDifference = true
56
+ for (let varName of vensimLog.keys()) {
57
+ let sdeValues = sdeLog.get(varName)
58
+ // Ignore variables that are not found in the SDE log file.
59
+ if (sdeValues && (!opts.name || varName === opts.name)) {
60
+ let vensimValue = undefined
61
+ let vensimValues = vensimLog.get(varName)
62
+ // Filter on time t, the key in the values list.
63
+ for (let t of vensimValues.keys()) {
64
+ if (!opts.times || R.find(time => isEqual(time, t), opts.times)) {
65
+ // In Vensim log files, const vars only have one value at the initial time.
66
+ if (vensimValues.size > 1 || !vensimValue) {
67
+ vensimValue = vensimValues.get(t)
68
+ }
69
+ let sdeValue = sdeValues.get(t)
70
+ let diff = difference(sdeValue, vensimValue)
71
+ if (diff > ε) {
72
+ let diffPct = (diff * 100).toFixed(6)
73
+ pr(`${varName} time=${t.toFixed(2)} vensim=${vensimValue} sde=${sdeValue} diff=${diffPct}%`)
74
+ noDATDifference = false
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+ if (noDATDifference) {
81
+ pr(`Data were the same for ${vensimfile} and ${sdefile}`)
82
+ }
83
+ }
84
+ let isZero = value => {
85
+ return Math.abs(value) < ε
86
+ }
87
+ let difference = (x, y) => {
88
+ let diff = 0
89
+ if (isZero(x) || isZero(y)) {
90
+ diff = Math.abs(x - y)
91
+ } else {
92
+ diff = Math.abs(1 - x / y)
93
+ }
94
+ return diff
95
+ }
96
+ let isEqual = (x, y) => {
97
+ return difference(x, y) < ε
98
+ }
99
+
100
+ export default {
101
+ command,
102
+ describe,
103
+ builder,
104
+ handler,
105
+ compare
106
+ }
@@ -0,0 +1,57 @@
1
+ import fs from 'fs-extra'
2
+ import path from 'path'
3
+ import sh from 'shelljs'
4
+
5
+ import { buildDir, execCmd, modelPathProps } from './utils.js'
6
+
7
+ export let command = 'compile [options] <model>'
8
+ export let describe = 'compile the generated model to an executable file'
9
+ export let builder = {
10
+ builddir: {
11
+ describe: 'build directory',
12
+ type: 'string',
13
+ alias: 'b'
14
+ }
15
+ }
16
+ export let handler = argv => {
17
+ compile(argv.model, argv)
18
+ }
19
+ export let compile = (model, opts) => {
20
+ let { modelDirname, modelName } = modelPathProps(model)
21
+ // Ensure the build directory exists.
22
+ let buildDirname = buildDir(opts.builddir, modelDirname)
23
+ // Link SDEverywhere C source files into the build directory.
24
+ linkCSourceFiles(modelDirname, buildDirname)
25
+ // Run make to compile the model C code.
26
+ let silentState = sh.config.silent
27
+ sh.config.silent = true
28
+ sh.pushd(buildDirname)
29
+ let exitCode = execCmd(`make P=${modelName}`)
30
+ sh.popd()
31
+ sh.config.silent = silentState
32
+ if (exitCode > 0) {
33
+ process.exit(exitCode)
34
+ }
35
+ return 0
36
+ }
37
+ export default {
38
+ command,
39
+ describe,
40
+ builder,
41
+ handler,
42
+ compile
43
+ }
44
+
45
+ let linkCSourceFiles = (modelDirname, buildDirname) => {
46
+ let cDirname = path.join(new URL('.', import.meta.url).pathname, 'c')
47
+ sh.ls(cDirname).forEach(filename => {
48
+ // If a C source file is present in the model directory, link to it instead
49
+ // as an override.
50
+ let srcPathname = path.join(modelDirname, filename)
51
+ if (!fs.existsSync(srcPathname)) {
52
+ srcPathname = path.join(cDirname, filename)
53
+ }
54
+ let dstPathname = path.join(buildDirname, filename)
55
+ fs.ensureSymlinkSync(srcPathname, dstPathname)
56
+ })
57
+ }
package/src/sde-dev.js ADDED
@@ -0,0 +1,52 @@
1
+ // Copyright (c) 2022 Climate Interactive / New Venture Fund
2
+
3
+ import path from 'path'
4
+
5
+ import { build as runBuild } from '@sdeverywhere/build'
6
+
7
+ export let command = 'dev [options]'
8
+ export let describe = 'run a model in a live development environment'
9
+ export let builder = {
10
+ config: {
11
+ describe: 'path to the config file (defaults to `sde.config.js` in the current directory)',
12
+ type: 'string',
13
+ alias: 'c'
14
+ },
15
+ verbose: {
16
+ describe: 'enable verbose log messages',
17
+ type: 'boolean'
18
+ }
19
+ }
20
+ export let handler = argv => {
21
+ dev(argv.config, argv.verbose)
22
+ }
23
+ export let dev = async (configPath, verbose) => {
24
+ const logLevels = ['error', 'info']
25
+ if (verbose) {
26
+ logLevels.push('verbose')
27
+ }
28
+ const srcDir = new URL('.', import.meta.url).pathname
29
+ const sdeDir = path.resolve(srcDir, '..')
30
+ const sdeCmdPath = path.resolve(srcDir, 'main.js')
31
+ const result = await runBuild('development', {
32
+ config: configPath,
33
+ logLevels,
34
+ sdeDir,
35
+ sdeCmdPath
36
+ })
37
+
38
+ // Exit with a non-zero code if any step failed, otherwise keep the
39
+ // builder process alive
40
+ if (!result.isOk()) {
41
+ console.error(`ERROR: ${result.error.message}\n`)
42
+ process.exit(1)
43
+ }
44
+ }
45
+
46
+ export default {
47
+ command,
48
+ describe,
49
+ builder,
50
+ handler,
51
+ dev
52
+ }
@@ -0,0 +1,48 @@
1
+ import path from 'path'
2
+
3
+ import { buildDir, execCmd, modelPathProps, outputDir } from './utils.js'
4
+
5
+ export let command = 'exec [options] <model>'
6
+ export let describe = 'execute the model and capture its output to a file'
7
+ export let builder = {
8
+ builddir: {
9
+ describe: 'build directory',
10
+ type: 'string',
11
+ alias: 'b'
12
+ },
13
+ outfile: {
14
+ describe: 'output pathname',
15
+ type: 'string',
16
+ alias: 'o'
17
+ }
18
+ }
19
+ export let handler = argv => {
20
+ exec(argv.model, argv)
21
+ }
22
+ export let exec = (model, opts) => {
23
+ let { modelDirname, modelName } = modelPathProps(model)
24
+ // Ensure the build and output directories exist.
25
+ let buildDirname = buildDir(opts.builddir, modelDirname)
26
+ let outputDirname = outputDir(opts.outfile, modelDirname)
27
+ // Run the model and capture output in the model directory.
28
+ let modelCmd = `${buildDirname}/${modelName}`
29
+ let outputPathname
30
+ if (opts.outfile) {
31
+ outputPathname = opts.outfile
32
+ } else {
33
+ outputPathname = path.join(outputDirname, `${modelName}.txt`)
34
+ }
35
+ let exitCode = execCmd(`${modelCmd} >${outputPathname}`)
36
+ if (exitCode > 0) {
37
+ process.exit(exitCode)
38
+ }
39
+ return 0
40
+ }
41
+
42
+ export default {
43
+ command,
44
+ describe,
45
+ builder,
46
+ handler,
47
+ exec
48
+ }
@@ -0,0 +1,195 @@
1
+ import path from 'path'
2
+
3
+ import B from 'bufx'
4
+
5
+ import { preprocessModel } from '@sdeverywhere/compile'
6
+
7
+ import { buildDir, modelPathProps } from './utils.js'
8
+
9
+ const command = 'flatten [options] <outmodel>'
10
+
11
+ // TODO: Set to false for now to keep it hidden from the help menu, since
12
+ // this is an experimental feature
13
+ //const describe = 'flatten submodels into a single preprocessed mdl file'
14
+ const describe = false
15
+
16
+ const builder = {
17
+ builddir: {
18
+ describe: 'build directory',
19
+ type: 'string',
20
+ alias: 'b'
21
+ },
22
+ inputs: {
23
+ describe: 'input mdl files',
24
+ demand: true,
25
+ type: 'array'
26
+ }
27
+ }
28
+
29
+ const handler = argv => {
30
+ flatten(argv.outmodel, argv.inputs, argv)
31
+ }
32
+
33
+ const flatten = async (outFile, inFiles, opts) => {
34
+ // Ensure the build directory exists
35
+ // TODO: Since the input mdl files can technically exist in more than
36
+ // one directory, we don't want to guess, so just use the current directory
37
+ // if one isn't provided on the command line
38
+ const buildDirname = buildDir(opts.builddir, '.')
39
+
40
+ // Extract the equations and declarations from each parent or submodel file
41
+ // and store them into a map keyed by source file
42
+ const fileDecls = {}
43
+ for (const inFile of inFiles) {
44
+ // Get the path and short name of the input mdl file
45
+ const inModelProps = modelPathProps(inFile)
46
+
47
+ // Preprocess the mdl file and extract the equations
48
+ const decls = []
49
+ preprocessModel(inModelProps.modelPathname, undefined, 'genc', false, decls)
50
+
51
+ // Associate each declaration with the name of the model from which it came
52
+ for (const decl of decls) {
53
+ decl.sourceModelName = inModelProps.modelName
54
+ }
55
+
56
+ fileDecls[inModelProps.modelName] = decls
57
+ }
58
+
59
+ // Collapse so that we have only one equation or declaration per key.
60
+ // Equations that are defined in a different submodel will appear like
61
+ // a data variable in the model file where it is used (i.e., no equals
62
+ // sign). If we see an equation, that takes priority over a "data"
63
+ // declaration.
64
+ const collapsed = {}
65
+ let hasErrors = false
66
+ for (const modelName of Object.keys(fileDecls)) {
67
+ const decls = fileDecls[modelName]
68
+ for (const decl of decls) {
69
+ const existingDecl = collapsed[decl.key]
70
+ if (existingDecl) {
71
+ // We've already seen a declaration with this key (from another file)
72
+ if (decl.kind === existingDecl.kind) {
73
+ if (decl.kind === 'sub') {
74
+ // The subscript keys are the same, so see if the contents are the same
75
+ if (decl.processedDecl !== existingDecl.processedDecl) {
76
+ // TODO: If we see two subscript declarations that differ, it's
77
+ // probably because a submodel uses a simplified set of dimension
78
+ // mappings, while the parent includes a superset of mappings.
79
+ // We should add more checks here, like see if the mappings in one
80
+ // are a subset of the other, but for now, we will emit a warning
81
+ // and just take the longer of the two.
82
+ let longerDecl
83
+ if (decl.processedDecl.length > existingDecl.processedDecl.length) {
84
+ longerDecl = decl
85
+ } else {
86
+ longerDecl = existingDecl
87
+ }
88
+ collapsed[decl.key] = longerDecl
89
+ let warnMsg = 'Subscript declarations are different; '
90
+ warnMsg += `will use the one from '${longerDecl.sourceModelName}.mdl' because it is longer`
91
+ console.warn('----------')
92
+ console.warn(`\nWARNING: ${warnMsg}:`)
93
+ console.warn(`\nIn ${existingDecl.sourceModelName}.mdl:`)
94
+ console.warn(`${existingDecl.processedDecl}`)
95
+ console.warn(`\nIn ${decl.sourceModelName}.mdl:`)
96
+ console.warn(`${decl.processedDecl}\n`)
97
+ }
98
+ } else if (decl.kind === 'eqn') {
99
+ // The equation keys are the same, so see if the contents are the same
100
+ if (decl.processedDecl !== existingDecl.processedDecl) {
101
+ hasErrors = true
102
+ console.error('----------')
103
+ console.error(`\nERROR: Differing equations:`)
104
+ console.error(`\nIn ${existingDecl.sourceModelName}.mdl:`)
105
+ console.error(`${existingDecl.processedDecl}`)
106
+ console.error(`\nIn ${decl.sourceModelName}.mdl:`)
107
+ console.error(`${decl.processedDecl}\n`)
108
+ }
109
+ }
110
+ } else if (decl.kind === 'eqn' && existingDecl.kind === 'decl') {
111
+ // Replace the declaration with this equation
112
+ collapsed[decl.key] = decl
113
+ }
114
+ } else {
115
+ // We haven't seen this key yet, so save the declaration
116
+ collapsed[decl.key] = decl
117
+ }
118
+ }
119
+ }
120
+
121
+ // XXX: For any subscripted (non-equation) declarations that remain, see if
122
+ // there are any corresponding equations. This can happen in the case of
123
+ // subscripted variables that are defined in multiple parts in one submodel,
124
+ // but are declared using the full set in the consuming model.
125
+ // For example, in the submodel:
126
+ // Variable[SubscriptA_Subset1] = ... ~~|
127
+ // Variable[SubscriptA_Subset2] = ... ~~|
128
+ // In the consuming model:
129
+ // Variable[SubscriptA_All] ~~|
130
+ // In this case, if we see Variable[SubscriptA_All], we can remove it and
131
+ // use the other `Variable` definitions.
132
+ for (const key of Object.keys(collapsed)) {
133
+ const decl = collapsed[key]
134
+ if (decl.kind === 'decl') {
135
+ const declKeyWithoutSubscript = key.split('[')[0]
136
+
137
+ // See if there is another equation that has the same key (minus subscript)
138
+ const otherDeclsThatMatch = []
139
+ for (const otherDecl of Object.values(collapsed)) {
140
+ if (otherDecl.kind === 'eqn') {
141
+ const otherDeclKeyWithoutSubscript = otherDecl.key.split('[')[0]
142
+ if (declKeyWithoutSubscript === otherDeclKeyWithoutSubscript) {
143
+ otherDeclsThatMatch.push(otherDecl)
144
+ }
145
+ }
146
+ }
147
+
148
+ if (otherDeclsThatMatch.length > 0) {
149
+ // Remove this declaration and emit a warning
150
+ delete collapsed[key]
151
+ console.warn('----------')
152
+ console.warn(`\nWARNING: Skipping declaration:`)
153
+ console.warn(`\nIn ${decl.sourceModelName}.mdl:`)
154
+ console.warn(`${decl.processedDecl}`)
155
+ console.warn(`\nThe following equations are assumed to provide the necessary data:`)
156
+ for (const otherDecl of otherDeclsThatMatch) {
157
+ console.warn(`\nIn ${otherDecl.sourceModelName}.mdl:`)
158
+ console.warn(`${otherDecl.processedDecl}`)
159
+ }
160
+ console.warn()
161
+ }
162
+ }
163
+ }
164
+
165
+ // Exit with a non-zero error code if there are any conflicting declarations
166
+ if (hasErrors) {
167
+ process.exit(1)
168
+ }
169
+
170
+ // Sort the declarations alphabetically by LHS variable name
171
+ const sorted = Object.values(collapsed).sort((a, b) => {
172
+ return a.key < b.key ? -1 : a.key > b.key ? 1 : 0
173
+ })
174
+
175
+ // Build a single buffer containing the sorted declarations
176
+ B.open('pp')
177
+ const ENCODING = '{UTF-8}'
178
+ B.emitLine(ENCODING, 'pp')
179
+ B.emitLine('', 'pp')
180
+ for (const decl of sorted) {
181
+ B.emitLine(`${decl.processedDecl}\n`, 'pp')
182
+ }
183
+
184
+ // Write the flattened mdl file to the build directory
185
+ const outModelProps = modelPathProps(outFile)
186
+ let outputPathname = path.join(buildDirname, `${outModelProps.modelName}.mdl`)
187
+ B.writeBuf(outputPathname, 'pp')
188
+ }
189
+
190
+ export default {
191
+ command,
192
+ describe,
193
+ builder,
194
+ handler
195
+ }
@@ -0,0 +1,86 @@
1
+ import path from 'path'
2
+ import B from 'bufx'
3
+
4
+ import { parseAndGenerate, preprocessModel } from '@sdeverywhere/compile'
5
+
6
+ import { buildDir, modelPathProps, parseSpec } from './utils.js'
7
+
8
+ export let command = 'generate [options] <model>'
9
+ export let describe = 'generate model code'
10
+ export let builder = {
11
+ genc: {
12
+ describe: 'generate C code for the model',
13
+ type: 'boolean'
14
+ },
15
+ list: {
16
+ describe: 'list model variables',
17
+ type: 'boolean',
18
+ alias: 'l'
19
+ },
20
+ preprocess: {
21
+ describe: 'write a preprocessed model that runs in Vensim',
22
+ type: 'boolean',
23
+ alias: 'p'
24
+ },
25
+ analysis: {
26
+ describe: 'write a nonexecutable preprocessed model for analysis',
27
+ type: 'boolean',
28
+ alias: 'a'
29
+ },
30
+ spec: {
31
+ describe: 'pathname of the I/O specification JSON file',
32
+ type: 'string',
33
+ alias: 's'
34
+ },
35
+ builddir: {
36
+ describe: 'build directory',
37
+ type: 'string',
38
+ alias: 'b'
39
+ },
40
+ refidtest: {
41
+ describe: 'test reference ids',
42
+ type: 'boolean',
43
+ alias: 'r'
44
+ }
45
+ }
46
+ export let handler = argv => {
47
+ generate(argv.model, argv)
48
+ }
49
+
50
+ export let generate = async (model, opts) => {
51
+ // Get the model name and directory from the model argument.
52
+ let { modelDirname, modelName, modelPathname } = modelPathProps(model)
53
+ // Ensure the build directory exists.
54
+ let buildDirname = buildDir(opts.builddir, modelDirname)
55
+ // Preprocess model text into parser input. Stop now if that's all we're doing.
56
+ let spec = parseSpec(opts.spec)
57
+ // Produce a runnable model with the "genc" and "preprocess" options.
58
+ let profile = opts.analysis ? 'analysis' : 'genc'
59
+ // Write the preprocessed model and removals if the option is "analysis" or "preprocess".
60
+ let writeFiles = opts.analysis || opts.preprocess
61
+ let input = preprocessModel(modelPathname, spec, profile, writeFiles)
62
+ if (writeFiles) {
63
+ let outputPathname = path.join(buildDirname, `${modelName}.mdl`)
64
+ B.write(input, outputPathname)
65
+ process.exit(0)
66
+ }
67
+ // Parse the model and generate code. If no operation is specified, the code generator will
68
+ // read the model and do nothing else. This is required for the list operation.
69
+ let operation = ''
70
+ if (opts.genc) {
71
+ operation = 'generateC'
72
+ } else if (opts.list) {
73
+ operation = 'printVarList'
74
+ } else if (opts.refidtest) {
75
+ operation = 'printRefIdTest'
76
+ }
77
+ await parseAndGenerate(input, spec, operation, modelDirname, modelName, buildDirname)
78
+ }
79
+
80
+ export default {
81
+ command,
82
+ describe,
83
+ builder,
84
+ handler,
85
+ generate
86
+ }