From c0a62355bbc0bc9ef8889a1a551d2203f2a440f7 Mon Sep 17 00:00:00 2001 From: Nuno Aguiar Date: Fri, 2 Jan 2026 00:59:43 +0000 Subject: [PATCH] Add interactive -i prompt for oafp --- data/completion.yaml | 2 ++ data/usage.json | 4 +++ src/docs/USAGE.md | 1 + src/oafp.source.js.hbs | 67 +++++++++++++++++++++++++++++++++++++++-- src/tests/autoTest.js | 8 ++++- src/tests/autoTest.yaml | 9 +++++- 6 files changed, 87 insertions(+), 4 deletions(-) diff --git a/data/completion.yaml b/data/completion.yaml index 92a9660..24e1a3d 100644 --- a/data/completion.yaml +++ b/data/completion.yaml @@ -524,6 +524,8 @@ complete: desc: A YAML format - name: '-h' desc: Shows the help document +- name: '-i' + desc: Start an interactive prompt (requires file= or data=) to configure input, transforms, and output - name: help= desc: If true will show the help document or other available -e.g. filters, template- opts: diff --git a/data/usage.json b/data/usage.json index a714621..a971d54 100644 --- a/data/usage.json +++ b/data/usage.json @@ -4,6 +4,10 @@ "Option": "-h", "Description": "Show this document" }, + { + "Option": "-i", + "Description": "Start an interactive prompt (requires file= or data=) to configure input, transforms, and output" + }, { "Option": "help", "Description": "Alternative way to show this document or others (e.g. filters, template)" diff --git a/src/docs/USAGE.md b/src/docs/USAGE.md index 3040888..89e792e 100644 --- a/src/docs/USAGE.md +++ b/src/docs/USAGE.md @@ -13,6 +13,7 @@ Takes an input, usually a data structure such as json, and transforms it to an e | Option | Description | |--------|-------------| | -h | Show this document | +| -i | Start an interactive prompt (requires file= or data=) to configure input, transforms, and output | | help | Alternative way to show this document or others (e.g. filters, template) | | file | The file to parse (if not provided stdin is used) | | cmd | Alternative to file and stdin to execute a command (e.g. kubectl, docker) to get the file contents | diff --git a/src/oafp.source.js.hbs b/src/oafp.source.js.hbs index aada4b5..8941d70 100755 --- a/src/oafp.source.js.hbs +++ b/src/oafp.source.js.hbs @@ -194,7 +194,7 @@ const showVersion = () => { if ("undefined" == typeof params.file && "undefined" == typeof params.cmd && "undefined" == typeof params.data && "undefined" == typeof params.url) { let _found = __ for (let key in params) { - if ("undefined" == typeof _found && params[key] === "" && key != "-debug" && key != "-v" && key != "-examples") { + if ("undefined" == typeof _found && params[key] === "" && key != "-debug" && key != "-v" && key != "-examples" && key != "-i") { _found = key break; } @@ -436,6 +436,69 @@ if (params["-examples"] == "" || (isString(params.examples) && params.examples.l delete params["-examples"] } +const _askInteractive = () => { + if (isUnDef(askStruct)) _exit(-1, "ERROR: -i requires OpenAF askStruct support.") + if (isUnDef(params.file) && isUnDef(params.data)) _exit(-1, "ERROR: -i requires file= or data=.") + + let inputTypes = Array.from(_inputFns.keys()).filter(r => r != "?").sort() + let outputTypes = Array.from(_outputFns.keys()).filter(r => r != "?").sort() + let questions = [ + { name: "inputType", prompt: "Input type (auto to detect)", type: "choose", options: [ "auto" ].concat(inputTypes) }, + { name: "inputOptions", prompt: "Input options (JSON/SLON map; leave empty to skip): ", type: "question" }, + { name: "inputFilters", prompt: "Input filters (JSON/SLON map for ifrom/isql; leave empty to skip): ", type: "question" }, + { name: "transformOptions", prompt: "Transform options (JSON/SLON map; leave empty to skip): ", type: "question" }, + { name: "outputType", prompt: "Output format (default to keep)", type: "choose", options: [ "default" ].concat(outputTypes) }, + { name: "outputOptions", prompt: "Output options (JSON/SLON map; leave empty to skip): ", type: "question" }, + { name: "outputFilters", prompt: "Output filters (JSON/SLON map for path/from/sql/opath; leave empty to skip): ", type: "question" }, + { name: "extraOptions", prompt: "Extra options (JSON/SLON map; leave empty to skip): ", type: "question" } + ] + + __initializeCon() + __conConsole = true + __con.getTerminal().settings.set("-icanon min 1 -echo") + let answers = askStruct(questions) + __con.getTerminal().settings.set("icanon echo") + print("") + + let answersMap = {} + if (isArray(answers)) { + answers.forEach(r => { + if (isMap(r) && isDef(r.name)) answersMap[r.name] = r.answer + }) + } + + const _applyMap = (label, value) => { + if (!isString(value)) return + let _v = value.trim() + if (_v.length == 0) return + let _m = _fromJSSLON(_v) + if (!isMap(_m)) _exit(-1, "ERROR: " + label + " must be a JSON/SLON map.") + Object.keys(_m).forEach(k => params[k] = _m[k]) + } + + if (isString(answersMap.inputType)) { + let _t = answersMap.inputType.trim() + if (_t.length > 0 && _t != "auto") params.type = _t + } + + if (isString(answersMap.outputType)) { + let _t = answersMap.outputType.trim() + if (_t.length > 0 && _t != "default") params.format = _t + } + + _applyMap("Input options", answersMap.inputOptions) + _applyMap("Input filters", answersMap.inputFilters) + _applyMap("Transform options", answersMap.transformOptions) + _applyMap("Output options", answersMap.outputOptions) + _applyMap("Output filters", answersMap.outputFilters) + _applyMap("Extra options", answersMap.extraOptions) + + procParams() + delete params["-i"] +} + +if (params["-i"] == "" || toBoolean(params["-i"]) || toBoolean(params.interactive)) _askInteractive() + // Read input from stdin or file var _res = "", noFurtherOutput = false @@ -649,4 +712,4 @@ if (isNumber(params.loop)) { // Close streams if (typeof global.__oafp_streams !== "undefined") Object.keys(global.__oafp_streams).forEach(s => global.__oafp_streams[s].s.close()) } -oafp(_params) \ No newline at end of file +oafp(_params) diff --git a/src/tests/autoTest.js b/src/tests/autoTest.js index d3fa3de..d1be3a7 100644 --- a/src/tests/autoTest.js +++ b/src/tests/autoTest.js @@ -151,6 +151,12 @@ } } + exports.testInteractiveRequiresInput = function() { + var _r = $sh([getOpenAFPath() + "/oaf", "-f", "../oafp.source.js", "-e", "-i"]).get(0) + ow.test.assert(_r.exitcode > 0, true, "Problem with -i requires file or data (exitcode)") + ow.test.assert(String(_r.stderr).indexOf("requires file= or data=") >= 0, true, "Problem with -i requires file or data (message)") + } + // Transforms // ---------- exports.testMerge = function() { @@ -320,4 +326,4 @@ var _r2 = $sh([getOpenAFPath() + "/oaf", "-f", "../oafp.source.js", "-e", "in=csv inputcsv=\"(withDelimiter: '|')\" correcttypes=true out=json file=" + _f2]).get(0) ow.test.assert(compare(jsonParse(_r2.stdout), data1), true, "Problem with csv to json") } -})() \ No newline at end of file +})() diff --git a/src/tests/autoTest.yaml b/src/tests/autoTest.yaml index 1aab3fa..f1bddbc 100644 --- a/src/tests/autoTest.yaml +++ b/src/tests/autoTest.yaml @@ -11,6 +11,7 @@ todo: - oafp::NDJSON2JSON - oafp::NDJSON2JSON_2 - oafp::NDJSON2JSON_2p +- oafp::interactiveRequiresInput - oafp::merge - oafp::sortMapKeys @@ -140,6 +141,13 @@ jobs: args: func: "global.test.testNDJSON2JSON_2p()" +# ------------------------------- +- name: oafp::interactiveRequiresInput + to : oJob Test + deps: Load test + args: + func: "global.test.testInteractiveRequiresInput()" + # ----------------- - name: oafp::merge to : oJob Test @@ -223,4 +231,3 @@ jobs: if (args.results.fail > 0) printErr("There are failed tests") io.writeFileString("oafp-test.md", ow.test.toMarkdown()) io.writeFileJSON("oafp-test.json", args.results) -