diff --git a/lib/cli.js b/lib/cli.js index a4deb87d..fe49dfe9 100755 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,111 +1,35 @@ #!/usr/bin/env node "use strict"; -//console.dir(argv); - -//TODO: what to require? const csdl = require("odata-csdl"); const lib = require("./csdl2openapi"); -const minimist = require("minimist"); const fs = require("fs"); +const { parseArgs } = require("./cliParts"); const { stringifyStream } = require("@discoveryjs/json-ext"); -var unknown = false; - -var argv = minimist(process.argv.slice(2), { - string: [ - "basePath", - "description", - "host", - "keep", - "levels", - "openapi-version", - "scheme", - "target", - "title", - ], - boolean: ["diagram", "help", "pretty", "skipBatchPath", "used-schemas-only"], - alias: { - d: "diagram", - h: "help", - k: "keep", - o: "openapi-version", - p: "pretty", - t: "target", - u: "used-schemas-only", - }, - default: { - levels: 4, - pretty: false, - skipBatchPath: false, - }, - unknown: (arg) => { - if (arg.substring(0, 1) == "-") { - console.error("Unknown option: " + arg); - unknown = true; - return false; - } - }, -}); - -if (unknown || argv._.length == 0 || argv.h) { - console.log(`Usage: odata-openapi3 -Options: - --basePath base path (default: /service-root) - --description default description if none is annotated - -d, --diagram include YUML diagram - -h, --help show this info - --host host (default: localhost) - -k, --keep root resource to keep (can be specified multiple times with one name each) - --levels maximum number of path segments - -o, --openapi-version 3.0.0 to 3.0.3 or 3.1.0 (default: 3.0.2) - -p, --pretty pretty-print JSON result - --scheme scheme (default: http) - --skipBatchPath skips the generation of the $batch path, (default: false) - -t, --target target file (default: source file basename + .openapi3.json) - --title default title if none is annotated`); -} else { - //TODO: further input parameters reserved for e.g. referenced CSDL documents - // for (var i = 0; i < argv._.length; i++) { - // convert(argv._[i]); - // } - convert(argv._[0]); -} +const args = parseArgs(process.argv.slice(2)); +if (args.unknown) + args.unknown.map((arg) => console.error(`Unknown option: ${arg}`)); +if (args.usage) console.log(args.usage); +else convert(args); -function convert(source) { - if (!fs.existsSync(source)) { - console.error("Source file not found: " + source); +function convert(args) { + if (!fs.existsSync(args.source)) { + console.error("Source file not found: " + args.source); return; } - const text = fs.readFileSync(source, "utf8"); + const text = fs.readFileSync(args.source, "utf8"); const json = text.startsWith("<") ? csdl.xml2json(text) : JSON.parse(text); - const target = - argv.target || - (source.lastIndexOf(".") <= 0 - ? source - : source.substring(0, source.lastIndexOf("."))) + ".openapi3.json"; - console.log(target); + console.log(args.target); const messages = []; - const openapi = lib.csdl2openapi(json, { - scheme: argv.scheme, - host: argv.host, - basePath: argv.basePath, - diagram: argv.diagram, - maxLevels: Number(argv.levels), - openapiVersion: argv.o, - messages, - skipBatchPath: argv.skipBatchPath, - defaultTitle: argv.title, - defaultDescription: argv.description, - rootResourcesToKeep: Array.isArray(argv.keep) ? argv.keep : argv.keep? [argv.keep]: undefined, - }); + const openapi = lib.csdl2openapi(json, { messages, ...args.options }); - stringifyStream(openapi, null, argv.pretty ? 4 : 0).pipe( - fs.createWriteStream(target), + stringifyStream(openapi, null, args.options.pretty ? 4 : 0).pipe( + fs.createWriteStream(args.target), ); if (messages.length > 0) console.dir(messages); diff --git a/lib/cliParts.js b/lib/cliParts.js new file mode 100644 index 00000000..931f2033 --- /dev/null +++ b/lib/cliParts.js @@ -0,0 +1,111 @@ +/** + * Parse command-line options + * + * Latest version: https://github.com/oasis-tcs/odata-openapi/blob/main/lib/cliOptions.js + */ + +const minimist = require("minimist"); + +module.exports = { parseArgs }; + +function parseArgs(argv) { + const usage = `Usage: odata-openapi3 + Options: + --basePath base path (default: /service-root) + --description default description if none is annotated + -d, --diagram include YUML diagram + -h, --help show this info + --host host (default: localhost) + -k, --keep root resource to keep (can be specified multiple times with one name each) + --levels maximum number of path segments + -o, --openapi-version 3.0.0 to 3.0.3 or 3.1.0 (default: 3.0.2) + -p, --pretty pretty-print JSON result + --scheme scheme (default: http) + --skipBatchPath skips the generation of the $batch path, (default: false) + -t, --target target file (default: source file basename + .openapi3.json) + --title default title if none is annotated`; + const unknown = []; + + const args = minimist(argv, { + string: [ + "basePath", + "description", + "host", + "keep", + "levels", + "openapi-version", + "scheme", + "target", + "title", + ], + boolean: ["diagram", "help", "pretty", "skipBatchPath"], + alias: { + d: "diagram", + h: "help", + k: "keep", + o: "openapi-version", + p: "pretty", + t: "target", + }, + default: { + pretty: false, + }, + unknown: (arg) => { + if (arg.substring(0, 1) == "-") { + unknown.push(arg); + return false; + } + }, + }); + + if (unknown.length > 0 || args.help || args._.length !== 1) + return { + unknown, + usage, + }; + + const source = args._[0]; + const target = + args.target || + (source.lastIndexOf(".") > 0 + ? source.substring(0, source.lastIndexOf(".")) + : source) + ".openapi3.json"; + + const options = {}; + + for (const [name, value] of Object.entries(args)) { + if (name.length === 1) continue; + if (value === false) continue; + switch (name) { + case "description": + options.defaultDescription = value; + break; + case "keep": + if (Array.isArray(value)) options.rootResourcesToKeep = value; + else options.rootResourcesToKeep = [value]; + break; + case "levels": { + const l = Number(value); + if (!isNaN(l)) options.maxLevels = l; + break; + } + case "openapi-version": + options.openapiVersion = value; + break; + case "target": + break; + case "title": + options.defaultTitle = value; + break; + default: + options[name] = value; + break; + } + } + + return { + source, + target, + options, + }; +} diff --git a/package.json b/package.json index cd656ffc..d46ebc98 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ }, "scripts": { "build": "node lib/transform.js", - "pretest": "node lib/cli.js -p --skipBatchPath -t examples/PingTest_V1.no-batch.openapi3.json examples/PingTest_V1.xml", + "pretest": "rm examples/PingTest_V1.no-batch.openapi3.json && node lib/cli.js -p --skipBatchPath -t examples/PingTest_V1.no-batch.openapi3.json examples/PingTest_V1.xml", "test": "c8 mocha", "watch": "mocha --watch" }, diff --git a/test/cliParts.test.js b/test/cliParts.test.js new file mode 100644 index 00000000..a1ff1d9e --- /dev/null +++ b/test/cliParts.test.js @@ -0,0 +1,160 @@ +const assert = require("assert"); + +const { parseArgs } = require("../lib/cliParts"); + +describe("CLI parameters", function () { + it("no arguments", function () { + const args = parseArgs([]); + assert.deepStrictEqual(args.unknown, []); + assert.match(args.usage, /Usage:/); + }); + + it("help", function () { + const args = parseArgs(["-h"]); + assert.deepStrictEqual(args.unknown, []); + assert.match(args.usage, /Usage:/); + + const args2 = parseArgs(["--help", "foo"]); + assert.deepStrictEqual(args2.unknown, []); + assert.match(args2.usage, /Usage:/); + }); + + it("unknown option", function () { + const args = parseArgs(["--do-not-know", "foo", "--whatever", "bar"]); + assert.deepStrictEqual(args.unknown, ["--do-not-know", "--whatever"]); + assert.match(args.usage, /Usage:/); + }); + + it("just the source", function () { + assert.deepStrictEqual(parseArgs(["foo"]), { + source: "foo", + target: "foo.openapi3.json", + options: {}, + }); + + assert.deepStrictEqual(parseArgs([".foo"]), { + source: ".foo", + target: ".foo.openapi3.json", + options: {}, + }); + + assert.deepStrictEqual(parseArgs(["foo.bar"]), { + source: "foo.bar", + target: "foo.openapi3.json", + options: {}, + }); + }); + + it("source and target", function () { + assert.deepStrictEqual(parseArgs(["foo", "-t", "bar"]), { + source: "foo", + target: "bar", + options: {}, + }); + + assert.deepStrictEqual(parseArgs(["-t", "bar", "foo"]), { + source: "foo", + target: "bar", + options: {}, + }); + }); + + it("two sources", function () { + const args = parseArgs(["foo", "bar"]); + assert.deepStrictEqual(args.unknown, []); + assert.match(args.usage, /Usage:/); + }); + + it("pretty", function () { + assert.deepStrictEqual(parseArgs(["foo", "-p"]).options, { + pretty: true, + }); + assert.deepStrictEqual(parseArgs(["--pretty", "foo"]).options, { + pretty: true, + }); + }); + + it("all flags on", function () { + assert.deepStrictEqual(parseArgs(["-dp", "foo"]).options, { + diagram: true, + pretty: true, + }); + assert.deepStrictEqual(parseArgs(["-d", "-p", "foo"]).options, { + diagram: true, + pretty: true, + }); + assert.deepStrictEqual( + parseArgs(["--diagram", "--pretty", "foo"]).options, + { + diagram: true, + pretty: true, + }, + ); + }); + + it("service root", function () { + assert.deepStrictEqual( + parseArgs([ + "--scheme", + "http", + "--host", + "localhost", + "--basePath", + "/root", + "foo", + ]).options, + { + scheme: "http", + host: "localhost", + basePath: "/root", + }, + ); + }); + + it("title & description & skip batch", function () { + assert.deepStrictEqual( + parseArgs([ + "--title", + "Title", + "--description", + "Description", + "--skipBatchPath", + "foo", + ]).options, + { + defaultTitle: "Title", + defaultDescription: "Description", + skipBatchPath: true, + }, + ); + }); + + it("openapi-version", function () { + assert.deepStrictEqual(parseArgs(["-o", "4.0", "foo"]).options, { + openapiVersion: "4.0", + }); + assert.deepStrictEqual( + parseArgs(["--openapi-version", "4.0", "foo"]).options, + { + openapiVersion: "4.0", + }, + ); + }); + + it("recursion levels", function () { + assert.deepStrictEqual(parseArgs(["--levels", "42", "foo"]).options, { + maxLevels: 42, + }); + assert.deepStrictEqual(parseArgs(["--levels", "max", "foo"]).options, {}); + }); + + it("root resources to keep", function () { + assert.deepStrictEqual(parseArgs(["--keep", "one", "foo"]).options, { + rootResourcesToKeep: ["one"], + }); + assert.deepStrictEqual( + parseArgs(["--k", "first", "--keep", "second", "foo"]).options, + { rootResourcesToKeep: ["first", "second"] }, + ); + }); +});