Skip to content

Commit d1e4426

Browse files
committed
feat(bindgen): support string choices
Future todo: handle these as string enums.
1 parent ab9949c commit d1e4426

File tree

5 files changed

+69
-34
lines changed

5 files changed

+69
-34
lines changed
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
function canonicalType(parameterType) {
22
// Strip extras
3-
const canonical = parameterType.split(' ')[0]
3+
let canonical = parameterType.split(' ')[0]
4+
canonical = canonical.split(':')[0]
45
return canonical
56
}
67

7-
export default canonicalType
8+
export default canonicalType

packages/core/typescript/itk-wasm/src/bindgen/python/function-module-args.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import interfaceJsonTypeToInterfaceType from '../interface-json-type-to-interfac
55
import canonicalType from '../canonical-type.js'
66

77
function functionModuleArgs(interfaceJson) {
8-
let functionArgs = ""
8+
let functionArgs = ''
99
interfaceJson['inputs'].forEach((value) => {
1010
const canonical = canonicalType(value.type)
1111
const pythonType = interfaceJsonTypeToPythonType.get(canonical)
1212
functionArgs += ` ${snakeCase(value.name)}: ${pythonType},\n`
1313
})
14-
const outputFiles = interfaceJson.outputs.filter(o => { return o.type.includes('FILE') })
14+
const outputFiles = interfaceJson.outputs.filter((o) => {
15+
return o.type.includes('FILE')
16+
})
1517
outputFiles.forEach((output) => {
1618
const isArray = output.itemsExpectedMax > 1
1719
const optionName = `${output.name}`
@@ -22,37 +24,36 @@ function functionModuleArgs(interfaceJson) {
2224
}
2325
})
2426
interfaceJson['parameters'].forEach((value) => {
25-
if (value.name === "memory-io" || value.name === "version") {
27+
if (value.name === 'memory-io' || value.name === 'version') {
2628
return
2729
}
2830
const canonical = canonicalType(value.type)
2931
const pythonType = interfaceJsonTypeToPythonType.get(canonical)
3032
if (interfaceJsonTypeToInterfaceType.has(value.type)) {
31-
if(value.required && value.itemsExpectedMax > 1) {
33+
if (value.required && value.itemsExpectedMax > 1) {
3234
functionArgs += ` ${snakeCase(value.name)}: List[${pythonType}] = [],\n`
3335
} else {
3436
functionArgs += ` ${snakeCase(value.name)}: Optional[${pythonType}] = None,\n`
3537
}
3638
} else {
37-
if(value.itemsExpectedMax > 1) {
39+
if (value.itemsExpectedMax > 1) {
3840
if (value.required) {
3941
functionArgs += ` ${snakeCase(value.name)}: List[${pythonType}]`
4042
} else {
4143
functionArgs += ` ${snakeCase(value.name)}: Optional[List[${pythonType}]]`
4244
}
4345
} else {
4446
functionArgs += ` ${snakeCase(value.name)}: ${pythonType}`
45-
4647
}
47-
if(value.type === "BOOL") {
48+
if (value.type === 'BOOL') {
4849
functionArgs += ` = False,\n`
49-
} else if(value.type === "TEXT") {
50+
} else if (value.type.startsWith('TEXT')) {
5051
if (value.default) {
5152
functionArgs += ` = "${value.default}",\n`
5253
} else {
5354
functionArgs += ` = "",\n`
5455
}
55-
} else if(value.required && value.itemsExpectedMax > 1) {
56+
} else if (value.required && value.itemsExpectedMax > 1) {
5657
functionArgs += ` = [],\n`
5758
} else {
5859
if (value.itemsExpectedMax > 1) {

packages/core/typescript/itk-wasm/src/bindgen/python/wasi/wasi-function-module.js

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ from itkwasm import (
5353
const interfaceType = interfaceJsonTypeToInterfaceType.get(output.type)
5454
const isArray = output.itemsExpectedMax > 1
5555
switch (interfaceType) {
56-
case "TextFile":
57-
case "BinaryFile":
56+
case 'TextFile':
57+
case 'BinaryFile':
5858
if (isArray) {
5959
haveArray = true
6060
pipelineOutputs += ` *${snakeCase(output.name)}_pipeline_outputs,\n`
@@ -97,12 +97,12 @@ from itkwasm import (
9797
if (interfaceJsonTypeToInterfaceType.has(input.type)) {
9898
const interfaceType = interfaceJsonTypeToInterfaceType.get(input.type)
9999
switch (interfaceType) {
100-
case "TextFile":
101-
case "BinaryFile":
100+
case 'TextFile':
101+
case 'BinaryFile':
102102
pipelineInputs += ` PipelineInput(InterfaceTypes.${interfaceType}, ${interfaceType}(PurePosixPath(${snakeCase(input.name)}))),\n`
103103
break
104-
case "TextStream":
105-
case "BinaryStream":
104+
case 'TextStream':
105+
case 'BinaryStream':
106106
pipelineInputs += ` PipelineInput(InterfaceTypes.${interfaceType}, ${interfaceType}(${snakeCase(input.name)})),\n`
107107
break
108108
default:
@@ -113,7 +113,7 @@ from itkwasm import (
113113

114114
let args = ` args: List[str] = ['--memory-io',]\n`
115115
let inputCount = 0
116-
args += " # Inputs\n"
116+
args += ' # Inputs\n'
117117
interfaceJson.inputs.forEach((input) => {
118118
const snakeName = snakeCase(input.name)
119119
if (interfaceJsonTypeToInterfaceType.has(input.type)) {
@@ -122,7 +122,9 @@ from itkwasm import (
122122
args += ` if not Path(${snakeName}).exists():\n`
123123
args += ` raise FileNotFoundError("${snakeName} does not exist")\n`
124124
}
125-
const name = interfaceType.includes('File') ? `str(PurePosixPath(${snakeName}))` : `'${inputCount.toString()}'`
125+
const name = interfaceType.includes('File')
126+
? `str(PurePosixPath(${snakeName}))`
127+
: `'${inputCount.toString()}'`
126128
args += ` args.append(${name})\n`
127129
inputCount++
128130
} else {
@@ -131,7 +133,7 @@ from itkwasm import (
131133
})
132134

133135
let outputCount = 0
134-
args += " # Outputs\n"
136+
args += ' # Outputs\n'
135137
interfaceJson.outputs.forEach((output) => {
136138
const snake = snakeCase(output.name)
137139
if (interfaceJsonTypeToInterfaceType.has(output.type)) {
@@ -158,15 +160,15 @@ from itkwasm import (
158160
}
159161
})
160162

161-
args += " # Options\n"
163+
args += ' # Options\n'
162164
args += ` input_count = len(pipeline_inputs)\n`
163165
interfaceJson.parameters.forEach((parameter) => {
164166
if (parameter.name === 'memory-io' || parameter.name === 'version') {
165167
// Internal
166168
return
167169
}
168170
const snake = snakeCase(parameter.name)
169-
if (parameter.type === "BOOL") {
171+
if (parameter.type === 'BOOL') {
170172
args += ` if ${snake}:\n`
171173
args += ` args.append('--${parameter.name}')\n`
172174
} else if (parameter.itemsExpectedMax > 1) {
@@ -177,7 +179,9 @@ from itkwasm import (
177179
args += ` args.append('--${parameter.name}')\n`
178180
args += ` for value in ${snake}:\n`
179181
if (interfaceJsonTypeToInterfaceType.has(parameter.type)) {
180-
const interfaceType = interfaceJsonTypeToInterfaceType.get(parameter.type)
182+
const interfaceType = interfaceJsonTypeToInterfaceType.get(
183+
parameter.type
184+
)
181185
if (interfaceType.includes('File')) {
182186
// for files
183187
args += ` input_file = str(PurePosixPath(value))\n`
@@ -195,12 +199,19 @@ from itkwasm import (
195199
args += ` input_count += 1\n`
196200
}
197201
} else {
202+
if (parameter.type.startsWith('TEXT:{')) {
203+
const choices = parameter.type.split('{')[1].split('}')[0].split(',')
204+
args += ` if ${snake} not in (${choices.map((c) => `'${c}'`).join(',')}):\n`
205+
args += ` raise ValueError(f'${snake} must be one of ${choices.join(', ')}')\n`
206+
}
198207
args += ` args.append(str(value))\n`
199208
}
200209
} else {
201210
if (interfaceJsonTypeToInterfaceType.has(parameter.type)) {
202211
args += ` if ${snake} is not None:\n`
203-
const interfaceType = interfaceJsonTypeToInterfaceType.get(parameter.type)
212+
const interfaceType = interfaceJsonTypeToInterfaceType.get(
213+
parameter.type
214+
)
204215
if (interfaceType.includes('File')) {
205216
// for files
206217
args += ` input_file = str(PurePosixPath(${snakeCase(parameter.name)}))\n`
@@ -222,6 +233,11 @@ from itkwasm import (
222233
}
223234
} else {
224235
args += ` if ${snake}:\n`
236+
if (parameter.type.startsWith('TEXT:{')) {
237+
const choices = parameter.type.split('{')[1].split('}')[0].split(',')
238+
args += ` if ${snake} not in (${choices.map((c) => `'${c}'`).join(',')}):\n`
239+
args += ` raise ValueError(f'${snake} must be one of ${choices.join(', ')}')\n`
240+
}
225241
args += ` args.append('--${parameter.name}')\n`
226242
args += ` args.append(str(${snake}))\n`
227243
}
@@ -234,34 +250,36 @@ from itkwasm import (
234250
const canonical = canonicalType(type)
235251
const pythonType = interfaceJsonTypeToPythonType.get(canonical)
236252
switch (pythonType) {
237-
case "os.PathLike":
253+
case 'os.PathLike':
238254
return `Path(${value}.data.path)`
239-
case "str":
255+
case 'str':
240256
if (type === 'TEXT') {
241257
return `${value}`
242258
} else {
243259
return `${value}.data.data`
244260
}
245-
case "bytes":
261+
case 'bytes':
246262
return `${value}.data.data`
247-
case "int":
263+
case 'int':
248264
return `int(${value})`
249-
case "bool":
265+
case 'bool':
250266
return `bool(${value})`
251-
case "float":
267+
case 'float':
252268
return `float(${value})`
253-
case "Any":
269+
case 'Any':
254270
return `${value}.data`
255271
default:
256272
return `${value}.data`
257273
}
258274
}
259275
outputCount = 0
260276
const jsonOutputs = interfaceJson['outputs']
261-
const numOutputs = interfaceJson.outputs.filter(o => !o.type.includes('FILE')).length
277+
const numOutputs = interfaceJson.outputs.filter(
278+
(o) => !o.type.includes('FILE')
279+
).length
262280
if (numOutputs > 1) {
263281
postOutput += ' result = (\n'
264-
} else if (numOutputs === 1){
282+
} else if (numOutputs === 1) {
265283
postOutput = ' result = '
266284
}
267285

packages/core/typescript/itk-wasm/src/bindgen/typescript/function-module.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,13 @@ function functionModule(
478478
functionContent += ' args.push(inputCountString)\n\n'
479479
}
480480
} else {
481-
functionContent += ' args.push(value.toString())\n\n'
481+
if (parameter.type.startsWith('TEXT:{')) {
482+
const choices = parameter.type.split('{')[1].split('}')[0].split(',')
483+
functionContent += ` if (![${choices.map((c) => `'${c}'`).join(', ')}].includes(options.${camel})) {\n`
484+
functionContent += ` throw new Error('"${parameter.name}" option must be one of ${choices.join(', ')}')\n`
485+
functionContent += ' }\n'
486+
}
487+
functionContent += ' args.push(value.toString())\n'
482488
}
483489
functionContent += forNode ? ' })\n' : ' }))\n'
484490
} else {
@@ -518,6 +524,12 @@ function functionModule(
518524
functionContent += ` args.push('--${parameter.name}', inputCountString)\n\n`
519525
}
520526
} else {
527+
if (parameter.type.startsWith('TEXT:{')) {
528+
const choices = parameter.type.split('{')[1].split('}')[0].split(',')
529+
functionContent += ` if (![${choices.map((c) => `'${c}'`).join(', ')}].includes(options.${camel})) {\n`
530+
functionContent += ` throw new Error('"${parameter.name}" option must be one of ${choices.join(', ')}')\n`
531+
functionContent += ' }\n'
532+
}
521533
functionContent += ` args.push('--${parameter.name}', options.${camel}.toString())\n\n`
522534
}
523535
}

packages/core/typescript/itk-wasm/test/pipelines/input-output-json-pipeline/input-output-json-test.cxx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ int main( int argc, char * argv[] )
2828
itk::wasm::InputTextStream inputJson;
2929
pipeline.add_option("input-json", inputJson, "The input json")->type_name("INPUT_JSON");
3030

31+
std::string stringChoice = "valuea";
32+
pipeline.add_option("--string-choice", stringChoice, "A string choice, one of: valuea, valueb, or valuec")->check(CLI::IsMember({"valuea", "valueb", "valuec"}));
33+
3134
itk::wasm::OutputTextStream outputJson;
3235
pipeline.add_option("output-json", outputJson, "The output json")->type_name("OUTPUT_JSON");
3336

0 commit comments

Comments
 (0)