diff --git a/dist/action.mjs b/dist/action.mjs index f639502..e98a1ee 100644 --- a/dist/action.mjs +++ b/dist/action.mjs @@ -123,279 +123,24 @@ async function buildProject(context) { await exec("cmake", ["--build", context.buildDir, ...context.build.args]); } -var shellQuote = {}; - -var quote; -var hasRequiredQuote; - -function requireQuote () { - if (hasRequiredQuote) return quote; - hasRequiredQuote = 1; - - quote = function quote(xs) { - return xs.map(function (s) { - if (s && typeof s === 'object') { - return s.op.replace(/(.)/g, '\\$1'); - } - if ((/["\s]/).test(s) && !(/'/).test(s)) { - return "'" + s.replace(/(['\\])/g, '\\$1') + "'"; - } - if ((/["'\s]/).test(s)) { - return '"' + s.replace(/(["\\$`!])/g, '\\$1') + '"'; - } - return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, '$1\\$2'); - }).join(' '); - }; - return quote; +const regex = /"([^"]*)"|'([^']*)'|`([^`]*)`|(\S+)/g; +/** + * Converts a space-separated string into a list of arguments. + * + * This function parses the provided string, which contains arguments separated by spaces and possibly enclosed in quotes, into a list of arguments. + * + * @param str - The space-separated string to parse. + * @returns A list of arguments. + */ +function parse(str) { + const args = []; + let match; + while ((match = regex.exec(str)) !== null) { + args.push(match[1] ?? match[2] ?? match[3] ?? match[4]); + } + return args; } -var parse; -var hasRequiredParse; - -function requireParse () { - if (hasRequiredParse) return parse; - hasRequiredParse = 1; - - // '<(' is process substitution operator and - // can be parsed the same as control operator - var CONTROL = '(?:' + [ - '\\|\\|', - '\\&\\&', - ';;', - '\\|\\&', - '\\<\\(', - '\\<\\<\\<', - '>>', - '>\\&', - '<\\&', - '[&;()|<>]' - ].join('|') + ')'; - var controlRE = new RegExp('^' + CONTROL + '$'); - var META = '|&;()<> \\t'; - var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"'; - var DOUBLE_QUOTE = '\'((\\\\\'|[^\'])*?)\''; - var hash = /^#$/; - - var SQ = "'"; - var DQ = '"'; - var DS = '$'; - - var TOKEN = ''; - var mult = 0x100000000; // Math.pow(16, 8); - for (var i = 0; i < 4; i++) { - TOKEN += (mult * Math.random()).toString(16); - } - var startsWithToken = new RegExp('^' + TOKEN); - - function matchAll(s, r) { - var origIndex = r.lastIndex; - - var matches = []; - var matchObj; - - while ((matchObj = r.exec(s))) { - matches.push(matchObj); - if (r.lastIndex === matchObj.index) { - r.lastIndex += 1; - } - } - - r.lastIndex = origIndex; - - return matches; - } - - function getVar(env, pre, key) { - var r = typeof env === 'function' ? env(key) : env[key]; - if (typeof r === 'undefined' && key != '') { - r = ''; - } else if (typeof r === 'undefined') { - r = '$'; - } - - if (typeof r === 'object') { - return pre + TOKEN + JSON.stringify(r) + TOKEN; - } - return pre + r; - } - - function parseInternal(string, env, opts) { - if (!opts) { - opts = {}; - } - var BS = opts.escape || '\\'; - var BAREWORD = '(\\' + BS + '[\'"' + META + ']|[^\\s\'"' + META + '])+'; - - var chunker = new RegExp([ - '(' + CONTROL + ')', // control chars - '(' + BAREWORD + '|' + SINGLE_QUOTE + '|' + DOUBLE_QUOTE + ')+' - ].join('|'), 'g'); - - var matches = matchAll(string, chunker); - - if (matches.length === 0) { - return []; - } - if (!env) { - env = {}; - } - - var commented = false; - - return matches.map(function (match) { - var s = match[0]; - if (!s || commented) { - return void undefined; - } - if (controlRE.test(s)) { - return { op: s }; - } - - // Hand-written scanner/parser for Bash quoting rules: - // - // 1. inside single quotes, all characters are printed literally. - // 2. inside double quotes, all characters are printed literally - // except variables prefixed by '$' and backslashes followed by - // either a double quote or another backslash. - // 3. outside of any quotes, backslashes are treated as escape - // characters and not printed (unless they are themselves escaped) - // 4. quote context can switch mid-token if there is no whitespace - // between the two quote contexts (e.g. all'one'"token" parses as - // "allonetoken") - var quote = false; - var esc = false; - var out = ''; - var isGlob = false; - var i; - - function parseEnvVar() { - i += 1; - var varend; - var varname; - var char = s.charAt(i); - - if (char === '{') { - i += 1; - if (s.charAt(i) === '}') { - throw new Error('Bad substitution: ' + s.slice(i - 2, i + 1)); - } - varend = s.indexOf('}', i); - if (varend < 0) { - throw new Error('Bad substitution: ' + s.slice(i)); - } - varname = s.slice(i, varend); - i = varend; - } else if ((/[*@#?$!_-]/).test(char)) { - varname = char; - i += 1; - } else { - var slicedFromI = s.slice(i); - varend = slicedFromI.match(/[^\w\d_]/); - if (!varend) { - varname = slicedFromI; - i = s.length; - } else { - varname = slicedFromI.slice(0, varend.index); - i += varend.index - 1; - } - } - return getVar(env, '', varname); - } - - for (i = 0; i < s.length; i++) { - var c = s.charAt(i); - isGlob = isGlob || (!quote && (c === '*' || c === '?')); - if (esc) { - out += c; - esc = false; - } else if (quote) { - if (c === quote) { - quote = false; - } else if (quote == SQ) { - out += c; - } else { // Double quote - if (c === BS) { - i += 1; - c = s.charAt(i); - if (c === DQ || c === BS || c === DS) { - out += c; - } else { - out += BS + c; - } - } else if (c === DS) { - out += parseEnvVar(); - } else { - out += c; - } - } - } else if (c === DQ || c === SQ) { - quote = c; - } else if (controlRE.test(c)) { - return { op: s }; - } else if (hash.test(c)) { - commented = true; - var commentObj = { comment: string.slice(match.index + i + 1) }; - if (out.length) { - return [out, commentObj]; - } - return [commentObj]; - } else if (c === BS) { - esc = true; - } else if (c === DS) { - out += parseEnvVar(); - } else { - out += c; - } - } - - if (isGlob) { - return { op: 'glob', pattern: out }; - } - - return out; - }).reduce(function (prev, arg) { // finalize parsed arguments - // TODO: replace this whole reduce with a concat - return typeof arg === 'undefined' ? prev : prev.concat(arg); - }, []); - } - - parse = function parse(s, env, opts) { - var mapped = parseInternal(s, env, opts); - if (typeof env !== 'function') { - return mapped; - } - return mapped.reduce(function (acc, s) { - if (typeof s === 'object') { - return acc.concat(s); - } - var xs = s.split(RegExp('(' + TOKEN + '.*?' + TOKEN + ')', 'g')); - if (xs.length === 1) { - return acc.concat(xs[0]); - } - return acc.concat(xs.filter(Boolean).map(function (x) { - if (startsWithToken.test(x)) { - return JSON.parse(x.split(TOKEN)[1]); - } - return x; - })); - }, []); - }; - return parse; -} - -var hasRequiredShellQuote; - -function requireShellQuote () { - if (hasRequiredShellQuote) return shellQuote; - hasRequiredShellQuote = 1; - - shellQuote.quote = requireQuote(); - shellQuote.parse = requireParse(); - return shellQuote; -} - -var shellQuoteExports = requireShellQuote(); - function getContext() { const sourceDir = getInput("source-dir"); const options = []; @@ -417,7 +162,7 @@ function getContext() { } input = getInput("options"); if (input) { - options.push(...shellQuoteExports.parse(input).map((opt) => opt.toString())); + options.push(...parse(input).map((opt) => opt.toString())); } return { sourceDir, @@ -425,11 +170,11 @@ function getContext() { configure: { generator: getInput("generator"), options, - args: shellQuoteExports.parse(getInput("args")).map((arg) => arg.toString()), + args: parse(getInput("args")).map((arg) => arg.toString()), }, build: { enabled: getInput("run-build") == "true", - args: shellQuoteExports.parse(getInput("build-args")).map((arg) => arg.toString()), + args: parse(getInput("build-args")).map((arg) => arg.toString()), }, }; } diff --git a/package.json b/package.json index b9107df..f62d6c0 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,15 @@ "test": "jest" }, "dependencies": { - "gha-utils": "^0.4.1", - "shell-quote": "^1.8.1" + "gha-utils": "^0.4.1" }, "devDependencies": { "@eslint/js": "^9.15.0", "@jest/globals": "^29.7.0", - "@rollup/plugin-commonjs": "^28.0.1", "@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-typescript": "^12.1.1", "@types/jest": "^29.5.14", "@types/node": "^22.9.1", - "@types/shell-quote": "^1", "eslint": "^9.15.0", "jest": "^29.7.0", "prettier": "^3.3.3", diff --git a/rollup.config.js b/rollup.config.js index 8ba4836..5119846 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,4 +1,3 @@ -import commonjs from "@rollup/plugin-commonjs"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import typescript from "@rollup/plugin-typescript"; @@ -8,5 +7,5 @@ export default { dir: "dist", entryFileNames: "[name].mjs", }, - plugins: [commonjs(), nodeResolve(), typescript()], + plugins: [nodeResolve(), typescript()], }; diff --git a/src/context.test.ts b/src/context.test.ts index 163a8c4..36cf992 100644 --- a/src/context.test.ts +++ b/src/context.test.ts @@ -99,7 +99,7 @@ describe("get action context", () => { { name: "with additional options specified", inputs: { - options: `BUILD_TESTING=ON BUILD_EXAMPLES=ON\nBUILD_DOCS=ON FOO="BAR BAZ"`, + options: `BUILD_TESTING=ON BUILD_EXAMPLES=ON\nBUILD_DOCS=ON "FOO=BAR BAZ"`, }, expectedContext: { configure: { @@ -150,7 +150,7 @@ describe("get action context", () => { "cxx-compiler": "clang++", "c-flags": "-Werror -Wall\n-Wextra", "cxx-flags": "-Werror -Wall\n-Wextra -Wpedantic", - options: `BUILD_TESTING=ON BUILD_EXAMPLES=ON\nBUILD_DOCS=ON FOO="BAR BAZ"`, + options: `BUILD_TESTING=ON BUILD_EXAMPLES=ON\nBUILD_DOCS=ON "FOO=BAR BAZ"`, args: `-Wdev -Wdeprecated\n--fresh --foo "bar baz"`, "run-build": "true", "build-args": `--target foo\n--parallel 8 --foo "bar baz"`, diff --git a/src/context.ts b/src/context.ts index 1415418..8d563ec 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,6 +1,6 @@ import { getInput } from "gha-utils"; import path from "node:path"; -import { parse } from "shell-quote"; +import { parse } from "./utils.js"; export interface Context { sourceDir: string; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..efa4463 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,18 @@ +const regex = /"([^"]*)"|'([^']*)'|`([^`]*)`|(\S+)/g; + +/** + * Converts a space-separated string into a list of arguments. + * + * This function parses the provided string, which contains arguments separated by spaces and possibly enclosed in quotes, into a list of arguments. + * + * @param str - The space-separated string to parse. + * @returns A list of arguments. + */ +export function parse(str: string): string[] { + const args: string[] = []; + let match: RegExpExecArray | null; + while ((match = regex.exec(str)) !== null) { + args.push(match[1] ?? match[2] ?? match[3] ?? match[4]); + } + return args; +} diff --git a/yarn.lock b/yarn.lock index 9e60b59..e8250ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -832,13 +832,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.5.0": - version: 1.5.0 - resolution: "@jridgewell/sourcemap-codec@npm:1.5.0" - checksum: 10c0/2eb864f276eb1096c3c11da3e9bb518f6d9fc0023c78344cdc037abadc725172c70314bdb360f2d4b7bffec7f5d657ce006816bc5d4ecb35e61b66132db00c18 - languageName: node - linkType: hard - "@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.24": version: 0.3.25 resolution: "@jridgewell/trace-mapping@npm:0.3.25" @@ -905,26 +898,6 @@ __metadata: languageName: node linkType: hard -"@rollup/plugin-commonjs@npm:^28.0.1": - version: 28.0.1 - resolution: "@rollup/plugin-commonjs@npm:28.0.1" - dependencies: - "@rollup/pluginutils": "npm:^5.0.1" - commondir: "npm:^1.0.1" - estree-walker: "npm:^2.0.2" - fdir: "npm:^6.2.0" - is-reference: "npm:1.2.1" - magic-string: "npm:^0.30.3" - picomatch: "npm:^4.0.2" - peerDependencies: - rollup: ^2.68.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - checksum: 10c0/15d73306f539763a4b0d5723a0be9099b56d07118ff12b4c7f4c04b26e762076706e9f88a45f131d639ed9b7bd52e51facf93f2ca265b994172677b48ca705fe - languageName: node - linkType: hard - "@rollup/plugin-node-resolve@npm:^15.3.0": version: 15.3.0 resolution: "@rollup/plugin-node-resolve@npm:15.3.0" @@ -1170,7 +1143,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:1.0.6, @types/estree@npm:^1.0.6": +"@types/estree@npm:1.0.6, @types/estree@npm:^1.0.6": version: 1.0.6 resolution: "@types/estree@npm:1.0.6" checksum: 10c0/cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a @@ -1260,13 +1233,6 @@ __metadata: languageName: node linkType: hard -"@types/shell-quote@npm:^1": - version: 1.7.5 - resolution: "@types/shell-quote@npm:1.7.5" - checksum: 10c0/ddcf225e85e5520e3f44411d7d79eee0e56477fab705d0d93e293b61b9f8de2a57db6e859d492a24bc9e0d071c0490271efeae832756e2ac0d4d255922ac281d - languageName: node - linkType: hard - "@types/stack-utils@npm:^2.0.0": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" @@ -1875,13 +1841,6 @@ __metadata: languageName: node linkType: hard -"commondir@npm:^1.0.1": - version: 1.0.1 - resolution: "commondir@npm:1.0.1" - checksum: 10c0/33a124960e471c25ee19280c9ce31ccc19574b566dc514fe4f4ca4c34fa8b0b57cf437671f5de380e11353ea9426213fca17687dd2ef03134fea2dbc53809fd6 - languageName: node - linkType: hard - "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -2340,18 +2299,6 @@ __metadata: languageName: node linkType: hard -"fdir@npm:^6.2.0": - version: 6.4.2 - resolution: "fdir@npm:6.4.2" - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - checksum: 10c0/34829886f34a3ca4170eca7c7180ec4de51a3abb4d380344063c0ae2e289b11d2ba8b724afee974598c83027fea363ff598caf2b51bc4e6b1e0d8b80cc530573 - languageName: node - linkType: hard - "file-entry-cache@npm:^8.0.0": version: 8.0.0 resolution: "file-entry-cache@npm:8.0.0" @@ -2797,15 +2744,6 @@ __metadata: languageName: node linkType: hard -"is-reference@npm:1.2.1": - version: 1.2.1 - resolution: "is-reference@npm:1.2.1" - dependencies: - "@types/estree": "npm:*" - checksum: 10c0/7dc819fc8de7790264a0a5d531164f9f5b9ef5aa1cd05f35322d14db39c8a2ec78fd5d4bf57f9789f3ddd2b3abeea7728432b759636157a42db12a9e8c3b549b - languageName: node - linkType: hard - "is-stream@npm:^2.0.0": version: 2.0.1 resolution: "is-stream@npm:2.0.1" @@ -3538,15 +3476,6 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.3": - version: 0.30.13 - resolution: "magic-string@npm:0.30.13" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.0" - checksum: 10c0/a275faeca1564c545019b4742c38a42ca80226c8c9e0805c32d1a1cc58b0e6ff7bbd914ed885fd10043858a7da0f732cb8f49c8975c3ecebde9cad4b57db5115 - languageName: node - linkType: hard - "make-dir@npm:^4.0.0": version: 4.0.0 resolution: "make-dir@npm:4.0.0" @@ -3990,13 +3919,6 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^4.0.2": - version: 4.0.2 - resolution: "picomatch@npm:4.0.2" - checksum: 10c0/7c51f3ad2bb42c776f49ebf964c644958158be30d0a510efd5a395e8d49cb5acfed5b82c0c5b365523ce18e6ab85013c9ebe574f60305892ec3fa8eee8304ccc - languageName: node - linkType: hard - "pirates@npm:^4.0.4": version: 4.0.6 resolution: "pirates@npm:4.0.6" @@ -4247,18 +4169,15 @@ __metadata: dependencies: "@eslint/js": "npm:^9.15.0" "@jest/globals": "npm:^29.7.0" - "@rollup/plugin-commonjs": "npm:^28.0.1" "@rollup/plugin-node-resolve": "npm:^15.3.0" "@rollup/plugin-typescript": "npm:^12.1.1" "@types/jest": "npm:^29.5.14" "@types/node": "npm:^22.9.1" - "@types/shell-quote": "npm:^1" eslint: "npm:^9.15.0" gha-utils: "npm:^0.4.1" jest: "npm:^29.7.0" prettier: "npm:^3.3.3" rollup: "npm:^4.27.3" - shell-quote: "npm:^1.8.1" ts-jest: "npm:^29.2.5" tslib: "npm:^2.8.1" typescript: "npm:^5.6.3" @@ -4327,13 +4246,6 @@ __metadata: languageName: node linkType: hard -"shell-quote@npm:^1.8.1": - version: 1.8.1 - resolution: "shell-quote@npm:1.8.1" - checksum: 10c0/8cec6fd827bad74d0a49347057d40dfea1e01f12a6123bf82c4649f3ef152fc2bc6d6176e6376bffcd205d9d0ccb4f1f9acae889384d20baff92186f01ea455a - languageName: node - linkType: hard - "signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7"