diff --git a/.github/actions/bundle/.npmrc b/.github/actions/bundle/.npmrc new file mode 100644 index 00000000..6c59086d --- /dev/null +++ b/.github/actions/bundle/.npmrc @@ -0,0 +1 @@ +enable-pre-post-scripts=true diff --git a/.github/actions/bundle/dist/custom_template.rtf b/.github/actions/bundle/dist/custom_template.rtf new file mode 100644 index 00000000..a33e4911 --- /dev/null +++ b/.github/actions/bundle/dist/custom_template.rtf @@ -0,0 +1,9 @@ + + {\rtf1\ansi\deff0{\fonttbl{\f0 \fswiss Helvetica;}{\f1 \fmodern Courier New;}} + {\colortbl;\red255\green0\blue0;\red0\green0\blue255;} + \widowctrl\hyphauto + \f0\fs20 + \f1\fs20 + $body$ + } + \ No newline at end of file diff --git a/.github/actions/bundle/dist/index.js b/.github/actions/bundle/dist/index.js index 1a96f619..42773d2e 100644 --- a/.github/actions/bundle/dist/index.js +++ b/.github/actions/bundle/dist/index.js @@ -1781,6 +1781,46 @@ function isLoopbackAddress(host) { /***/ }), +/***/ 3166: +/***/ ((module) => { + +module.exports = function dedent(templateStrings) { + var values = []; + for (var _i = 1; _i < arguments.length; _i++) { + values[_i - 1] = arguments[_i]; + } + var matches = []; + var strings = typeof templateStrings === 'string' ? [templateStrings] : templateStrings.slice(); + // 1. Remove trailing whitespace. + strings[strings.length - 1] = strings[strings.length - 1].replace(/\r?\n([\t ]*)$/, ''); + // 2. Find all line breaks to determine the highest common indentation level. + for (var i = 0; i < strings.length; i++) { + var match = void 0; + if (match = strings[i].match(/\n[\t ]+/g)) { + matches.push.apply(matches, match); + } + } + // 3. Remove the common indentation from all strings. + if (matches.length) { + var size = Math.min.apply(Math, matches.map(function (value) { return value.length - 1; })); + var pattern = new RegExp("\n[\t ]{" + size + "}", 'g'); + for (var i = 0; i < strings.length; i++) { + strings[i] = strings[i].replace(pattern, '\n'); + } + } + // 4. Remove leading whitespace. + strings[0] = strings[0].replace(/^\r?\n/, ''); + // 5. Perform interpolation. + var string = strings[0]; + for (var i = 0; i < values.length; i++) { + string += values[i] + strings[i + 1]; + } + return string; +}; +//# sourceMappingURL=index.js.map + +/***/ }), + /***/ 9479: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { @@ -6781,7 +6821,11 @@ const bundleFileBase = (name, importedFiles, mixins, fetcher) => { }; exports.bundleFileBase = bundleFileBase; const bundleFile = (name, sourcePath, mixins) => { - return (0, remove_comments_1.removeComments)((0, exports.bundleFileBase)(name, exports.files, mixins, (fileName) => fs_1.default.readFileSync(path_1.default.join(sourcePath, fileName)).toString())); + const bundled = (0, exports.bundleFileBase)(name, exports.files, mixins, (fileName) => fs_1.default.readFileSync(path_1.default.join(sourcePath, fileName)).toString()); + const parts = (0, helpers_1.getFileParts)(bundled); + return (0, remove_comments_1.removeComments)(parts.prolog, true) + + (0, remove_comments_1.removeComments)(parts.plugindef, false) + + (0, remove_comments_1.removeComments)(parts.epilog, true); }; exports.bundleFile = bundleFile; @@ -6794,7 +6838,7 @@ exports.bundleFile = bundleFile; "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getAllImports = exports.getImport = exports.requireRegex = void 0; +exports.getFileParts = exports.getAllImports = exports.getImport = exports.requireRegex = void 0; exports.requireRegex = new RegExp([ /(?:^|[\W])/, /(?:__original_)?require/, @@ -6833,6 +6877,19 @@ const getAllImports = (file) => { return [...imports]; }; exports.getAllImports = getAllImports; +const plugindefRegex = /(?^function\s+plugindef.*?^end)/ms; +const getFileParts = (contents) => { + const plugindefMatch = contents.match(plugindefRegex); + if ((plugindefMatch === null || plugindefMatch === void 0 ? void 0 : plugindefMatch.index) !== undefined) { + const prolog = contents.slice(0, plugindefMatch.index); + const epilog = contents.slice(plugindefMatch.index + plugindefMatch[0].length); + return { prolog, plugindef: plugindefMatch[0], epilog }; + } + else { + return { prolog: '', plugindef: '', epilog: contents }; + } +}; +exports.getFileParts = getFileParts; /***/ }), @@ -6885,30 +6942,64 @@ sourceFiles.forEach(file => { /***/ }), /***/ 9879: -/***/ ((__unused_webpack_module, exports) => { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.injectExtras = void 0; +const helpers_1 = __nccwpck_require__(8092); +const child_process_1 = __nccwpck_require__(2081); +const dedent_js_1 = __importDefault(__nccwpck_require__(3166)); +const path_1 = __importDefault(__nccwpck_require__(1017)); const injectExtras = (name, contents) => { - const functionRegex = /^(\s*)function\s+plugindef/gm; - const returnRegex = /^(\s*)return/gm; - const endRegex = /^(\s*)*end/gm; - const functionMatch = functionRegex.exec(contents); - if (functionMatch) { - const functionIndex = functionMatch.index; - const returnMatch = returnRegex.exec(contents.substring(functionIndex)); - const endMatch = endRegex.exec(contents.substring(functionIndex)); - const index = Math.min(returnMatch ? returnMatch.index : Infinity, endMatch ? endMatch.index : Infinity); - const strippedName = name.split('.').slice(0, -1).join(''); - const injection = ` finaleplugin.HashURL = \"https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/${strippedName}.hash\"`; - const injectedContents = contents.slice(0, functionIndex + index) + injection + '\n' + contents.slice(functionIndex + index); - return injectedContents; + const parts = (0, helpers_1.getFileParts)(contents); + if (parts.plugindef) { + if (!parts.plugindef.includes('finaleplugin.RTFNotes')) + parts.plugindef = inject(parts.plugindef, getRTFNotes(parts.plugindef)); + parts.plugindef = inject(parts.plugindef, getHashURL(name)); } - return contents; + return parts.prolog + parts.plugindef + parts.epilog; }; exports.injectExtras = injectExtras; +const inject = (contents, injection) => { + if (injection) { + const returnRegex = /^(\s*)return/gm; + const endRegex = /^(\s*)*end/gm; + const returnMatch = returnRegex.exec(contents); + const endMatch = endRegex.exec(contents); + const index = Math.min(returnMatch ? returnMatch.index : Infinity, endMatch ? endMatch.index : Infinity); + return contents.slice(0, index) + injection + '\n' + contents.slice(index); + } + else { + return contents; + } +}; +const getHashURL = (name) => { + const strippedName = name.split('.').slice(0, -1).join(''); + return ` finaleplugin.HashURL = "https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/${strippedName}.hash"`; +}; +const getRTFNotes = (input) => { + let result = ''; + const notesRegex = /(?<=finaleplugin.Notes = \[\[).*(?=\]\])/ius; + const match = input.match(notesRegex); + if (match) { + const notes = (0, dedent_js_1.default)(match[0]); + const templateFile = path_1.default.join(__dirname, "custom_template.rtf"); + const args = ['-f', 'markdown', '-t', 'rtf', '-s', `--template=${templateFile}`]; + const pandocResult = (0, child_process_1.spawnSync)('pandoc', args, { input: notes, encoding: 'utf-8' }); + if (!pandocResult.error) { + result = pandocResult.stdout.replace(/fs28/g, 'fs24') + .replace(/fs32/g, 'fs28') + .replace(/fs36/g, 'fs32'); + result = ` finaleplugin.RTFNotes = [[${result}]]`; + } + } + return result; +}; /***/ }), @@ -6942,12 +7033,16 @@ exports.resolveRequiredFile = resolveRequiredFile; Object.defineProperty(exports, "__esModule", ({ value: true })); exports.removeComments = void 0; -const removeComments = (contents) => { - return contents +const removeComments = (contents, trimWhitespace) => { + let result = contents .replace(/--\[\[[\s\S]*?\]\]/giu, '') - .replace(/(? { + +"use strict"; +module.exports = require("child_process"); + +/***/ }), + /***/ 2057: /***/ ((module) => { diff --git a/.github/actions/bundle/package.json b/.github/actions/bundle/package.json index fb829091..d26d622a 100644 --- a/.github/actions/bundle/package.json +++ b/.github/actions/bundle/package.json @@ -10,7 +10,8 @@ "test": "jest", "test:watch": "npm run test -- --watch", "build": "ncc build src/index.ts -o dist --target=es2015", - "dev": "npm run build && NODE_ENV=development node dist/index.js" + "dev": "npm run build && NODE_ENV=development node dist/index.js", + "postbuild": "copyfiles -u 1 src/custom_template.rtf dist" }, "jest": { "collectCoverage": true, @@ -33,6 +34,7 @@ "@types/n-readlines": "^1.0.2", "@types/node": "^16.4.3", "@vercel/ncc": "^0.34.0", + "copyfiles": "^2.4.1", "esbuild": "^0.14.49", "esbuild-jest": "^0.5.0", "eslint": "^7.31.0", @@ -42,6 +44,7 @@ }, "dependencies": { "@actions/core": "^1.4.0", + "dedent-js": "^1.0.1", "fs-extra": "^10.0.0", "luabundle": "^1.6.0", "luamin": "^1.0.4", diff --git a/.github/actions/bundle/pnpm-lock.yaml b/.github/actions/bundle/pnpm-lock.yaml index 58a0ed40..cf83486c 100644 --- a/.github/actions/bundle/pnpm-lock.yaml +++ b/.github/actions/bundle/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: '@actions/core': specifier: ^1.4.0 version: 1.9.1 + dedent-js: + specifier: ^1.0.1 + version: 1.0.1 fs-extra: specifier: ^10.0.0 version: 10.0.0 @@ -40,6 +43,9 @@ devDependencies: '@vercel/ncc': specifier: ^0.34.0 version: 0.34.0 + copyfiles: + specifier: ^2.4.1 + version: 2.4.1 esbuild: specifier: ^0.14.49 version: 0.14.49 @@ -1710,7 +1716,7 @@ packages: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 - strip-ansi: 6.0.0 + strip-ansi: 6.0.1 wrap-ansi: 7.0.0 dev: true @@ -1775,11 +1781,28 @@ packages: engines: {node: '>=0.10.0'} dev: true + /copyfiles@2.4.1: + resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==} + hasBin: true + dependencies: + glob: 7.1.7 + minimatch: 3.0.4 + mkdirp: 1.0.4 + noms: 0.0.0 + through2: 2.0.5 + untildify: 4.0.0 + yargs: 16.2.0 + dev: true + /core-js-pure@3.15.2: resolution: {integrity: sha512-D42L7RYh1J2grW8ttxoY1+17Y4wXZeKe7uyplAI3FkNQyI5OgBIAjUfFiTPfL1rs0qLpxaabITNbjKl1Sp82tA==} requiresBuild: true dev: true + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: true + /cross-spawn@6.0.5: resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} engines: {node: '>=4.8'} @@ -1843,6 +1866,10 @@ packages: engines: {node: '>=0.10'} dev: true + /dedent-js@1.0.1: + resolution: {integrity: sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==} + dev: false + /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true @@ -2870,7 +2897,7 @@ packages: dev: false /fs.realpath@1.0.0: - resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true /fsevents@2.3.2: @@ -3080,7 +3107,7 @@ packages: dev: true /inflight@1.0.6: - resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: once: 1.4.0 wrappy: 1.0.2 @@ -3296,8 +3323,12 @@ packages: engines: {node: '>=0.10.0'} dev: true + /isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + dev: true + /isarray@1.0.0: - resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=} + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true /isexe@2.0.0: @@ -4134,6 +4165,12 @@ packages: is-extendable: 1.0.1 dev: true + /mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + dev: true + /moonsharp-luaparse@0.2.4: resolution: {integrity: sha512-1bkZPHRVJhCzt681KnFxWB5fYQlmcOZuBAlzdcsdTSjDfL//sqjcH2L2bbbgxf/l5s1krpGL8Ba7twT7mdj91Q==} hasBin: true @@ -4200,6 +4237,13 @@ packages: resolution: {integrity: sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg==} dev: true + /noms@0.0.0: + resolution: {integrity: sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==} + dependencies: + inherits: 2.0.4 + readable-stream: 1.0.34 + dev: true + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -4311,7 +4355,7 @@ packages: dev: true /once@1.4.0: - resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 dev: true @@ -4419,7 +4463,7 @@ packages: dev: true /path-is-absolute@1.0.1: - resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} dev: true @@ -4540,6 +4584,10 @@ packages: react-is: 18.2.0 dev: true + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: true + /progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -4625,6 +4673,27 @@ packages: type-fest: 0.6.0 dev: true + /readable-stream@1.0.34: + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + dev: true + + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: true + /regenerator-runtime@0.13.9: resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} dev: true @@ -4991,7 +5060,7 @@ packages: engines: {node: '>=10'} dependencies: char-regex: 1.0.2 - strip-ansi: 6.0.0 + strip-ansi: 6.0.1 dev: true /string-width@4.2.2: @@ -5039,6 +5108,16 @@ packages: define-properties: 1.1.3 dev: true + /string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + dev: true + + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: true + /strip-ansi@6.0.0: resolution: {integrity: sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==} engines: {node: '>=8'} @@ -5149,6 +5228,13 @@ packages: resolution: {integrity: sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==} dev: true + /through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + dev: true + /tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: true @@ -5292,6 +5378,11 @@ packages: isobject: 3.0.1 dev: true + /untildify@4.0.0: + resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} + engines: {node: '>=8'} + dev: true + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -5308,6 +5399,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: true + /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -5379,7 +5474,7 @@ packages: dev: true /wrappy@1.0.2: - resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true /write-file-atomic@3.0.3: @@ -5399,6 +5494,11 @@ packages: signal-exit: 3.0.7 dev: true + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: true + /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -5408,11 +5508,29 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: true + /yargs-parser@21.0.1: resolution: {integrity: sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==} engines: {node: '>=12'} dev: true + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + dev: true + /yargs@17.5.1: resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==} engines: {node: '>=12'} diff --git a/.github/actions/bundle/src/bundle.ts b/.github/actions/bundle/src/bundle.ts index e593555b..eb1d21fa 100644 --- a/.github/actions/bundle/src/bundle.ts +++ b/.github/actions/bundle/src/bundle.ts @@ -1,6 +1,6 @@ import fs from 'fs' import path from 'path' -import { getAllImports } from './helpers' +import { getAllImports, getFileParts } from './helpers' import { resolveRequiredFile } from './lua-require' import { removeComments } from './remove-comments' import { wrapImport } from './wrap-import' @@ -63,9 +63,12 @@ export const bundleFileBase = ( } export const bundleFile = (name: string, sourcePath: string, mixins: string[]): string => { - return removeComments( - bundleFileBase(name, files, mixins, (fileName: string) => + const bundled: string = bundleFileBase(name, files, mixins, (fileName: string) => fs.readFileSync(path.join(sourcePath, fileName)).toString() - ) - ) + ); + const parts = getFileParts(bundled); + + return removeComments(parts.prolog, true) + + removeComments(parts.plugindef, false) + + removeComments(parts.epilog, true); } diff --git a/.github/actions/bundle/src/custom_template.rtf b/.github/actions/bundle/src/custom_template.rtf new file mode 100644 index 00000000..a33e4911 --- /dev/null +++ b/.github/actions/bundle/src/custom_template.rtf @@ -0,0 +1,9 @@ + + {\rtf1\ansi\deff0{\fonttbl{\f0 \fswiss Helvetica;}{\f1 \fmodern Courier New;}} + {\colortbl;\red255\green0\blue0;\red0\green0\blue255;} + \widowctrl\hyphauto + \f0\fs20 + \f1\fs20 + $body$ + } + \ No newline at end of file diff --git a/.github/actions/bundle/src/helpers.test.ts b/.github/actions/bundle/src/helpers.test.ts index 9436ed3a..abb85aa5 100644 --- a/.github/actions/bundle/src/helpers.test.ts +++ b/.github/actions/bundle/src/helpers.test.ts @@ -1,4 +1,4 @@ -import { getAllImports, getImport } from './helpers' +import { getAllImports, getFileParts, getImport } from './helpers' describe('detects valid imports', () => { const lines: [string, string][] = [ @@ -44,3 +44,115 @@ describe('checks if file imports anything', () => { expect(getAllImports(file)).toEqual(imports) }) }) + +describe('splits file into parts', () => { + it('should split a file with all three parts', () => { + const contents = `-- some comment at the top of the file +function random_function() +end + +function plugindef() + finaleplugin.RequireSelection = true +end + +function another_function() +end + +another_function()`; + + const expected = { + prolog: `-- some comment at the top of the file +function random_function() +end + +`, + plugindef: `function plugindef() + finaleplugin.RequireSelection = true +end`, + epilog: ` + +function another_function() +end + +another_function()` + }; + + expect(getFileParts(contents)).toEqual(expected); + }); + + it('should split a file with no prolog', () => { + const contents = `function plugindef() + finaleplugin.RequireSelection = true +end + +function another_function() +end + +another_function()`; + + const expected = { + prolog: '', + plugindef: `function plugindef() + finaleplugin.RequireSelection = true +end`, + epilog: ` + +function another_function() +end + +another_function()` + }; + + expect(getFileParts(contents)).toEqual(expected); + }); + + it('should split a file with no plugindef', () => { + const contents = `-- some comment at the top of the file +function random_function() +end + +function another_function() +end + +another_function()`; + + const expected = { + prolog: '', + plugindef: '', + epilog: `-- some comment at the top of the file +function random_function() +end + +function another_function() +end + +another_function()` + }; + + expect(getFileParts(contents)).toEqual(expected); + }); + + it('should split a file with no epilog', () => { + const contents = `-- some comment at the top of the file +function random_function() +end + +function plugindef() + finaleplugin.RequireSelection = true +end`; + + const expected = { + prolog: `-- some comment at the top of the file +function random_function() +end + +`, + plugindef: `function plugindef() + finaleplugin.RequireSelection = true +end`, + epilog: '' + }; + + expect(getFileParts(contents)).toEqual(expected); + }); +}); diff --git a/.github/actions/bundle/src/helpers.ts b/.github/actions/bundle/src/helpers.ts index 59ea54cf..98d23c40 100644 --- a/.github/actions/bundle/src/helpers.ts +++ b/.github/actions/bundle/src/helpers.ts @@ -38,3 +38,17 @@ export const getAllImports = (file: string): string[] => { } return [...imports] } + +const plugindefRegex = /(?^function\s+plugindef.*?^end)/ms; + +export const getFileParts = (contents: string): { prolog: string, plugindef: string, epilog: string } => { + + const plugindefMatch = contents.match(plugindefRegex); + if (plugindefMatch?.index !== undefined) { + const prolog = contents.slice(0, plugindefMatch.index); + const epilog = contents.slice(plugindefMatch.index + plugindefMatch[0].length); + return { prolog, plugindef: plugindefMatch[0], epilog }; + } else { + return { prolog: '', plugindef: '', epilog: contents }; + } +} diff --git a/.github/actions/bundle/src/inject-extras.test.ts b/.github/actions/bundle/src/inject-extras.test.ts index 7bd42527..059063f2 100644 --- a/.github/actions/bundle/src/inject-extras.test.ts +++ b/.github/actions/bundle/src/inject-extras.test.ts @@ -1,11 +1,22 @@ import { injectExtras } from './inject-extras'; +/** + * Note: These tests don't pass on Windows, not entirely sure why. + * But there are other tests in this package that don't pass on Windows either! + * + * Also note that pandoc output is slightly different between Windows and Linux. + * On Win, Headings include an \outlinelevel tag; this doesn't seem to be important. + */ + describe('injectExtras', () => { - it('should inject the HashURL string into the contents', () => { + it('should inject HashURL and RTFNotes into the contents (with return)', () => { const name = 'test.lua'; const contents = ` function plugindef() -- plugin definition goes here + finaleplugin.Notes = [[ + # Header 1 + ]] return "1", "2", "3" end @@ -18,6 +29,18 @@ return true const expected = ` function plugindef() -- plugin definition goes here + finaleplugin.Notes = [[ + # Header 1 + ]] + finaleplugin.RTFNotes = [[ + {\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 \\fswiss Helvetica;}{\\f1 \\fmodern Courier New;}} + {\\colortbl;\\red255\\green0\\blue0;\\red0\\green0\\blue255;} + \\widowctrl\\hyphauto + \\f0\\fs20 + \\f1\\fs20 + {\\pard \\ql \\f0 \\sa180 \\li0 \\fi0 \\b \\fs32 Header 1\\par} + } + ]] finaleplugin.HashURL = "https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/test.hash" return "1", "2", "3" end @@ -34,10 +57,46 @@ return true expect(result).toEqual(expected); }); - it('should inject the HashURL string into the contents', () => { + it('should inject only HashURL into the contents (with return)', () => { const name = 'test.lua'; const contents = ` function plugindef() + -- plugin definition goes here + return "1", "2", "3" +end + +function testFunction() + -- function definition goes here +end + +return true +`; + const expected = ` +function plugindef() + -- plugin definition goes here + finaleplugin.HashURL = "https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/test.hash" + return "1", "2", "3" +end + +function testFunction() + -- function definition goes here +end + +return true +`; + + const result = injectExtras(name, contents); + + expect(result).toEqual(expected); + }); + + it('should inject HashURL and RTFNotes into the contents (no return)', () => { + const name = 'test.lua'; + const contents = ` +function plugindef() + finaleplugin.Notes = [[ + # Header 1 + ]] finaleplugin.RequireSelection = true end @@ -49,7 +108,19 @@ return true `; const expected = ` function plugindef() + finaleplugin.Notes = [[ + # Header 1 + ]] finaleplugin.RequireSelection = true + finaleplugin.RTFNotes = [[ + {\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 \\fswiss Helvetica;}{\\f1 \\fmodern Courier New;}} + {\\colortbl;\\red255\\green0\\blue0;\\red0\\green0\\blue255;} + \\widowctrl\\hyphauto + \\f0\\fs20 + \\f1\\fs20 + {\\pard \\ql \\f0 \\sa180 \\li0 \\fi0 \\b \\fs32 Header 1\\par} + } + ]] finaleplugin.HashURL = "https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/test.hash" end @@ -65,7 +136,7 @@ return true expect(result).toEqual(expected); }); - it('should inject the HashURL string into the contents', () => { + it('should inject HashURL and RTFNotes into the contents (more properties)', () => { const name = 'test.lua'; const contents = ` function plugindef() @@ -90,6 +161,122 @@ function plugindef() finaleplugin.Notes = [[ This is a description of the plugin. ]] + finaleplugin.RTFNotes = [[ + {\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 \\fswiss Helvetica;}{\\f1 \\fmodern Courier New;}} + {\\colortbl;\\red255\\green0\\blue0;\\red0\\green0\\blue255;} + \\widowctrl\\hyphauto + \\f0\\fs20 + \\f1\\fs20 + {\\pard \\ql \\f0 \\sa180 \\li0 \\fi0 This is a description of the plugin.\\par} + } + ]] + finaleplugin.HashURL = "https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/test.hash" + return "1", "2", "3" +end + +function testFunction() + -- function definition goes here +end + +return true +`; + + const result = injectExtras(name, contents); + + expect(result).toEqual(expected); + }); + + it('should should handle complex Markdown', () => { + const name = 'test.lua'; + const contents = ` +function plugindef() + finaleplugin.RequireSelection = true + -- other options + finaleplugin.Notes = [[ + # Heading one + + ## Heading two + + ### Heading three + + This is a regular paragraph with **Bold** and *Italic*. + + - Bullet 1 + - Bullet 2 + - Bullet 3 + + 1. Number 1 + 2. Number 2 + 3. Number 3 + + This is a paragraph with \`inline code\`. + + \`\`\` + function foo() { + do_something() + } + \`\`\` + ]] + return "1", "2", "3" +end + +function testFunction() + -- function definition goes here +end + +return true +`; + const expected = ` +function plugindef() + finaleplugin.RequireSelection = true + -- other options + finaleplugin.Notes = [[ + # Heading one + + ## Heading two + + ### Heading three + + This is a regular paragraph with **Bold** and *Italic*. + + - Bullet 1 + - Bullet 2 + - Bullet 3 + + 1. Number 1 + 2. Number 2 + 3. Number 3 + + This is a paragraph with \`inline code\`. + + \`\`\` + function foo() { + do_something() + } + \`\`\` + ]] + finaleplugin.RTFNotes = [[ + {\\rtf1\\ansi\\deff0{\\fonttbl{\\f0 \\fswiss Helvetica;}{\\f1 \\fmodern Courier New;}} + {\\colortbl;\\red255\\green0\\blue0;\\red0\\green0\\blue255;} + \\widowctrl\\hyphauto + \\f0\\fs20 + \\f1\\fs20 + {\\pard \\ql \\f0 \\sa180 \\li0 \\fi0 \\b \\fs32 Heading one\\par} + {\\pard \\ql \\f0 \\sa180 \\li0 \\fi0 \\b \\fs28 Heading two\\par} + {\\pard \\ql \\f0 \\sa180 \\li0 \\fi0 \\b \\fs24 Heading three\\par} + {\\pard \\ql \\f0 \\sa180 \\li0 \\fi0 This is a regular paragraph with {\\b Bold} and {\\i Italic}.\\par} + {\\pard \\ql \\f0 \\sa0 \\li360 \\fi-360 \\bullet \\tx360\\tab Bullet 1\\par} + {\\pard \\ql \\f0 \\sa0 \\li360 \\fi-360 \\bullet \\tx360\\tab Bullet 2\\par} + {\\pard \\ql \\f0 \\sa0 \\li360 \\fi-360 \\bullet \\tx360\\tab Bullet 3\\sa180\\par} + {\\pard \\ql \\f0 \\sa0 \\li360 \\fi-360 1.\\tx360\\tab Number 1\\par} + {\\pard \\ql \\f0 \\sa0 \\li360 \\fi-360 2.\\tx360\\tab Number 2\\par} + {\\pard \\ql \\f0 \\sa0 \\li360 \\fi-360 3.\\tx360\\tab Number 3\\sa180\\par} + {\\pard \\ql \\f0 \\sa180 \\li0 \\fi0 This is a paragraph with {\\f1 inline code}.\\par} + {\\pard \\ql \\f0 \\sa180 \\li0 \\fi0 \\f1 function foo() \\{\\line + do_something()\\line + \\}\\par} + } + ]] finaleplugin.HashURL = "https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/test.hash" return "1", "2", "3" end diff --git a/.github/actions/bundle/src/inject-extras.ts b/.github/actions/bundle/src/inject-extras.ts index 91e8e340..797779d7 100644 --- a/.github/actions/bundle/src/inject-extras.ts +++ b/.github/actions/bundle/src/inject-extras.ts @@ -1,28 +1,61 @@ +import { getFileParts } from './helpers' +import { spawnSync } from 'child_process'; +import dedent from 'dedent-js'; +import path from 'path'; + export const injectExtras = (name: string, contents: string): string => { - const functionRegex = /^(\s*)function\s+plugindef/gm; - const returnRegex = /^(\s*)return/gm; - const endRegex = /^(\s*)*end/gm; + const parts = getFileParts(contents); - const functionMatch = functionRegex.exec(contents); - if (functionMatch) { - const functionIndex = functionMatch.index; + if (parts.plugindef) { + if (!parts.plugindef.includes('finaleplugin.RTFNotes')) + parts.plugindef = inject(parts.plugindef, getRTFNotes(parts.plugindef)); + parts.plugindef = inject(parts.plugindef, getHashURL(name)); + } + + return parts.prolog + parts.plugindef + parts.epilog; +} - const returnMatch = returnRegex.exec(contents.substring(functionIndex)); - const endMatch = endRegex.exec(contents.substring(functionIndex)); +const inject = (contents: string, injection: string): string => { + if (injection) { + const returnRegex = /^(\s*)return/gm; + const endRegex = /^(\s*)*end/gm; + + const returnMatch = returnRegex.exec(contents); + const endMatch = endRegex.exec(contents); const index = Math.min( returnMatch ? returnMatch.index : Infinity, endMatch ? endMatch.index : Infinity ); + return contents.slice(0, index) + injection + '\n' + contents.slice(index); + } else { + return contents; + } +} - const strippedName = name.split('.').slice(0, -1).join(''); - - const injection = ` finaleplugin.HashURL = \"https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/${strippedName}.hash\"`; - - const injectedContents = contents.slice(0, functionIndex + index) + injection + '\n' + contents.slice(functionIndex + index); +const getHashURL = (name: string): string => { + const strippedName = name.split('.').slice(0, -1).join(''); + return ` finaleplugin.HashURL = "https://raw.githubusercontent.com/finale-lua/lua-scripts/master/hash/${strippedName}.hash"`; +} - return injectedContents; +const getRTFNotes = (input: string): string => { + let result = ''; + + const notesRegex = /(?<=finaleplugin.Notes = \[\[).*(?=\]\])/ius; + const match = input.match(notesRegex); + + if (match) { + const notes = dedent(match[0]); + const templateFile = path.join(__dirname, "custom_template.rtf"); + const args = [ '-f', 'markdown', '-t', 'rtf', '-s', `--template=${templateFile}` ]; + const pandocResult = spawnSync('pandoc', args, { input: notes, encoding: 'utf-8'}); + if (!pandocResult.error) { + result = pandocResult.stdout.replace(/fs28/g, 'fs24') + .replace(/fs32/g, 'fs28') + .replace(/fs36/g, 'fs32'); + result = ` finaleplugin.RTFNotes = [[${result}]]`; + } } - return contents; -} + return result; +} \ No newline at end of file diff --git a/.github/actions/bundle/src/lua-require.test.ts b/.github/actions/bundle/src/lua-require.test.ts index 0c94bce3..faa501ec 100644 --- a/.github/actions/bundle/src/lua-require.test.ts +++ b/.github/actions/bundle/src/lua-require.test.ts @@ -1,4 +1,5 @@ import { resolveRequiredFile } from './lua-require' +import path from 'path' describe('resolveRequiredFile', () => { const tests: [string, string][] = [ @@ -12,6 +13,7 @@ describe('resolveRequiredFile', () => { ['hello.', 'hello.lua'], ] it.each(tests)('%p resolves to %p', (name, file) => { + file = file.replace(/\//g, path.sep) expect(resolveRequiredFile(name)).toBe(file) }) }) diff --git a/.github/actions/bundle/src/remove-comments.test.ts b/.github/actions/bundle/src/remove-comments.test.ts index 485d5c42..404ee4fb 100644 --- a/.github/actions/bundle/src/remove-comments.test.ts +++ b/.github/actions/bundle/src/remove-comments.test.ts @@ -145,5 +145,18 @@ Uses \`FCArticulation.CalcMetricPos\` to determine if the input articulation is ] it.each(tests)(`removeComments(%p)`, (input, expected) => { - expect(removeComments(input)).toBe(expected) + expect(removeComments(input, true)).toBe(expected) }) + +const dontTrimWhitespaceTests: [string, string][] = [ + [`\n\n`, `\n\n`], + [`\n\n\n`, `\n\n\n`], + [`\n\n\n\n`, `\n\n\n\n`], + [`\n\n\n\n\n`, `\n\n\n\n\n`], + [`--[[\nhello\nworld\n]]`, ``], + [`--[[\n--hello\nworld\n]]`, ``], +] + +it.each(dontTrimWhitespaceTests)(`removeComments(%p)`, (input, expected) => { + expect(removeComments(input, false)).toBe(expected) +}) \ No newline at end of file diff --git a/.github/actions/bundle/src/remove-comments.ts b/.github/actions/bundle/src/remove-comments.ts index dd6924f7..16718d6d 100644 --- a/.github/actions/bundle/src/remove-comments.ts +++ b/.github/actions/bundle/src/remove-comments.ts @@ -1,7 +1,11 @@ -export const removeComments = (contents: string): string => { - return contents +export const removeComments = (contents: string, trimWhitespace: boolean): string => { + let result = contents .replace(/--\[\[[\s\S]*?\]\]/giu, '') - .replace(/(?