From b7b1e95eb8b0fdf8ed1b84075747bb23f3148712 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 1 Mar 2024 15:25:40 +0000 Subject: [PATCH 01/59] Add working PoC of snippets for both polyfilled vm and isolated-vm --- packages/server/package.json | 3 +- packages/server/src/api/controllers/script.ts | 2 +- packages/server/src/jsRunner/bundles/index.ts | 2 ++ .../jsRunner/bundles/snippets.ivm.bundle.js | 3 ++ .../server/src/jsRunner/bundles/snippets.ts | 18 ++++++++++++ packages/server/src/jsRunner/index.ts | 6 ++-- .../src/jsRunner/tests/isolatedVM.spec.ts | 2 +- packages/server/src/jsRunner/utilities.ts | 3 -- .../server/src/jsRunner/vm/isolated-vm.ts | 14 ++++++++- packages/server/src/threads/query.ts | 2 +- packages/string-templates/package.json | 3 +- .../src/helpers/javascript.js | 29 ++++++++++++++++++- .../string-templates/src/helpers/snippet.js | 1 + packages/string-templates/src/iife.js | 3 ++ packages/string-templates/src/index.js | 2 ++ 15 files changed, 81 insertions(+), 12 deletions(-) create mode 100644 packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js create mode 100644 packages/server/src/jsRunner/bundles/snippets.ts delete mode 100644 packages/server/src/jsRunner/utilities.ts create mode 100644 packages/string-templates/src/helpers/snippet.js create mode 100644 packages/string-templates/src/iife.js diff --git a/packages/server/package.json b/packages/server/package.json index 45980a4be6..572e735335 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -13,9 +13,10 @@ "build": "node ./scripts/build.js", "postbuild": "copyfiles -f ../client/dist/budibase-client.js ../client/manifest.json client && copyfiles -f ../../yarn.lock ./dist/", "check:types": "tsc -p tsconfig.json --noEmit --paths null", + "build:isolated-vm-lib:snippets": "esbuild --minify --bundle src/jsRunner/bundles/snippets.ts --outfile=src/jsRunner/bundles/snippets.ivm.bundle.js --platform=node --format=iife --global-name=snippets", "build:isolated-vm-lib:string-templates": "esbuild --minify --bundle src/jsRunner/bundles/index-helpers.ts --outfile=src/jsRunner/bundles/index-helpers.ivm.bundle.js --platform=node --format=iife --external:handlebars --global-name=helpers", "build:isolated-vm-lib:bson": "esbuild --minify --bundle src/jsRunner/bundles/bsonPackage.ts --outfile=src/jsRunner/bundles/bson.ivm.bundle.js --platform=node --format=iife --global-name=bson", - "build:isolated-vm-libs": "yarn build:isolated-vm-lib:string-templates && yarn build:isolated-vm-lib:bson", + "build:isolated-vm-libs": "yarn build:isolated-vm-lib:string-templates && yarn build:isolated-vm-lib:bson && yarn build:isolated-vm-lib:snippets", "build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput", "debug": "yarn build && node --expose-gc --inspect=9222 dist/index.js", "jest": "NODE_OPTIONS=\"--no-node-snapshot $NODE_OPTIONS\" jest", diff --git a/packages/server/src/api/controllers/script.ts b/packages/server/src/api/controllers/script.ts index b69fc430a6..4317f5fcde 100644 --- a/packages/server/src/api/controllers/script.ts +++ b/packages/server/src/api/controllers/script.ts @@ -1,6 +1,6 @@ import { Ctx } from "@budibase/types" import { IsolatedVM } from "../../jsRunner/vm" -import { iifeWrapper } from "../../jsRunner/utilities" +import { iifeWrapper } from "@budibase/string-templates" export async function execute(ctx: Ctx) { const { script, context } = ctx.request.body diff --git a/packages/server/src/jsRunner/bundles/index.ts b/packages/server/src/jsRunner/bundles/index.ts index 9e2960807a..f7685206a6 100644 --- a/packages/server/src/jsRunner/bundles/index.ts +++ b/packages/server/src/jsRunner/bundles/index.ts @@ -5,11 +5,13 @@ import fs from "fs" export const enum BundleType { HELPERS = "helpers", BSON = "bson", + SNIPPETS = "snippets", } const bundleSourceFile: Record = { [BundleType.HELPERS]: "./index-helpers.ivm.bundle.js", [BundleType.BSON]: "./bson.ivm.bundle.js", + [BundleType.SNIPPETS]: "./snippets.ivm.bundle.js", } const bundleSourceCode: Partial> = {} diff --git a/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js new file mode 100644 index 0000000000..e5bc2df6c6 --- /dev/null +++ b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js @@ -0,0 +1,3 @@ +"use strict";var snippets=(()=>{var s=Object.create;var p=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var c=Object.getOwnPropertyNames;var l=Object.getPrototypeOf,m=Object.prototype.hasOwnProperty;var W=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports),d=(e,r)=>{for(var n in r)p(e,n,{get:r[n],enumerable:!0})},o=(e,r,n,i)=>{if(r&&typeof r=="object"||typeof r=="function")for(let t of c(r))!m.call(e,t)&&t!==n&&p(e,t,{get:()=>r[t],enumerable:!(i=x(r,t))||i.enumerable});return e};var g=(e,r,n)=>(n=e!=null?s(l(e)):{},o(r||!e||!e.__esModule?p(n,"default",{value:e,enumerable:!0}):n,e)),v=e=>o(p({},"__esModule",{value:!0}),e);var u=W((_,f)=>{f.exports.iifeWrapper=e=>`(function(){ +${e} +})();`});var y={};d(y,{default:()=>w});var a=g(u()),w=new Proxy({},{get:function(e,r){return[eval][0]((0,a.iifeWrapper)($("snippets")[r]))}});return v(y);})(); diff --git a/packages/server/src/jsRunner/bundles/snippets.ts b/packages/server/src/jsRunner/bundles/snippets.ts new file mode 100644 index 0000000000..d45fe56ec0 --- /dev/null +++ b/packages/server/src/jsRunner/bundles/snippets.ts @@ -0,0 +1,18 @@ +// @ts-ignore +// eslint-disable-next-line local-rules/no-budibase-imports +import { iifeWrapper } from "@budibase/string-templates/iife" + +export default new Proxy( + {}, + { + get: function (_, name) { + // Get snippet definitions from global context, get the correct snippet + // then eval the JS. + // https://esbuild.github.io/content-types/#direct-eval for info on why + // eval is being called this way. + // @ts-ignore + // eslint-disable-next-line no-undef + return [eval][0](iifeWrapper($("snippets")[name])) + }, + } +) diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index 0c9f5d9f01..67aaffae7f 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -21,13 +21,15 @@ export function init() { memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS, - }).withHelpers() + }) + .withHelpers() + .withSnippets() if (bbCtx) { // If we have a context, we want to persist it to reuse the isolate bbCtx.vm = vm } - const { helpers, ...rest } = ctx + const { helpers, snippets, ...rest } = ctx return vm.withContext(rest, () => vm.execute(js)) } catch (error: any) { if (error.message === "Script execution timed out.") { diff --git a/packages/server/src/jsRunner/tests/isolatedVM.spec.ts b/packages/server/src/jsRunner/tests/isolatedVM.spec.ts index 5296598ef1..5a9bc05d76 100644 --- a/packages/server/src/jsRunner/tests/isolatedVM.spec.ts +++ b/packages/server/src/jsRunner/tests/isolatedVM.spec.ts @@ -1,7 +1,7 @@ import fs from "fs" import path from "path" import { IsolatedVM } from "../vm" -import { iifeWrapper } from "../utilities" +import { iifeWrapper } from "@budibase/string-templates" function runJSWithIsolatedVM(script: string, context: Record) { const runner = new IsolatedVM() diff --git a/packages/server/src/jsRunner/utilities.ts b/packages/server/src/jsRunner/utilities.ts deleted file mode 100644 index fa398ec239..0000000000 --- a/packages/server/src/jsRunner/utilities.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function iifeWrapper(script: string) { - return `(function(){\n${script}\n})();` -} diff --git a/packages/server/src/jsRunner/vm/isolated-vm.ts b/packages/server/src/jsRunner/vm/isolated-vm.ts index b0692f0fd1..fb45abf5df 100644 --- a/packages/server/src/jsRunner/vm/isolated-vm.ts +++ b/packages/server/src/jsRunner/vm/isolated-vm.ts @@ -7,7 +7,7 @@ import querystring from "querystring" import { BundleType, loadBundle } from "../bundles" import { VM } from "@budibase/types" -import { iifeWrapper } from "../utilities" +import { iifeWrapper } from "@budibase/string-templates" import environment from "../../environment" class ExecutionTimeoutError extends Error { @@ -98,6 +98,18 @@ export class IsolatedVM implements VM { return this } + withSnippets() { + const snippetsSource = loadBundle(BundleType.SNIPPETS) + const script = this.isolate.compileScriptSync( + `${snippetsSource};snippets=snippets.default;` + ) + script.runSync(this.vm, { timeout: this.invocationTimeout, release: false }) + new Promise(() => { + script.release() + }) + return this + } + withContext(context: Record, executeWithContext: () => T) { this.addToContext(context) diff --git a/packages/server/src/threads/query.ts b/packages/server/src/threads/query.ts index 9d7b7062a5..40caa6dfd8 100644 --- a/packages/server/src/threads/query.ts +++ b/packages/server/src/threads/query.ts @@ -8,7 +8,7 @@ import { QueryResponse, } from "./definitions" import { IsolatedVM } from "../jsRunner/vm" -import { iifeWrapper } from "../jsRunner/utilities" +import { iifeWrapper } from "@budibase/string-templates" import { getIntegration } from "../integrations" import { processStringSync } from "@budibase/string-templates" import { context, cache, auth } from "@budibase/backend-core" diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index ceafd5364f..340d74ef8a 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -12,7 +12,8 @@ "import": "./dist/bundle.mjs" }, "./package.json": "./package.json", - "./test/utils": "./test/utils.js" + "./test/utils": "./test/utils.js", + "./iife": "./src/iife.js" }, "files": [ "dist", diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index 7827736812..65c70cc330 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -2,6 +2,8 @@ const { atob, isBackendService, isJSAllowed } = require("../utilities") const cloneDeep = require("lodash.clonedeep") const { LITERAL_MARKER } = require("../helpers/constants") const { getJsHelperList } = require("./list") +const { iifeWrapper } = require("../iife") +const { CrazyLongSnippet } = require("./snippet") // The method of executing JS scripts depends on the bundle being built. // This setter is used in the entrypoint (either index.js or index.mjs). @@ -40,15 +42,30 @@ const getContextValue = (path, context) => { return data } +const snippets = { + Square: ` + return function(num) { + return num * num + } + `, + HelloWorld: ` + return "Hello, world!" + `, + CrazyLongSnippet: atob(CrazyLongSnippet), +} + // Evaluates JS code against a certain context module.exports.processJS = (handlebars, context) => { + // for testing + context.snippets = snippets + if (!isJSAllowed() || (isBackendService() && !runJS)) { throw new Error("JS disabled in environment.") } try { // Wrap JS in a function and immediately invoke it. // This is required to allow the final `return` statement to be valid. - const js = `(function(){${atob(handlebars)}})();` + const js = iifeWrapper(atob(handlebars)) // Our $ context function gets a value from context. // We clone the context to avoid mutation in the binding affecting real @@ -56,6 +73,16 @@ module.exports.processJS = (handlebars, context) => { const sandboxContext = { $: path => getContextValue(path, cloneDeep(context)), helpers: getJsHelperList(), + + // Proxy to evaluate snippets when running in the browser + snippets: new Proxy( + {}, + { + get: function (_, name) { + return eval(iifeWrapper(context.snippets[name])) + }, + } + ), } // Create a sandbox with our context and run the JS diff --git a/packages/string-templates/src/helpers/snippet.js b/packages/string-templates/src/helpers/snippet.js new file mode 100644 index 0000000000..950019a69f --- /dev/null +++ b/packages/string-templates/src/helpers/snippet.js @@ -0,0 +1 @@ +module.exports.CrazyLongSnippet = `LyoqCiAqIG1hcmtlZCAtIGEgbWFya2Rvd24gcGFyc2VyCiAqIENvcHlyaWdodCAoYykgMjAxMS0yMDIyLCBDaHJpc3RvcGhlciBKZWZmcmV5LiAoTUlUIExpY2Vuc2VkKQogKiBodHRwczovL2dpdGh1Yi5jb20vbWFya2VkanMvbWFya2VkCiAqLwoKLyoqCiAqIERPIE5PVCBFRElUIFRISVMgRklMRQogKiBUaGUgY29kZSBpbiB0aGlzIGZpbGUgaXMgZ2VuZXJhdGVkIGZyb20gZmlsZXMgaW4gLi9zcmMvCiAqLwoKZnVuY3Rpb24gZ2V0RGVmYXVsdHMoKSB7CiAgcmV0dXJuIHsKICAgIGJhc2VVcmw6IG51bGwsCiAgICBicmVha3M6IGZhbHNlLAogICAgZXh0ZW5zaW9uczogbnVsbCwKICAgIGdmbTogdHJ1ZSwKICAgIGhlYWRlcklkczogdHJ1ZSwKICAgIGhlYWRlclByZWZpeDogIiIsCiAgICBoaWdobGlnaHQ6IG51bGwsCiAgICBsYW5nUHJlZml4OiAibGFuZ3VhZ2UtIiwKICAgIG1hbmdsZTogdHJ1ZSwKICAgIHBlZGFudGljOiBmYWxzZSwKICAgIHJlbmRlcmVyOiBudWxsLAogICAgc2FuaXRpemU6IGZhbHNlLAogICAgc2FuaXRpemVyOiBudWxsLAogICAgc2lsZW50OiBmYWxzZSwKICAgIHNtYXJ0TGlzdHM6IGZhbHNlLAogICAgc21hcnR5cGFudHM6IGZhbHNlLAogICAgdG9rZW5pemVyOiBudWxsLAogICAgd2Fsa1Rva2VuczogbnVsbCwKICAgIHhodG1sOiBmYWxzZSwKICB9Cn0KCmxldCBkZWZhdWx0cyA9IGdldERlZmF1bHRzKCkKCmZ1bmN0aW9uIGNoYW5nZURlZmF1bHRzKG5ld0RlZmF1bHRzKSB7CiAgZGVmYXVsdHMgPSBuZXdEZWZhdWx0cwp9CgovKioKICogSGVscGVycwogKi8KY29uc3QgZXNjYXBlVGVzdCA9IC9bJjw+IiddLwpjb25zdCBlc2NhcGVSZXBsYWNlID0gL1smPD4iJ10vZwpjb25zdCBlc2NhcGVUZXN0Tm9FbmNvZGUgPSAvWzw+IiddfCYoPyEjP1x3KzspLwpjb25zdCBlc2NhcGVSZXBsYWNlTm9FbmNvZGUgPSAvWzw+IiddfCYoPyEjP1x3KzspL2cKY29uc3QgZXNjYXBlUmVwbGFjZW1lbnRzID0gewogICImIjogIiZhbXA7IiwKICAiPCI6ICImbHQ7IiwKICAiPiI6ICImZ3Q7IiwKICAnIic6ICImcXVvdDsiLAogICInIjogIiYjMzk7IiwKfQpjb25zdCBnZXRFc2NhcGVSZXBsYWNlbWVudCA9IGNoID0+IGVzY2FwZVJlcGxhY2VtZW50c1tjaF0KZnVuY3Rpb24gZXNjYXBlKGh0bWwsIGVuY29kZSkgewogIGlmIChlbmNvZGUpIHsKICAgIGlmIChlc2NhcGVUZXN0LnRlc3QoaHRtbCkpIHsKICAgICAgcmV0dXJuIGh0bWwucmVwbGFjZShlc2NhcGVSZXBsYWNlLCBnZXRFc2NhcGVSZXBsYWNlbWVudCkKICAgIH0KICB9IGVsc2UgewogICAgaWYgKGVzY2FwZVRlc3ROb0VuY29kZS50ZXN0KGh0bWwpKSB7CiAgICAgIHJldHVybiBodG1sLnJlcGxhY2UoZXNjYXBlUmVwbGFjZU5vRW5jb2RlLCBnZXRFc2NhcGVSZXBsYWNlbWVudCkKICAgIH0KICB9CgogIHJldHVybiBodG1sCn0KCmNvbnN0IHVuZXNjYXBlVGVzdCA9IC8mKCMoPzpcZCspfCg/OiN4WzAtOUEtRmEtZl0rKXwoPzpcdyspKTs/L2dpCgovKioKICogQHBhcmFtIHtzdHJpbmd9IGh0bWwKICovCmZ1bmN0aW9uIHVuZXNjYXBlKGh0bWwpIHsKICAvLyBleHBsaWNpdGx5IG1hdGNoIGRlY2ltYWwsIGhleCwgYW5kIG5hbWVkIEhUTUwgZW50aXRpZXMKICByZXR1cm4gaHRtbC5yZXBsYWNlKHVuZXNjYXBlVGVzdCwgKF8sIG4pID0+IHsKICAgIG4gPSBuLnRvTG93ZXJDYXNlKCkKICAgIGlmIChuID09PSAiY29sb24iKSByZXR1cm4gIjoiCiAgICBpZiAobi5jaGFyQXQoMCkgPT09ICIjIikgewogICAgICByZXR1cm4gbi5jaGFyQXQoMSkgPT09ICJ4IgogICAgICAgID8gU3RyaW5nLmZyb21DaGFyQ29kZShwYXJzZUludChuLnN1YnN0cmluZygyKSwgMTYpKQogICAgICAgIDogU3RyaW5nLmZyb21DaGFyQ29kZSgrbi5zdWJzdHJpbmcoMSkpCiAgICB9CiAgICByZXR1cm4gIiIKICB9KQp9Cgpjb25zdCBjYXJldCA9IC8oXnxbXlxbXSlcXi9nCgovKioKICogQHBhcmFtIHtzdHJpbmcgfCBSZWdFeHB9IHJlZ2V4CiAqIEBwYXJhbSB7c3RyaW5nfSBvcHQKICovCmZ1bmN0aW9uIGVkaXQocmVnZXgsIG9wdCkgewogIHJlZ2V4ID0gdHlwZW9mIHJlZ2V4ID09PSAic3RyaW5nIiA/IHJlZ2V4IDogcmVnZXguc291cmNlCiAgb3B0ID0gb3B0IHx8ICIiCiAgY29uc3Qgb2JqID0gewogICAgcmVwbGFjZTogKG5hbWUsIHZhbCkgPT4gewogICAgICB2YWwgPSB2YWwuc291cmNlIHx8IHZhbAogICAgICB2YWwgPSB2YWwucmVwbGFjZShjYXJldCwgIiQxIikKICAgICAgcmVnZXggPSByZWdleC5yZXBsYWNlKG5hbWUsIHZhbCkKICAgICAgcmV0dXJuIG9iagogICAgfSwKICAgIGdldFJlZ2V4OiAoKSA9PiB7CiAgICAgIHJldHVybiBuZXcgUmVnRXhwKHJlZ2V4LCBvcHQpCiAgICB9LAogIH0KICByZXR1cm4gb2JqCn0KCmNvbnN0IG5vbldvcmRBbmRDb2xvblRlc3QgPSAvW15cdzpdL2cKY29uc3Qgb3JpZ2luSW5kZXBlbmRlbnRVcmwgPSAvXiR8XlthLXpdW2EtejAtOSsuLV0qOnxeWz8jXS9pCgovKioKICogQHBhcmFtIHtib29sZWFufSBzYW5pdGl6ZQogKiBAcGFyYW0ge3N0cmluZ30gYmFzZQogKiBAcGFyYW0ge3N0cmluZ30gaHJlZgogKi8KZnVuY3Rpb24gY2xlYW5Vcmwoc2FuaXRpemUsIGJhc2UsIGhyZWYpIHsKICBpZiAoc2FuaXRpemUpIHsKICAgIGxldCBwcm90CiAgICB0cnkgewogICAgICBwcm90ID0gZGVjb2RlVVJJQ29tcG9uZW50KHVuZXNjYXBlKGhyZWYpKQogICAgICAgIC5yZXBsYWNlKG5vbldvcmRBbmRDb2xvblRlc3QsICIiKQogICAgICAgIC50b0xvd2VyQ2FzZSgpCiAgICB9IGNhdGNoIChlKSB7CiAgICAgIHJldHVybiBudWxsCiAgICB9CiAgICBpZiAoCiAgICAgIHByb3QuaW5kZXhPZigiamF2YXNjcmlwdDoiKSA9PT0gMCB8fAogICAgICBwcm90LmluZGV4T2YoInZic2NyaXB0OiIpID09PSAwIHx8CiAgICAgIHByb3QuaW5kZXhPZigiZGF0YToiKSA9PT0gMAogICAgKSB7CiAgICAgIHJldHVybiBudWxsCiAgICB9CiAgfQogIGlmIChiYXNlICYmICFvcmlnaW5JbmRlcGVuZGVudFVybC50ZXN0KGhyZWYpKSB7CiAgICBocmVmID0gcmVzb2x2ZVVybChiYXNlLCBocmVmKQogIH0KICB0cnkgewogICAgaHJlZiA9IGVuY29kZVVSSShocmVmKS5yZXBsYWNlKC8lMjUvZywgIiUiKQogIH0gY2F0Y2ggKGUpIHsKICAgIHJldHVybiBudWxsCiAgfQogIHJldHVybiBocmVmCn0KCmNvbnN0IGJhc2VVcmxzID0ge30KY29uc3QganVzdERvbWFpbiA9IC9eW146XSs6XC8qW14vXSokLwpjb25zdCBwcm90b2NvbCA9IC9eKFteOl0rOilbXHNcU10qJC8KY29uc3QgZG9tYWluID0gL14oW146XSs6XC8qW14vXSopW1xzXFNdKiQvCgovKioKICogQHBhcmFtIHtzdHJpbmd9IGJhc2UKICogQHBhcmFtIHtzdHJpbmd9IGhyZWYKICovCmZ1bmN0aW9uIHJlc29sdmVVcmwoYmFzZSwgaHJlZikgewogIGlmICghYmFzZVVybHNbIiAiICsgYmFzZV0pIHsKICAgIC8vIHdlIGNhbiBpZ25vcmUgZXZlcnl0aGluZyBpbiBiYXNlIGFmdGVyIHRoZSBsYXN0IHNsYXNoIG9mIGl0cyBwYXRoIGNvbXBvbmVudCwKICAgIC8vIGJ1dCB3ZSBtaWdodCBuZWVkIHRvIGFkZCBfdGhhdF8KICAgIC8vIGh0dHBzOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmMzOTg2I3NlY3Rpb24tMwogICAgaWYgKGp1c3REb21haW4udGVzdChiYXNlKSkgewogICAgICBiYXNlVXJsc1siICIgKyBiYXNlXSA9IGJhc2UgKyAiLyIKICAgIH0gZWxzZSB7CiAgICAgIGJhc2VVcmxzWyIgIiArIGJhc2VdID0gcnRyaW0oYmFzZSwgIi8iLCB0cnVlKQogICAgfQogIH0KICBiYXNlID0gYmFzZVVybHNbIiAiICsgYmFzZV0KICBjb25zdCByZWxhdGl2ZUJhc2UgPSBiYXNlLmluZGV4T2YoIjoiKSA9PT0gLTEKCiAgaWYgKGhyZWYuc3Vic3RyaW5nKDAsIDIpID09PSAiLy8iKSB7CiAgICBpZiAocmVsYXRpdmVCYXNlKSB7CiAgICAgIHJldHVybiBocmVmCiAgICB9CiAgICByZXR1cm4gYmFzZS5yZXBsYWNlKHByb3RvY29sLCAiJDEiKSArIGhyZWYKICB9IGVsc2UgaWYgKGhyZWYuY2hhckF0KDApID09PSAiLyIpIHsKICAgIGlmIChyZWxhdGl2ZUJhc2UpIHsKICAgICAgcmV0dXJuIGhyZWYKICAgIH0KICAgIHJldHVybiBiYXNlLnJlcGxhY2UoZG9tYWluLCAiJDEiKSArIGhyZWYKICB9IGVsc2UgewogICAgcmV0dXJuIGJhc2UgKyBocmVmCiAgfQp9Cgpjb25zdCBub29wVGVzdCA9IHsgZXhlYzogZnVuY3Rpb24gbm9vcFRlc3QoKSB7fSB9CgpmdW5jdGlvbiBtZXJnZShvYmopIHsKICBsZXQgaSA9IDEsCiAgICB0YXJnZXQsCiAgICBrZXkKCiAgZm9yICg7IGkgPCBhcmd1bWVudHMubGVuZ3RoOyBpKyspIHsKICAgIHRhcmdldCA9IGFyZ3VtZW50c1tpXQogICAgZm9yIChrZXkgaW4gdGFyZ2V0KSB7CiAgICAgIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwodGFyZ2V0LCBrZXkpKSB7CiAgICAgICAgb2JqW2tleV0gPSB0YXJnZXRba2V5XQogICAgICB9CiAgICB9CiAgfQoKICByZXR1cm4gb2JqCn0KCmZ1bmN0aW9uIHNwbGl0Q2VsbHModGFibGVSb3csIGNvdW50KSB7CiAgLy8gZW5zdXJlIHRoYXQgZXZlcnkgY2VsbC1kZWxpbWl0aW5nIHBpcGUgaGFzIGEgc3BhY2UKICAvLyBiZWZvcmUgaXQgdG8gZGlzdGluZ3Vpc2ggaXQgZnJvbSBhbiBlc2NhcGVkIHBpcGUKICBjb25zdCByb3cgPSB0YWJsZVJvdy5yZXBsYWNlKC9cfC9nLCAobWF0Y2gsIG9mZnNldCwgc3RyKSA9PiB7CiAgICAgIGxldCBlc2NhcGVkID0gZmFsc2UsCiAgICAgICAgY3VyciA9IG9mZnNldAogICAgICB3aGlsZSAoLS1jdXJyID49IDAgJiYgc3RyW2N1cnJdID09PSAiXFwiKSBlc2NhcGVkID0gIWVzY2FwZWQKICAgICAgaWYgKGVzY2FwZWQpIHsKICAgICAgICAvLyBvZGQgbnVtYmVyIG9mIHNsYXNoZXMgbWVhbnMgfCBpcyBlc2NhcGVkCiAgICAgICAgLy8gc28gd2UgbGVhdmUgaXQgYWxvbmUKICAgICAgICByZXR1cm4gInwiCiAgICAgIH0gZWxzZSB7CiAgICAgICAgLy8gYWRkIHNwYWNlIGJlZm9yZSB1bmVzY2FwZWQgfAogICAgICAgIHJldHVybiAiIHwiCiAgICAgIH0KICAgIH0pLAogICAgY2VsbHMgPSByb3cuc3BsaXQoLyBcfC8pCiAgbGV0IGkgPSAwCgogIC8vIEZpcnN0L2xhc3QgY2VsbCBpbiBhIHJvdyBjYW5ub3QgYmUgZW1wdHkgaWYgaXQgaGFzIG5vIGxlYWRpbmcvdHJhaWxpbmcgcGlwZQogIGlmICghY2VsbHNbMF0udHJpbSgpKSB7CiAgICBjZWxscy5zaGlmdCgpCiAgfQogIGlmIChjZWxscy5sZW5ndGggPiAwICYmICFjZWxsc1tjZWxscy5sZW5ndGggLSAxXS50cmltKCkpIHsKICAgIGNlbGxzLnBvcCgpCiAgfQoKICBpZiAoY2VsbHMubGVuZ3RoID4gY291bnQpIHsKICAgIGNlbGxzLnNwbGljZShjb3VudCkKICB9IGVsc2UgewogICAgd2hpbGUgKGNlbGxzLmxlbmd0aCA8IGNvdW50KSBjZWxscy5wdXNoKCIiKQogIH0KCiAgZm9yICg7IGkgPCBjZWxscy5sZW5ndGg7IGkrKykgewogICAgLy8gbGVhZGluZyBvciB0cmFpbGluZyB3aGl0ZXNwYWNlIGlzIGlnbm9yZWQgcGVyIHRoZSBnZm0gc3BlYwogICAgY2VsbHNbaV0gPSBjZWxsc1tpXS50cmltKCkucmVwbGFjZSgvXFxcfC9nLCAifCIpCiAgfQogIHJldHVybiBjZWxscwp9CgovKioKICogUmVtb3ZlIHRyYWlsaW5nICdjJ3MuIEVxdWl2YWxlbnQgdG8gc3RyLnJlcGxhY2UoL2MqJC8sICcnKS4KICogL2MqJC8gaXMgdnVsbmVyYWJsZSB0byBSRURPUy4KICoKICogQHBhcmFtIHtzdHJpbmd9IHN0cgogKiBAcGFyYW0ge3N0cmluZ30gYwogKiBAcGFyYW0ge2Jvb2xlYW59IGludmVydCBSZW1vdmUgc3VmZml4IG9mIG5vbi1jIGNoYXJzIGluc3RlYWQuIERlZmF1bHQgZmFsc2V5LgogKi8KZnVuY3Rpb24gcnRyaW0oc3RyLCBjLCBpbnZlcnQpIHsKICBjb25zdCBsID0gc3RyLmxlbmd0aAogIGlmIChsID09PSAwKSB7CiAgICByZXR1cm4gIiIKICB9CgogIC8vIExlbmd0aCBvZiBzdWZmaXggbWF0Y2hpbmcgdGhlIGludmVydCBjb25kaXRpb24uCiAgbGV0IHN1ZmZMZW4gPSAwCgogIC8vIFN0ZXAgbGVmdCB1bnRpbCB3ZSBmYWlsIHRvIG1hdGNoIHRoZSBpbnZlcnQgY29uZGl0aW9uLgogIHdoaWxlIChzdWZmTGVuIDwgbCkgewogICAgY29uc3QgY3VyckNoYXIgPSBzdHIuY2hhckF0KGwgLSBzdWZmTGVuIC0gMSkKICAgIGlmIChjdXJyQ2hhciA9PT0gYyAmJiAhaW52ZXJ0KSB7CiAgICAgIHN1ZmZMZW4rKwogICAgfSBlbHNlIGlmIChjdXJyQ2hhciAhPT0gYyAmJiBpbnZlcnQpIHsKICAgICAgc3VmZkxlbisrCiAgICB9IGVsc2UgewogICAgICBicmVhawogICAgfQogIH0KCiAgcmV0dXJuIHN0ci5zbGljZSgwLCBsIC0gc3VmZkxlbikKfQoKZnVuY3Rpb24gZmluZENsb3NpbmdCcmFja2V0KHN0ciwgYikgewogIGlmIChzdHIuaW5kZXhPZihiWzFdKSA9PT0gLTEpIHsKICAgIHJldHVybiAtMQogIH0KICBjb25zdCBsID0gc3RyLmxlbmd0aAogIGxldCBsZXZlbCA9IDAsCiAgICBpID0gMAogIGZvciAoOyBpIDwgbDsgaSsrKSB7CiAgICBpZiAoc3RyW2ldID09PSAiXFwiKSB7CiAgICAgIGkrKwogICAgfSBlbHNlIGlmIChzdHJbaV0gPT09IGJbMF0pIHsKICAgICAgbGV2ZWwrKwogICAgfSBlbHNlIGlmIChzdHJbaV0gPT09IGJbMV0pIHsKICAgICAgbGV2ZWwtLQogICAgICBpZiAobGV2ZWwgPCAwKSB7CiAgICAgICAgcmV0dXJuIGkKICAgICAgfQogICAgfQogIH0KICByZXR1cm4gLTEKfQoKZnVuY3Rpb24gY2hlY2tTYW5pdGl6ZURlcHJlY2F0aW9uKG9wdCkgewogIGlmIChvcHQgJiYgb3B0LnNhbml0aXplICYmICFvcHQuc2lsZW50KSB7CiAgICBjb25zb2xlLndhcm4oCiAgICAgICJtYXJrZWQoKTogc2FuaXRpemUgYW5kIHNhbml0aXplciBwYXJhbWV0ZXJzIGFyZSBkZXByZWNhdGVkIHNpbmNlIHZlcnNpb24gMC43LjAsIHNob3VsZCBub3QgYmUgdXNlZCBhbmQgd2lsbCBiZSByZW1vdmVkIGluIHRoZSBmdXR1cmUuIFJlYWQgbW9yZSBoZXJlOiBodHRwczovL21hcmtlZC5qcy5vcmcvIy9VU0lOR19BRFZBTkNFRC5tZCNvcHRpb25zIgogICAgKQogIH0KfQoKLy8gY29waWVkIGZyb20gaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9hLzU0NTAxMTMvODA2Nzc3Ci8qKgogKiBAcGFyYW0ge3N0cmluZ30gcGF0dGVybgogKiBAcGFyYW0ge251bWJlcn0gY291bnQKICovCmZ1bmN0aW9uIHJlcGVhdFN0cmluZyhwYXR0ZXJuLCBjb3VudCkgewogIGlmIChjb3VudCA8IDEpIHsKICAgIHJldHVybiAiIgogIH0KICBsZXQgcmVzdWx0ID0gIiIKICB3aGlsZSAoY291bnQgPiAxKSB7CiAgICBpZiAoY291bnQgJiAxKSB7CiAgICAgIHJlc3VsdCArPSBwYXR0ZXJuCiAgICB9CiAgICBjb3VudCA+Pj0gMQogICAgcGF0dGVybiArPSBwYXR0ZXJuCiAgfQogIHJldHVybiByZXN1bHQgKyBwYXR0ZXJuCn0KCmZ1bmN0aW9uIG91dHB1dExpbmsoY2FwLCBsaW5rLCByYXcsIGxleGVyKSB7CiAgY29uc3QgaHJlZiA9IGxpbmsuaHJlZgogIGNvbnN0IHRpdGxlID0gbGluay50aXRsZSA/IGVzY2FwZShsaW5rLnRpdGxlKSA6IG51bGwKICBjb25zdCB0ZXh0ID0gY2FwWzFdLnJlcGxhY2UoL1xcKFtcW1xdXSkvZywgIiQxIikKCiAgaWYgKGNhcFswXS5jaGFyQXQoMCkgIT09ICIhIikgewogICAgbGV4ZXIuc3RhdGUuaW5MaW5rID0gdHJ1ZQogICAgY29uc3QgdG9rZW4gPSB7CiAgICAgIHR5cGU6ICJsaW5rIiwKICAgICAgcmF3LAogICAgICBocmVmLAogICAgICB0aXRsZSwKICAgICAgdGV4dCwKICAgICAgdG9rZW5zOiBsZXhlci5pbmxpbmVUb2tlbnModGV4dCwgW10pLAogICAgfQogICAgbGV4ZXIuc3RhdGUuaW5MaW5rID0gZmFsc2UKICAgIHJldHVybiB0b2tlbgogIH0KICByZXR1cm4gewogICAgdHlwZTogImltYWdlIiwKICAgIHJhdywKICAgIGhyZWYsCiAgICB0aXRsZSwKICAgIHRleHQ6IGVzY2FwZSh0ZXh0KSwKICB9Cn0KCmZ1bmN0aW9uIGluZGVudENvZGVDb21wZW5zYXRpb24ocmF3LCB0ZXh0KSB7CiAgY29uc3QgbWF0Y2hJbmRlbnRUb0NvZGUgPSByYXcubWF0Y2goL14oXHMrKSg/OmBgYCkvKQoKICBpZiAobWF0Y2hJbmRlbnRUb0NvZGUgPT09IG51bGwpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICBjb25zdCBpbmRlbnRUb0NvZGUgPSBtYXRjaEluZGVudFRvQ29kZVsxXQoKICByZXR1cm4gdGV4dAogICAgLnNwbGl0KCJcbiIpCiAgICAubWFwKG5vZGUgPT4gewogICAgICBjb25zdCBtYXRjaEluZGVudEluTm9kZSA9IG5vZGUubWF0Y2goL15ccysvKQogICAgICBpZiAobWF0Y2hJbmRlbnRJbk5vZGUgPT09IG51bGwpIHsKICAgICAgICByZXR1cm4gbm9kZQogICAgICB9CgogICAgICBjb25zdCBbaW5kZW50SW5Ob2RlXSA9IG1hdGNoSW5kZW50SW5Ob2RlCgogICAgICBpZiAoaW5kZW50SW5Ob2RlLmxlbmd0aCA+PSBpbmRlbnRUb0NvZGUubGVuZ3RoKSB7CiAgICAgICAgcmV0dXJuIG5vZGUuc2xpY2UoaW5kZW50VG9Db2RlLmxlbmd0aCkKICAgICAgfQoKICAgICAgcmV0dXJuIG5vZGUKICAgIH0pCiAgICAuam9pbigiXG4iKQp9CgovKioKICogVG9rZW5pemVyCiAqLwpjbGFzcyBUb2tlbml6ZXIgewogIGNvbnN0cnVjdG9yKG9wdGlvbnMpIHsKICAgIHRoaXMub3B0aW9ucyA9IG9wdGlvbnMgfHwgZGVmYXVsdHMKICB9CgogIHNwYWNlKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5uZXdsaW5lLmV4ZWMoc3JjKQogICAgaWYgKGNhcCAmJiBjYXBbMF0ubGVuZ3RoID4gMCkgewogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJzcGFjZSIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgIH0KICAgIH0KICB9CgogIGNvZGUoc3JjKSB7CiAgICBjb25zdCBjYXAgPSB0aGlzLnJ1bGVzLmJsb2NrLmNvZGUuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGNvbnN0IHRleHQgPSBjYXBbMF0ucmVwbGFjZSgvXiB7MSw0fS9nbSwgIiIpCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImNvZGUiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGNvZGVCbG9ja1N0eWxlOiAiaW5kZW50ZWQiLAogICAgICAgIHRleHQ6ICF0aGlzLm9wdGlvbnMucGVkYW50aWMgPyBydHJpbSh0ZXh0LCAiXG4iKSA6IHRleHQsCiAgICAgIH0KICAgIH0KICB9CgogIGZlbmNlcyhzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuYmxvY2suZmVuY2VzLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBjb25zdCByYXcgPSBjYXBbMF0KICAgICAgY29uc3QgdGV4dCA9IGluZGVudENvZGVDb21wZW5zYXRpb24ocmF3LCBjYXBbM10gfHwgIiIpCgogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJjb2RlIiwKICAgICAgICByYXcsCiAgICAgICAgbGFuZzogY2FwWzJdID8gY2FwWzJdLnRyaW0oKSA6IGNhcFsyXSwKICAgICAgICB0ZXh0LAogICAgICB9CiAgICB9CiAgfQoKICBoZWFkaW5nKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5oZWFkaW5nLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBsZXQgdGV4dCA9IGNhcFsyXS50cmltKCkKCiAgICAgIC8vIHJlbW92ZSB0cmFpbGluZyAjcwogICAgICBpZiAoLyMkLy50ZXN0KHRleHQpKSB7CiAgICAgICAgY29uc3QgdHJpbW1lZCA9IHJ0cmltKHRleHQsICIjIikKICAgICAgICBpZiAodGhpcy5vcHRpb25zLnBlZGFudGljKSB7CiAgICAgICAgICB0ZXh0ID0gdHJpbW1lZC50cmltKCkKICAgICAgICB9IGVsc2UgaWYgKCF0cmltbWVkIHx8IC8gJC8udGVzdCh0cmltbWVkKSkgewogICAgICAgICAgLy8gQ29tbW9uTWFyayByZXF1aXJlcyBzcGFjZSBiZWZvcmUgdHJhaWxpbmcgI3MKICAgICAgICAgIHRleHQgPSB0cmltbWVkLnRyaW0oKQogICAgICAgIH0KICAgICAgfQoKICAgICAgY29uc3QgdG9rZW4gPSB7CiAgICAgICAgdHlwZTogImhlYWRpbmciLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGRlcHRoOiBjYXBbMV0ubGVuZ3RoLAogICAgICAgIHRleHQsCiAgICAgICAgdG9rZW5zOiBbXSwKICAgICAgfQogICAgICB0aGlzLmxleGVyLmlubGluZSh0b2tlbi50ZXh0LCB0b2tlbi50b2tlbnMpCiAgICAgIHJldHVybiB0b2tlbgogICAgfQogIH0KCiAgaHIoc3JjKSB7CiAgICBjb25zdCBjYXAgPSB0aGlzLnJ1bGVzLmJsb2NrLmhyLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJociIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgIH0KICAgIH0KICB9CgogIGJsb2NrcXVvdGUoc3JjKSB7CiAgICBjb25zdCBjYXAgPSB0aGlzLnJ1bGVzLmJsb2NrLmJsb2NrcXVvdGUuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGNvbnN0IHRleHQgPSBjYXBbMF0ucmVwbGFjZSgvXiAqPlsgXHRdPy9nbSwgIiIpCgogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJibG9ja3F1b3RlIiwKICAgICAgICByYXc6IGNhcFswXSwKICAgICAgICB0b2tlbnM6IHRoaXMubGV4ZXIuYmxvY2tUb2tlbnModGV4dCwgW10pLAogICAgICAgIHRleHQsCiAgICAgIH0KICAgIH0KICB9CgogIGxpc3Qoc3JjKSB7CiAgICBsZXQgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5saXN0LmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBsZXQgcmF3LAogICAgICAgIGlzdGFzaywKICAgICAgICBpc2NoZWNrZWQsCiAgICAgICAgaW5kZW50LAogICAgICAgIGksCiAgICAgICAgYmxhbmtMaW5lLAogICAgICAgIGVuZHNXaXRoQmxhbmtMaW5lLAogICAgICAgIGxpbmUsCiAgICAgICAgbmV4dExpbmUsCiAgICAgICAgcmF3TGluZSwKICAgICAgICBpdGVtQ29udGVudHMsCiAgICAgICAgZW5kRWFybHkKCiAgICAgIGxldCBidWxsID0gY2FwWzFdLnRyaW0oKQogICAgICBjb25zdCBpc29yZGVyZWQgPSBidWxsLmxlbmd0aCA+IDEKCiAgICAgIGNvbnN0IGxpc3QgPSB7CiAgICAgICAgdHlwZTogImxpc3QiLAogICAgICAgIHJhdzogIiIsCiAgICAgICAgb3JkZXJlZDogaXNvcmRlcmVkLAogICAgICAgIHN0YXJ0OiBpc29yZGVyZWQgPyArYnVsbC5zbGljZSgwLCAtMSkgOiAiIiwKICAgICAgICBsb29zZTogZmFsc2UsCiAgICAgICAgaXRlbXM6IFtdLAogICAgICB9CgogICAgICBidWxsID0gaXNvcmRlcmVkID8gYFxcZHsxLDl9XFwke2J1bGwuc2xpY2UoLTEpfWAgOiBgXFwke2J1bGx9YAoKICAgICAgaWYgKHRoaXMub3B0aW9ucy5wZWRhbnRpYykgewogICAgICAgIGJ1bGwgPSBpc29yZGVyZWQgPyBidWxsIDogIlsqKy1dIgogICAgICB9CgogICAgICAvLyBHZXQgbmV4dCBsaXN0IGl0ZW0KICAgICAgY29uc3QgaXRlbVJlZ2V4ID0gbmV3IFJlZ0V4cCgKICAgICAgICBgXiggezAsM30ke2J1bGx9KSgoPzpbXHQgXVteXFxuXSopPyg/OlxcbnwkKSlgCiAgICAgICkKCiAgICAgIC8vIENoZWNrIGlmIGN1cnJlbnQgYnVsbGV0IHBvaW50IGNhbiBzdGFydCBhIG5ldyBMaXN0IEl0ZW0KICAgICAgd2hpbGUgKHNyYykgewogICAgICAgIGVuZEVhcmx5ID0gZmFsc2UKICAgICAgICBpZiAoIShjYXAgPSBpdGVtUmVnZXguZXhlYyhzcmMpKSkgewogICAgICAgICAgYnJlYWsKICAgICAgICB9CgogICAgICAgIGlmICh0aGlzLnJ1bGVzLmJsb2NrLmhyLnRlc3Qoc3JjKSkgewogICAgICAgICAgLy8gRW5kIGxpc3QgaWYgYnVsbGV0IHdhcyBhY3R1YWxseSBIUiAocG9zc2libHkgbW92ZSBpbnRvIGl0ZW1SZWdleD8pCiAgICAgICAgICBicmVhawogICAgICAgIH0KCiAgICAgICAgcmF3ID0gY2FwWzBdCiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyhyYXcubGVuZ3RoKQoKICAgICAgICBsaW5lID0gY2FwWzJdLnNwbGl0KCJcbiIsIDEpWzBdCiAgICAgICAgbmV4dExpbmUgPSBzcmMuc3BsaXQoIlxuIiwgMSlbMF0KCiAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5wZWRhbnRpYykgewogICAgICAgICAgaW5kZW50ID0gMgogICAgICAgICAgaXRlbUNvbnRlbnRzID0gbGluZS50cmltTGVmdCgpCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIGluZGVudCA9IGNhcFsyXS5zZWFyY2goL1teIF0vKSAvLyBGaW5kIGZpcnN0IG5vbi1zcGFjZSBjaGFyCiAgICAgICAgICBpbmRlbnQgPSBpbmRlbnQgPiA0ID8gMSA6IGluZGVudCAvLyBUcmVhdCBpbmRlbnRlZCBjb2RlIGJsb2NrcyAoPiA0IHNwYWNlcykgYXMgaGF2aW5nIG9ubHkgMSBpbmRlbnQKICAgICAgICAgIGl0ZW1Db250ZW50cyA9IGxpbmUuc2xpY2UoaW5kZW50KQogICAgICAgICAgaW5kZW50ICs9IGNhcFsxXS5sZW5ndGgKICAgICAgICB9CgogICAgICAgIGJsYW5rTGluZSA9IGZhbHNlCgogICAgICAgIGlmICghbGluZSAmJiAvXiAqJC8udGVzdChuZXh0TGluZSkpIHsKICAgICAgICAgIC8vIEl0ZW1zIGJlZ2luIHdpdGggYXQgbW9zdCBvbmUgYmxhbmsgbGluZQogICAgICAgICAgcmF3ICs9IG5leHRMaW5lICsgIlxuIgogICAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyhuZXh0TGluZS5sZW5ndGggKyAxKQogICAgICAgICAgZW5kRWFybHkgPSB0cnVlCiAgICAgICAgfQoKICAgICAgICBpZiAoIWVuZEVhcmx5KSB7CiAgICAgICAgICBjb25zdCBuZXh0QnVsbGV0UmVnZXggPSBuZXcgUmVnRXhwKAogICAgICAgICAgICBgXiB7MCwke01hdGgubWluKAogICAgICAgICAgICAgIDMsCiAgICAgICAgICAgICAgaW5kZW50IC0gMQogICAgICAgICAgICApfX0oPzpbKistXXxcXGR7MSw5fVsuKV0pKCg/OiBbXlxcbl0qKT8oPzpcXG58JCkpYAogICAgICAgICAgKQogICAgICAgICAgY29uc3QgaHJSZWdleCA9IG5ldyBSZWdFeHAoCiAgICAgICAgICAgIGBeIHswLCR7TWF0aC5taW4oCiAgICAgICAgICAgICAgMywKICAgICAgICAgICAgICBpbmRlbnQgLSAxCiAgICAgICAgICAgICl9fSgoPzotICopezMsfXwoPzpfICopezMsfXwoPzpcXCogKil7Myx9KSg/Olxcbit8JClgCiAgICAgICAgICApCgogICAgICAgICAgLy8gQ2hlY2sgaWYgZm9sbG93aW5nIGxpbmVzIHNob3VsZCBiZSBpbmNsdWRlZCBpbiBMaXN0IEl0ZW0KICAgICAgICAgIHdoaWxlIChzcmMpIHsKICAgICAgICAgICAgcmF3TGluZSA9IHNyYy5zcGxpdCgiXG4iLCAxKVswXQogICAgICAgICAgICBsaW5lID0gcmF3TGluZQoKICAgICAgICAgICAgLy8gUmUtYWxpZ24gdG8gZm9sbG93IGNvbW1vbm1hcmsgbmVzdGluZyBydWxlcwogICAgICAgICAgICBpZiAodGhpcy5vcHRpb25zLnBlZGFudGljKSB7CiAgICAgICAgICAgICAgbGluZSA9IGxpbmUucmVwbGFjZSgvXiB7MSw0fSg/PSggezR9KSpbXiBdKS9nLCAiICAiKQogICAgICAgICAgICB9CgogICAgICAgICAgICAvLyBFbmQgbGlzdCBpdGVtIGlmIGZvdW5kIHN0YXJ0IG9mIG5ldyBidWxsZXQKICAgICAgICAgICAgaWYgKG5leHRCdWxsZXRSZWdleC50ZXN0KGxpbmUpKSB7CiAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy8gSG9yaXpvbnRhbCBydWxlIGZvdW5kCiAgICAgICAgICAgIGlmIChoclJlZ2V4LnRlc3Qoc3JjKSkgewogICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGlmIChsaW5lLnNlYXJjaCgvW14gXS8pID49IGluZGVudCB8fCAhbGluZS50cmltKCkpIHsKICAgICAgICAgICAgICAvLyBEZWRlbnQgaWYgcG9zc2libGUKICAgICAgICAgICAgICBpdGVtQ29udGVudHMgKz0gIlxuIiArIGxpbmUuc2xpY2UoaW5kZW50KQogICAgICAgICAgICB9IGVsc2UgaWYgKCFibGFua0xpbmUpIHsKICAgICAgICAgICAgICAvLyBVbnRpbCBibGFuayBsaW5lLCBpdGVtIGRvZXNuJ3QgbmVlZCBpbmRlbnRhdGlvbgogICAgICAgICAgICAgIGl0ZW1Db250ZW50cyArPSAiXG4iICsgbGluZQogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgIC8vIE90aGVyd2lzZSwgaW1wcm9wZXIgaW5kZW50YXRpb24gZW5kcyB0aGlzIGl0ZW0KICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICB9CgogICAgICAgICAgICBpZiAoIWJsYW5rTGluZSAmJiAhbGluZS50cmltKCkpIHsKICAgICAgICAgICAgICAvLyBDaGVjayBpZiBjdXJyZW50IGxpbmUgaXMgYmxhbmsKICAgICAgICAgICAgICBibGFua0xpbmUgPSB0cnVlCiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIHJhdyArPSByYXdMaW5lICsgIlxuIgogICAgICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHJhd0xpbmUubGVuZ3RoICsgMSkKICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIGlmICghbGlzdC5sb29zZSkgewogICAgICAgICAgLy8gSWYgdGhlIHByZXZpb3VzIGl0ZW0gZW5kZWQgd2l0aCBhIGJsYW5rIGxpbmUsIHRoZSBsaXN0IGlzIGxvb3NlCiAgICAgICAgICBpZiAoZW5kc1dpdGhCbGFua0xpbmUpIHsKICAgICAgICAgICAgbGlzdC5sb29zZSA9IHRydWUKICAgICAgICAgIH0gZWxzZSBpZiAoL1xuICpcbiAqJC8udGVzdChyYXcpKSB7CiAgICAgICAgICAgIGVuZHNXaXRoQmxhbmtMaW5lID0gdHJ1ZQogICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgLy8gQ2hlY2sgZm9yIHRhc2sgbGlzdCBpdGVtcwogICAgICAgIGlmICh0aGlzLm9wdGlvbnMuZ2ZtKSB7CiAgICAgICAgICBpc3Rhc2sgPSAvXlxbWyB4WF1cXSAvLmV4ZWMoaXRlbUNvbnRlbnRzKQogICAgICAgICAgaWYgKGlzdGFzaykgewogICAgICAgICAgICBpc2NoZWNrZWQgPSBpc3Rhc2tbMF0gIT09ICJbIF0gIgogICAgICAgICAgICBpdGVtQ29udGVudHMgPSBpdGVtQ29udGVudHMucmVwbGFjZSgvXlxbWyB4WF1cXSArLywgIiIpCiAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBsaXN0Lml0ZW1zLnB1c2goewogICAgICAgICAgdHlwZTogImxpc3RfaXRlbSIsCiAgICAgICAgICByYXcsCiAgICAgICAgICB0YXNrOiAhIWlzdGFzaywKICAgICAgICAgIGNoZWNrZWQ6IGlzY2hlY2tlZCwKICAgICAgICAgIGxvb3NlOiBmYWxzZSwKICAgICAgICAgIHRleHQ6IGl0ZW1Db250ZW50cywKICAgICAgICB9KQoKICAgICAgICBsaXN0LnJhdyArPSByYXcKICAgICAgfQoKICAgICAgLy8gRG8gbm90IGNvbnN1bWUgbmV3bGluZXMgYXQgZW5kIG9mIGZpbmFsIGl0ZW0uIEFsdGVybmF0aXZlbHksIG1ha2UgaXRlbVJlZ2V4ICpzdGFydCogd2l0aCBhbnkgbmV3bGluZXMgdG8gc2ltcGxpZnkvc3BlZWQgdXAgZW5kc1dpdGhCbGFua0xpbmUgbG9naWMKICAgICAgbGlzdC5pdGVtc1tsaXN0Lml0ZW1zLmxlbmd0aCAtIDFdLnJhdyA9IHJhdy50cmltUmlnaHQoKQogICAgICBsaXN0Lml0ZW1zW2xpc3QuaXRlbXMubGVuZ3RoIC0gMV0udGV4dCA9IGl0ZW1Db250ZW50cy50cmltUmlnaHQoKQogICAgICBsaXN0LnJhdyA9IGxpc3QucmF3LnRyaW1SaWdodCgpCgogICAgICBjb25zdCBsID0gbGlzdC5pdGVtcy5sZW5ndGgKCiAgICAgIC8vIEl0ZW0gY2hpbGQgdG9rZW5zIGhhbmRsZWQgaGVyZSBhdCBlbmQgYmVjYXVzZSB3ZSBuZWVkZWQgdG8gaGF2ZSB0aGUgZmluYWwgaXRlbSB0byB0cmltIGl0IGZpcnN0CiAgICAgIGZvciAoaSA9IDA7IGkgPCBsOyBpKyspIHsKICAgICAgICB0aGlzLmxleGVyLnN0YXRlLnRvcCA9IGZhbHNlCiAgICAgICAgbGlzdC5pdGVtc1tpXS50b2tlbnMgPSB0aGlzLmxleGVyLmJsb2NrVG9rZW5zKGxpc3QuaXRlbXNbaV0udGV4dCwgW10pCiAgICAgICAgY29uc3Qgc3BhY2VycyA9IGxpc3QuaXRlbXNbaV0udG9rZW5zLmZpbHRlcih0ID0+IHQudHlwZSA9PT0gInNwYWNlIikKICAgICAgICBjb25zdCBoYXNNdWx0aXBsZUxpbmVCcmVha3MgPSBzcGFjZXJzLmV2ZXJ5KHQgPT4gewogICAgICAgICAgY29uc3QgY2hhcnMgPSB0LnJhdy5zcGxpdCgiIikKICAgICAgICAgIGxldCBsaW5lQnJlYWtzID0gMAogICAgICAgICAgZm9yIChjb25zdCBjaGFyIG9mIGNoYXJzKSB7CiAgICAgICAgICAgIGlmIChjaGFyID09PSAiXG4iKSB7CiAgICAgICAgICAgICAgbGluZUJyZWFrcyArPSAxCiAgICAgICAgICAgIH0KICAgICAgICAgICAgaWYgKGxpbmVCcmVha3MgPiAxKSB7CiAgICAgICAgICAgICAgcmV0dXJuIHRydWUKICAgICAgICAgICAgfQogICAgICAgICAgfQoKICAgICAgICAgIHJldHVybiBmYWxzZQogICAgICAgIH0pCgogICAgICAgIGlmICghbGlzdC5sb29zZSAmJiBzcGFjZXJzLmxlbmd0aCAmJiBoYXNNdWx0aXBsZUxpbmVCcmVha3MpIHsKICAgICAgICAgIC8vIEhhdmluZyBhIHNpbmdsZSBsaW5lIGJyZWFrIGRvZXNuJ3QgbWVhbiBhIGxpc3QgaXMgbG9vc2UuIEEgc2luZ2xlIGxpbmUgYnJlYWsgaXMgdGVybWluYXRpbmcgdGhlIGxhc3QgbGlzdCBpdGVtCiAgICAgICAgICBsaXN0Lmxvb3NlID0gdHJ1ZQogICAgICAgICAgbGlzdC5pdGVtc1tpXS5sb29zZSA9IHRydWUKICAgICAgICB9CiAgICAgIH0KCiAgICAgIHJldHVybiBsaXN0CiAgICB9CiAgfQoKICBodG1sKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5odG1sLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBjb25zdCB0b2tlbiA9IHsKICAgICAgICB0eXBlOiAiaHRtbCIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgICAgcHJlOgogICAgICAgICAgIXRoaXMub3B0aW9ucy5zYW5pdGl6ZXIgJiYKICAgICAgICAgIChjYXBbMV0gPT09ICJwcmUiIHx8IGNhcFsxXSA9PT0gInNjcmlwdCIgfHwgY2FwWzFdID09PSAic3R5bGUiKSwKICAgICAgICB0ZXh0OiBjYXBbMF0sCiAgICAgIH0KICAgICAgaWYgKHRoaXMub3B0aW9ucy5zYW5pdGl6ZSkgewogICAgICAgIHRva2VuLnR5cGUgPSAicGFyYWdyYXBoIgogICAgICAgIHRva2VuLnRleHQgPSB0aGlzLm9wdGlvbnMuc2FuaXRpemVyCiAgICAgICAgICA/IHRoaXMub3B0aW9ucy5zYW5pdGl6ZXIoY2FwWzBdKQogICAgICAgICAgOiBlc2NhcGUoY2FwWzBdKQogICAgICAgIHRva2VuLnRva2VucyA9IFtdCiAgICAgICAgdGhpcy5sZXhlci5pbmxpbmUodG9rZW4udGV4dCwgdG9rZW4udG9rZW5zKQogICAgICB9CiAgICAgIHJldHVybiB0b2tlbgogICAgfQogIH0KCiAgZGVmKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5kZWYuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGlmIChjYXBbM10pIGNhcFszXSA9IGNhcFszXS5zdWJzdHJpbmcoMSwgY2FwWzNdLmxlbmd0aCAtIDEpCiAgICAgIGNvbnN0IHRhZyA9IGNhcFsxXS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL1xzKy9nLCAiICIpCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImRlZiIsCiAgICAgICAgdGFnLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGhyZWY6IGNhcFsyXSwKICAgICAgICB0aXRsZTogY2FwWzNdLAogICAgICB9CiAgICB9CiAgfQoKICB0YWJsZShzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuYmxvY2sudGFibGUuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGNvbnN0IGl0ZW0gPSB7CiAgICAgICAgdHlwZTogInRhYmxlIiwKICAgICAgICBoZWFkZXI6IHNwbGl0Q2VsbHMoY2FwWzFdKS5tYXAoYyA9PiB7CiAgICAgICAgICByZXR1cm4geyB0ZXh0OiBjIH0KICAgICAgICB9KSwKICAgICAgICBhbGlnbjogY2FwWzJdLnJlcGxhY2UoL14gKnxcfCAqJC9nLCAiIikuc3BsaXQoLyAqXHwgKi8pLAogICAgICAgIHJvd3M6CiAgICAgICAgICBjYXBbM10gJiYgY2FwWzNdLnRyaW0oKQogICAgICAgICAgICA/IGNhcFszXS5yZXBsYWNlKC9cblsgXHRdKiQvLCAiIikuc3BsaXQoIlxuIikKICAgICAgICAgICAgOiBbXSwKICAgICAgfQoKICAgICAgaWYgKGl0ZW0uaGVhZGVyLmxlbmd0aCA9PT0gaXRlbS5hbGlnbi5sZW5ndGgpIHsKICAgICAgICBpdGVtLnJhdyA9IGNhcFswXQoKICAgICAgICBsZXQgbCA9IGl0ZW0uYWxpZ24ubGVuZ3RoCiAgICAgICAgbGV0IGksIGosIGssIHJvdwogICAgICAgIGZvciAoaSA9IDA7IGkgPCBsOyBpKyspIHsKICAgICAgICAgIGlmICgvXiAqLSs6ICokLy50ZXN0KGl0ZW0uYWxpZ25baV0pKSB7CiAgICAgICAgICAgIGl0ZW0uYWxpZ25baV0gPSAicmlnaHQiCiAgICAgICAgICB9IGVsc2UgaWYgKC9eICo6LSs6ICokLy50ZXN0KGl0ZW0uYWxpZ25baV0pKSB7CiAgICAgICAgICAgIGl0ZW0uYWxpZ25baV0gPSAiY2VudGVyIgogICAgICAgICAgfSBlbHNlIGlmICgvXiAqOi0rICokLy50ZXN0KGl0ZW0uYWxpZ25baV0pKSB7CiAgICAgICAgICAgIGl0ZW0uYWxpZ25baV0gPSAibGVmdCIKICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGl0ZW0uYWxpZ25baV0gPSBudWxsCiAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBsID0gaXRlbS5yb3dzLmxlbmd0aAogICAgICAgIGZvciAoaSA9IDA7IGkgPCBsOyBpKyspIHsKICAgICAgICAgIGl0ZW0ucm93c1tpXSA9IHNwbGl0Q2VsbHMoaXRlbS5yb3dzW2ldLCBpdGVtLmhlYWRlci5sZW5ndGgpLm1hcChjID0+IHsKICAgICAgICAgICAgcmV0dXJuIHsgdGV4dDogYyB9CiAgICAgICAgICB9KQogICAgICAgIH0KCiAgICAgICAgLy8gcGFyc2UgY2hpbGQgdG9rZW5zIGluc2lkZSBoZWFkZXJzIGFuZCBjZWxscwoKICAgICAgICAvLyBoZWFkZXIgY2hpbGQgdG9rZW5zCiAgICAgICAgbCA9IGl0ZW0uaGVhZGVyLmxlbmd0aAogICAgICAgIGZvciAoaiA9IDA7IGogPCBsOyBqKyspIHsKICAgICAgICAgIGl0ZW0uaGVhZGVyW2pdLnRva2VucyA9IFtdCiAgICAgICAgICB0aGlzLmxleGVyLmlubGluZShpdGVtLmhlYWRlcltqXS50ZXh0LCBpdGVtLmhlYWRlcltqXS50b2tlbnMpCiAgICAgICAgfQoKICAgICAgICAvLyBjZWxsIGNoaWxkIHRva2VucwogICAgICAgIGwgPSBpdGVtLnJvd3MubGVuZ3RoCiAgICAgICAgZm9yIChqID0gMDsgaiA8IGw7IGorKykgewogICAgICAgICAgcm93ID0gaXRlbS5yb3dzW2pdCiAgICAgICAgICBmb3IgKGsgPSAwOyBrIDwgcm93Lmxlbmd0aDsgaysrKSB7CiAgICAgICAgICAgIHJvd1trXS50b2tlbnMgPSBbXQogICAgICAgICAgICB0aGlzLmxleGVyLmlubGluZShyb3dba10udGV4dCwgcm93W2tdLnRva2VucykKICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIHJldHVybiBpdGVtCiAgICAgIH0KICAgIH0KICB9CgogIGxoZWFkaW5nKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5saGVhZGluZy5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgY29uc3QgdG9rZW4gPSB7CiAgICAgICAgdHlwZTogImhlYWRpbmciLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGRlcHRoOiBjYXBbMl0uY2hhckF0KDApID09PSAiPSIgPyAxIDogMiwKICAgICAgICB0ZXh0OiBjYXBbMV0sCiAgICAgICAgdG9rZW5zOiBbXSwKICAgICAgfQogICAgICB0aGlzLmxleGVyLmlubGluZSh0b2tlbi50ZXh0LCB0b2tlbi50b2tlbnMpCiAgICAgIHJldHVybiB0b2tlbgogICAgfQogIH0KCiAgcGFyYWdyYXBoKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5wYXJhZ3JhcGguZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGNvbnN0IHRva2VuID0gewogICAgICAgIHR5cGU6ICJwYXJhZ3JhcGgiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIHRleHQ6CiAgICAgICAgICBjYXBbMV0uY2hhckF0KGNhcFsxXS5sZW5ndGggLSAxKSA9PT0gIlxuIgogICAgICAgICAgICA/IGNhcFsxXS5zbGljZSgwLCAtMSkKICAgICAgICAgICAgOiBjYXBbMV0sCiAgICAgICAgdG9rZW5zOiBbXSwKICAgICAgfQogICAgICB0aGlzLmxleGVyLmlubGluZSh0b2tlbi50ZXh0LCB0b2tlbi50b2tlbnMpCiAgICAgIHJldHVybiB0b2tlbgogICAgfQogIH0KCiAgdGV4dChzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuYmxvY2sudGV4dC5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgY29uc3QgdG9rZW4gPSB7CiAgICAgICAgdHlwZTogInRleHQiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIHRleHQ6IGNhcFswXSwKICAgICAgICB0b2tlbnM6IFtdLAogICAgICB9CiAgICAgIHRoaXMubGV4ZXIuaW5saW5lKHRva2VuLnRleHQsIHRva2VuLnRva2VucykKICAgICAgcmV0dXJuIHRva2VuCiAgICB9CiAgfQoKICBlc2NhcGUoc3JjKSB7CiAgICBjb25zdCBjYXAgPSB0aGlzLnJ1bGVzLmlubGluZS5lc2NhcGUuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImVzY2FwZSIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgICAgdGV4dDogZXNjYXBlKGNhcFsxXSksCiAgICAgIH0KICAgIH0KICB9CgogIHRhZyhzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLnRhZy5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgaWYgKCF0aGlzLmxleGVyLnN0YXRlLmluTGluayAmJiAvXjxhIC9pLnRlc3QoY2FwWzBdKSkgewogICAgICAgIHRoaXMubGV4ZXIuc3RhdGUuaW5MaW5rID0gdHJ1ZQogICAgICB9IGVsc2UgaWYgKHRoaXMubGV4ZXIuc3RhdGUuaW5MaW5rICYmIC9ePFwvYT4vaS50ZXN0KGNhcFswXSkpIHsKICAgICAgICB0aGlzLmxleGVyLnN0YXRlLmluTGluayA9IGZhbHNlCiAgICAgIH0KICAgICAgaWYgKAogICAgICAgICF0aGlzLmxleGVyLnN0YXRlLmluUmF3QmxvY2sgJiYKICAgICAgICAvXjwocHJlfGNvZGV8a2JkfHNjcmlwdCkoXHN8PikvaS50ZXN0KGNhcFswXSkKICAgICAgKSB7CiAgICAgICAgdGhpcy5sZXhlci5zdGF0ZS5pblJhd0Jsb2NrID0gdHJ1ZQogICAgICB9IGVsc2UgaWYgKAogICAgICAgIHRoaXMubGV4ZXIuc3RhdGUuaW5SYXdCbG9jayAmJgogICAgICAgIC9ePFwvKHByZXxjb2RlfGtiZHxzY3JpcHQpKFxzfD4pL2kudGVzdChjYXBbMF0pCiAgICAgICkgewogICAgICAgIHRoaXMubGV4ZXIuc3RhdGUuaW5SYXdCbG9jayA9IGZhbHNlCiAgICAgIH0KCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogdGhpcy5vcHRpb25zLnNhbml0aXplID8gInRleHQiIDogImh0bWwiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGluTGluazogdGhpcy5sZXhlci5zdGF0ZS5pbkxpbmssCiAgICAgICAgaW5SYXdCbG9jazogdGhpcy5sZXhlci5zdGF0ZS5pblJhd0Jsb2NrLAogICAgICAgIHRleHQ6IHRoaXMub3B0aW9ucy5zYW5pdGl6ZQogICAgICAgICAgPyB0aGlzLm9wdGlvbnMuc2FuaXRpemVyCiAgICAgICAgICAgID8gdGhpcy5vcHRpb25zLnNhbml0aXplcihjYXBbMF0pCiAgICAgICAgICAgIDogZXNjYXBlKGNhcFswXSkKICAgICAgICAgIDogY2FwWzBdLAogICAgICB9CiAgICB9CiAgfQoKICBsaW5rKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5pbmxpbmUubGluay5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgY29uc3QgdHJpbW1lZFVybCA9IGNhcFsyXS50cmltKCkKICAgICAgaWYgKCF0aGlzLm9wdGlvbnMucGVkYW50aWMgJiYgL148Ly50ZXN0KHRyaW1tZWRVcmwpKSB7CiAgICAgICAgLy8gY29tbW9ubWFyayByZXF1aXJlcyBtYXRjaGluZyBhbmdsZSBicmFja2V0cwogICAgICAgIGlmICghLz4kLy50ZXN0KHRyaW1tZWRVcmwpKSB7CiAgICAgICAgICByZXR1cm4KICAgICAgICB9CgogICAgICAgIC8vIGVuZGluZyBhbmdsZSBicmFja2V0IGNhbm5vdCBiZSBlc2NhcGVkCiAgICAgICAgY29uc3QgcnRyaW1TbGFzaCA9IHJ0cmltKHRyaW1tZWRVcmwuc2xpY2UoMCwgLTEpLCAiXFwiKQogICAgICAgIGlmICgodHJpbW1lZFVybC5sZW5ndGggLSBydHJpbVNsYXNoLmxlbmd0aCkgJSAyID09PSAwKSB7CiAgICAgICAgICByZXR1cm4KICAgICAgICB9CiAgICAgIH0gZWxzZSB7CiAgICAgICAgLy8gZmluZCBjbG9zaW5nIHBhcmVudGhlc2lzCiAgICAgICAgY29uc3QgbGFzdFBhcmVuSW5kZXggPSBmaW5kQ2xvc2luZ0JyYWNrZXQoY2FwWzJdLCAiKCkiKQogICAgICAgIGlmIChsYXN0UGFyZW5JbmRleCA+IC0xKSB7CiAgICAgICAgICBjb25zdCBzdGFydCA9IGNhcFswXS5pbmRleE9mKCIhIikgPT09IDAgPyA1IDogNAogICAgICAgICAgY29uc3QgbGlua0xlbiA9IHN0YXJ0ICsgY2FwWzFdLmxlbmd0aCArIGxhc3RQYXJlbkluZGV4CiAgICAgICAgICBjYXBbMl0gPSBjYXBbMl0uc3Vic3RyaW5nKDAsIGxhc3RQYXJlbkluZGV4KQogICAgICAgICAgY2FwWzBdID0gY2FwWzBdLnN1YnN0cmluZygwLCBsaW5rTGVuKS50cmltKCkKICAgICAgICAgIGNhcFszXSA9ICIiCiAgICAgICAgfQogICAgICB9CiAgICAgIGxldCBocmVmID0gY2FwWzJdCiAgICAgIGxldCB0aXRsZSA9ICIiCiAgICAgIGlmICh0aGlzLm9wdGlvbnMucGVkYW50aWMpIHsKICAgICAgICAvLyBzcGxpdCBwZWRhbnRpYyBocmVmIGFuZCB0aXRsZQogICAgICAgIGNvbnN0IGxpbmsgPSAvXihbXiciXSpbXlxzXSlccysoWyciXSkoLiopXDIvLmV4ZWMoaHJlZikKCiAgICAgICAgaWYgKGxpbmspIHsKICAgICAgICAgIGhyZWYgPSBsaW5rWzFdCiAgICAgICAgICB0aXRsZSA9IGxpbmtbM10KICAgICAgICB9CiAgICAgIH0gZWxzZSB7CiAgICAgICAgdGl0bGUgPSBjYXBbM10gPyBjYXBbM10uc2xpY2UoMSwgLTEpIDogIiIKICAgICAgfQoKICAgICAgaHJlZiA9IGhyZWYudHJpbSgpCiAgICAgIGlmICgvXjwvLnRlc3QoaHJlZikpIHsKICAgICAgICBpZiAodGhpcy5vcHRpb25zLnBlZGFudGljICYmICEvPiQvLnRlc3QodHJpbW1lZFVybCkpIHsKICAgICAgICAgIC8vIHBlZGFudGljIGFsbG93cyBzdGFydGluZyBhbmdsZSBicmFja2V0IHdpdGhvdXQgZW5kaW5nIGFuZ2xlIGJyYWNrZXQKICAgICAgICAgIGhyZWYgPSBocmVmLnNsaWNlKDEpCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIGhyZWYgPSBocmVmLnNsaWNlKDEsIC0xKQogICAgICAgIH0KICAgICAgfQogICAgICByZXR1cm4gb3V0cHV0TGluaygKICAgICAgICBjYXAsCiAgICAgICAgewogICAgICAgICAgaHJlZjogaHJlZiA/IGhyZWYucmVwbGFjZSh0aGlzLnJ1bGVzLmlubGluZS5fZXNjYXBlcywgIiQxIikgOiBocmVmLAogICAgICAgICAgdGl0bGU6IHRpdGxlCiAgICAgICAgICAgID8gdGl0bGUucmVwbGFjZSh0aGlzLnJ1bGVzLmlubGluZS5fZXNjYXBlcywgIiQxIikKICAgICAgICAgICAgOiB0aXRsZSwKICAgICAgICB9LAogICAgICAgIGNhcFswXSwKICAgICAgICB0aGlzLmxleGVyCiAgICAgICkKICAgIH0KICB9CgogIHJlZmxpbmsoc3JjLCBsaW5rcykgewogICAgbGV0IGNhcAogICAgaWYgKAogICAgICAoY2FwID0gdGhpcy5ydWxlcy5pbmxpbmUucmVmbGluay5leGVjKHNyYykpIHx8CiAgICAgIChjYXAgPSB0aGlzLnJ1bGVzLmlubGluZS5ub2xpbmsuZXhlYyhzcmMpKQogICAgKSB7CiAgICAgIGxldCBsaW5rID0gKGNhcFsyXSB8fCBjYXBbMV0pLnJlcGxhY2UoL1xzKy9nLCAiICIpCiAgICAgIGxpbmsgPSBsaW5rc1tsaW5rLnRvTG93ZXJDYXNlKCldCiAgICAgIGlmICghbGluayB8fCAhbGluay5ocmVmKSB7CiAgICAgICAgY29uc3QgdGV4dCA9IGNhcFswXS5jaGFyQXQoMCkKICAgICAgICByZXR1cm4gewogICAgICAgICAgdHlwZTogInRleHQiLAogICAgICAgICAgcmF3OiB0ZXh0LAogICAgICAgICAgdGV4dCwKICAgICAgICB9CiAgICAgIH0KICAgICAgcmV0dXJuIG91dHB1dExpbmsoY2FwLCBsaW5rLCBjYXBbMF0sIHRoaXMubGV4ZXIpCiAgICB9CiAgfQoKICBlbVN0cm9uZyhzcmMsIG1hc2tlZFNyYywgcHJldkNoYXIgPSAiIikgewogICAgbGV0IG1hdGNoID0gdGhpcy5ydWxlcy5pbmxpbmUuZW1TdHJvbmcubERlbGltLmV4ZWMoc3JjKQogICAgaWYgKCFtYXRjaCkgcmV0dXJuCgogICAgLy8gXyBjYW4ndCBiZSBiZXR3ZWVuIHR3byBhbHBoYW51bWVyaWNzLiBccHtMfVxwe059IGluY2x1ZGVzIG5vbi1lbmdsaXNoIGFscGhhYmV0L251bWJlcnMgYXMgd2VsbAogICAgaWYgKG1hdGNoWzNdICYmIHByZXZDaGFyLm1hdGNoKC9bXHB7TH1ccHtOfV0vdSkpIHJldHVybgoKICAgIGNvbnN0IG5leHRDaGFyID0gbWF0Y2hbMV0gfHwgbWF0Y2hbMl0gfHwgIiIKCiAgICBpZiAoCiAgICAgICFuZXh0Q2hhciB8fAogICAgICAobmV4dENoYXIgJiYKICAgICAgICAocHJldkNoYXIgPT09ICIiIHx8IHRoaXMucnVsZXMuaW5saW5lLnB1bmN0dWF0aW9uLmV4ZWMocHJldkNoYXIpKSkKICAgICkgewogICAgICBjb25zdCBsTGVuZ3RoID0gbWF0Y2hbMF0ubGVuZ3RoIC0gMQogICAgICBsZXQgckRlbGltLAogICAgICAgIHJMZW5ndGgsCiAgICAgICAgZGVsaW1Ub3RhbCA9IGxMZW5ndGgsCiAgICAgICAgbWlkRGVsaW1Ub3RhbCA9IDAKCiAgICAgIGNvbnN0IGVuZFJlZyA9CiAgICAgICAgbWF0Y2hbMF1bMF0gPT09ICIqIgogICAgICAgICAgPyB0aGlzLnJ1bGVzLmlubGluZS5lbVN0cm9uZy5yRGVsaW1Bc3QKICAgICAgICAgIDogdGhpcy5ydWxlcy5pbmxpbmUuZW1TdHJvbmcuckRlbGltVW5kCiAgICAgIGVuZFJlZy5sYXN0SW5kZXggPSAwCgogICAgICAvLyBDbGlwIG1hc2tlZFNyYyB0byBzYW1lIHNlY3Rpb24gb2Ygc3RyaW5nIGFzIHNyYyAobW92ZSB0byBsZXhlcj8pCiAgICAgIG1hc2tlZFNyYyA9IG1hc2tlZFNyYy5zbGljZSgtMSAqIHNyYy5sZW5ndGggKyBsTGVuZ3RoKQoKICAgICAgd2hpbGUgKChtYXRjaCA9IGVuZFJlZy5leGVjKG1hc2tlZFNyYykpICE9IG51bGwpIHsKICAgICAgICByRGVsaW0gPQogICAgICAgICAgbWF0Y2hbMV0gfHwgbWF0Y2hbMl0gfHwgbWF0Y2hbM10gfHwgbWF0Y2hbNF0gfHwgbWF0Y2hbNV0gfHwgbWF0Y2hbNl0KCiAgICAgICAgaWYgKCFyRGVsaW0pIGNvbnRpbnVlIC8vIHNraXAgc2luZ2xlICogaW4gX19hYmMqYWJjX18KCiAgICAgICAgckxlbmd0aCA9IHJEZWxpbS5sZW5ndGgKCiAgICAgICAgaWYgKG1hdGNoWzNdIHx8IG1hdGNoWzRdKSB7CiAgICAgICAgICAvLyBmb3VuZCBhbm90aGVyIExlZnQgRGVsaW0KICAgICAgICAgIGRlbGltVG90YWwgKz0gckxlbmd0aAogICAgICAgICAgY29udGludWUKICAgICAgICB9IGVsc2UgaWYgKG1hdGNoWzVdIHx8IG1hdGNoWzZdKSB7CiAgICAgICAgICAvLyBlaXRoZXIgTGVmdCBvciBSaWdodCBEZWxpbQogICAgICAgICAgaWYgKGxMZW5ndGggJSAzICYmICEoKGxMZW5ndGggKyByTGVuZ3RoKSAlIDMpKSB7CiAgICAgICAgICAgIG1pZERlbGltVG90YWwgKz0gckxlbmd0aAogICAgICAgICAgICBjb250aW51ZSAvLyBDb21tb25NYXJrIEVtcGhhc2lzIFJ1bGVzIDktMTAKICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIGRlbGltVG90YWwgLT0gckxlbmd0aAoKICAgICAgICBpZiAoZGVsaW1Ub3RhbCA+IDApIGNvbnRpbnVlIC8vIEhhdmVuJ3QgZm91bmQgZW5vdWdoIGNsb3NpbmcgZGVsaW1pdGVycwoKICAgICAgICAvLyBSZW1vdmUgZXh0cmEgY2hhcmFjdGVycy4gKmEqKiogLT4gKmEqCiAgICAgICAgckxlbmd0aCA9IE1hdGgubWluKHJMZW5ndGgsIHJMZW5ndGggKyBkZWxpbVRvdGFsICsgbWlkRGVsaW1Ub3RhbCkKCiAgICAgICAgLy8gQ3JlYXRlIGBlbWAgaWYgc21hbGxlc3QgZGVsaW1pdGVyIGhhcyBvZGQgY2hhciBjb3VudC4gKmEqKioKICAgICAgICBpZiAoTWF0aC5taW4obExlbmd0aCwgckxlbmd0aCkgJSAyKSB7CiAgICAgICAgICBjb25zdCB0ZXh0ID0gc3JjLnNsaWNlKDEsIGxMZW5ndGggKyBtYXRjaC5pbmRleCArIHJMZW5ndGgpCiAgICAgICAgICByZXR1cm4gewogICAgICAgICAgICB0eXBlOiAiZW0iLAogICAgICAgICAgICByYXc6IHNyYy5zbGljZSgwLCBsTGVuZ3RoICsgbWF0Y2guaW5kZXggKyByTGVuZ3RoICsgMSksCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIHRva2VuczogdGhpcy5sZXhlci5pbmxpbmVUb2tlbnModGV4dCwgW10pLAogICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgLy8gQ3JlYXRlICdzdHJvbmcnIGlmIHNtYWxsZXN0IGRlbGltaXRlciBoYXMgZXZlbiBjaGFyIGNvdW50LiAqKmEqKioKICAgICAgICBjb25zdCB0ZXh0ID0gc3JjLnNsaWNlKDIsIGxMZW5ndGggKyBtYXRjaC5pbmRleCArIHJMZW5ndGggLSAxKQogICAgICAgIHJldHVybiB7CiAgICAgICAgICB0eXBlOiAic3Ryb25nIiwKICAgICAgICAgIHJhdzogc3JjLnNsaWNlKDAsIGxMZW5ndGggKyBtYXRjaC5pbmRleCArIHJMZW5ndGggKyAxKSwKICAgICAgICAgIHRleHQsCiAgICAgICAgICB0b2tlbnM6IHRoaXMubGV4ZXIuaW5saW5lVG9rZW5zKHRleHQsIFtdKSwKICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9CgogIGNvZGVzcGFuKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5pbmxpbmUuY29kZS5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgbGV0IHRleHQgPSBjYXBbMl0ucmVwbGFjZSgvXG4vZywgIiAiKQogICAgICBjb25zdCBoYXNOb25TcGFjZUNoYXJzID0gL1teIF0vLnRlc3QodGV4dCkKICAgICAgY29uc3QgaGFzU3BhY2VDaGFyc09uQm90aEVuZHMgPSAvXiAvLnRlc3QodGV4dCkgJiYgLyAkLy50ZXN0KHRleHQpCiAgICAgIGlmIChoYXNOb25TcGFjZUNoYXJzICYmIGhhc1NwYWNlQ2hhcnNPbkJvdGhFbmRzKSB7CiAgICAgICAgdGV4dCA9IHRleHQuc3Vic3RyaW5nKDEsIHRleHQubGVuZ3RoIC0gMSkKICAgICAgfQogICAgICB0ZXh0ID0gZXNjYXBlKHRleHQsIHRydWUpCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImNvZGVzcGFuIiwKICAgICAgICByYXc6IGNhcFswXSwKICAgICAgICB0ZXh0LAogICAgICB9CiAgICB9CiAgfQoKICBicihzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLmJyLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJiciIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgIH0KICAgIH0KICB9CgogIGRlbChzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLmRlbC5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgcmV0dXJuIHsKICAgICAgICB0eXBlOiAiZGVsIiwKICAgICAgICByYXc6IGNhcFswXSwKICAgICAgICB0ZXh0OiBjYXBbMl0sCiAgICAgICAgdG9rZW5zOiB0aGlzLmxleGVyLmlubGluZVRva2VucyhjYXBbMl0sIFtdKSwKICAgICAgfQogICAgfQogIH0KCiAgYXV0b2xpbmsoc3JjLCBtYW5nbGUpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLmF1dG9saW5rLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBsZXQgdGV4dCwgaHJlZgogICAgICBpZiAoY2FwWzJdID09PSAiQCIpIHsKICAgICAgICB0ZXh0ID0gZXNjYXBlKHRoaXMub3B0aW9ucy5tYW5nbGUgPyBtYW5nbGUoY2FwWzFdKSA6IGNhcFsxXSkKICAgICAgICBocmVmID0gIm1haWx0bzoiICsgdGV4dAogICAgICB9IGVsc2UgewogICAgICAgIHRleHQgPSBlc2NhcGUoY2FwWzFdKQogICAgICAgIGhyZWYgPSB0ZXh0CiAgICAgIH0KCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImxpbmsiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIHRleHQsCiAgICAgICAgaHJlZiwKICAgICAgICB0b2tlbnM6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgdHlwZTogInRleHQiLAogICAgICAgICAgICByYXc6IHRleHQsCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICB9LAogICAgICAgIF0sCiAgICAgIH0KICAgIH0KICB9CgogIHVybChzcmMsIG1hbmdsZSkgewogICAgbGV0IGNhcAogICAgaWYgKChjYXAgPSB0aGlzLnJ1bGVzLmlubGluZS51cmwuZXhlYyhzcmMpKSkgewogICAgICBsZXQgdGV4dCwgaHJlZgogICAgICBpZiAoY2FwWzJdID09PSAiQCIpIHsKICAgICAgICB0ZXh0ID0gZXNjYXBlKHRoaXMub3B0aW9ucy5tYW5nbGUgPyBtYW5nbGUoY2FwWzBdKSA6IGNhcFswXSkKICAgICAgICBocmVmID0gIm1haWx0bzoiICsgdGV4dAogICAgICB9IGVsc2UgewogICAgICAgIC8vIGRvIGV4dGVuZGVkIGF1dG9saW5rIHBhdGggdmFsaWRhdGlvbgogICAgICAgIGxldCBwcmV2Q2FwWmVybwogICAgICAgIGRvIHsKICAgICAgICAgIHByZXZDYXBaZXJvID0gY2FwWzBdCiAgICAgICAgICBjYXBbMF0gPSB0aGlzLnJ1bGVzLmlubGluZS5fYmFja3BlZGFsLmV4ZWMoY2FwWzBdKVswXQogICAgICAgIH0gd2hpbGUgKHByZXZDYXBaZXJvICE9PSBjYXBbMF0pCiAgICAgICAgdGV4dCA9IGVzY2FwZShjYXBbMF0pCiAgICAgICAgaWYgKGNhcFsxXSA9PT0gInd3dy4iKSB7CiAgICAgICAgICBocmVmID0gImh0dHA6Ly8iICsgdGV4dAogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICBocmVmID0gdGV4dAogICAgICAgIH0KICAgICAgfQogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJsaW5rIiwKICAgICAgICByYXc6IGNhcFswXSwKICAgICAgICB0ZXh0LAogICAgICAgIGhyZWYsCiAgICAgICAgdG9rZW5zOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgIHR5cGU6ICJ0ZXh0IiwKICAgICAgICAgICAgcmF3OiB0ZXh0LAogICAgICAgICAgICB0ZXh0LAogICAgICAgICAgfSwKICAgICAgICBdLAogICAgICB9CiAgICB9CiAgfQoKICBpbmxpbmVUZXh0KHNyYywgc21hcnR5cGFudHMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLnRleHQuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGxldCB0ZXh0CiAgICAgIGlmICh0aGlzLmxleGVyLnN0YXRlLmluUmF3QmxvY2spIHsKICAgICAgICB0ZXh0ID0gdGhpcy5vcHRpb25zLnNhbml0aXplCiAgICAgICAgICA/IHRoaXMub3B0aW9ucy5zYW5pdGl6ZXIKICAgICAgICAgICAgPyB0aGlzLm9wdGlvbnMuc2FuaXRpemVyKGNhcFswXSkKICAgICAgICAgICAgOiBlc2NhcGUoY2FwWzBdKQogICAgICAgICAgOiBjYXBbMF0KICAgICAgfSBlbHNlIHsKICAgICAgICB0ZXh0ID0gZXNjYXBlKHRoaXMub3B0aW9ucy5zbWFydHlwYW50cyA/IHNtYXJ0eXBhbnRzKGNhcFswXSkgOiBjYXBbMF0pCiAgICAgIH0KICAgICAgcmV0dXJuIHsKICAgICAgICB0eXBlOiAidGV4dCIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgICAgdGV4dCwKICAgICAgfQogICAgfQogIH0KfQoKLyoqCiAqIEJsb2NrLUxldmVsIEdyYW1tYXIKICovCmNvbnN0IGJsb2NrID0gewogIG5ld2xpbmU6IC9eKD86ICooPzpcbnwkKSkrLywKICBjb2RlOiAvXiggezR9W15cbl0rKD86XG4oPzogKig/OlxufCQpKSopPykrLywKICBmZW5jZXM6CiAgICAvXiB7MCwzfShgezMsfSg/PVteYFxuXSpcbil8fnszLH0pKFteXG5dKilcbig/OnwoW1xzXFNdKj8pXG4pKD86IHswLDN9XDFbfmBdKiAqKD89XG58JCl8JCkvLAogIGhyOiAvXiB7MCwzfSgoPzotW1x0IF0qKXszLH18KD86X1sgXHRdKil7Myx9fCg/OlwqWyBcdF0qKXszLH0pKD86XG4rfCQpLywKICBoZWFkaW5nOiAvXiB7MCwzfSgjezEsNn0pKD89XHN8JCkoLiopKD86XG4rfCQpLywKICBibG9ja3F1b3RlOiAvXiggezAsM30+ID8ocGFyYWdyYXBofFteXG5dKikoPzpcbnwkKSkrLywKICBsaXN0OiAvXiggezAsM31idWxsKShbIFx0XVteXG5dKz8pPyg/OlxufCQpLywKICBodG1sOgogICAgIl4gezAsM30oPzoiICsgLy8gb3B0aW9uYWwgaW5kZW50YXRpb24KICAgICI8KHNjcmlwdHxwcmV8c3R5bGV8dGV4dGFyZWEpW1xccz5dW1xcc1xcU10qPyg/OjwvXFwxPlteXFxuXSpcXG4rfCQpIiArIC8vICgxKQogICAgInxjb21tZW50W15cXG5dKihcXG4rfCQpIiArIC8vICgyKQogICAgInw8XFw/W1xcc1xcU10qPyg/OlxcPz5cXG4qfCQpIiArIC8vICgzKQogICAgInw8IVtBLVpdW1xcc1xcU10qPyg/Oj5cXG4qfCQpIiArIC8vICg0KQogICAgInw8IVxcW0NEQVRBXFxbW1xcc1xcU10qPyg/OlxcXVxcXT5cXG4qfCQpIiArIC8vICg1KQogICAgInw8Lz8odGFnKSg/OiArfFxcbnwvPz4pW1xcc1xcU10qPyg/Oig/OlxcbiAqKStcXG58JCkiICsgLy8gKDYpCiAgICAifDwoPyFzY3JpcHR8cHJlfHN0eWxlfHRleHRhcmVhKShbYS16XVtcXHctXSopKD86YXR0cmlidXRlKSo/ICovPz4oPz1bIFxcdF0qKD86XFxufCQpKVtcXHNcXFNdKj8oPzooPzpcXG4gKikrXFxufCQpIiArIC8vICg3KSBvcGVuIHRhZwogICAgInw8Lyg/IXNjcmlwdHxwcmV8c3R5bGV8dGV4dGFyZWEpW2Etel1bXFx3LV0qXFxzKj4oPz1bIFxcdF0qKD86XFxufCQpKVtcXHNcXFNdKj8oPzooPzpcXG4gKikrXFxufCQpIiArIC8vICg3KSBjbG9zaW5nIHRhZwogICAgIikiLAogIGRlZjogL14gezAsM31cWyhsYWJlbClcXTogKig/OlxuICopPzw/KFteXHM+XSspPj8oPzooPzogKyg/OlxuICopP3wgKlxuICopKHRpdGxlKSk/ICooPzpcbit8JCkvLAogIHRhYmxlOiBub29wVGVzdCwKICBsaGVhZGluZzogL14oW15cbl0rKVxuIHswLDN9KD0rfC0rKSAqKD86XG4rfCQpLywKICAvLyByZWdleCB0ZW1wbGF0ZSwgcGxhY2Vob2xkZXJzIHdpbGwgYmUgcmVwbGFjZWQgYWNjb3JkaW5nIHRvIGRpZmZlcmVudCBwYXJhZ3JhcGgKICAvLyBpbnRlcnJ1cHRpb24gcnVsZXMgb2YgY29tbW9ubWFyayBhbmQgdGhlIG9yaWdpbmFsIG1hcmtkb3duIHNwZWM6CiAgX3BhcmFncmFwaDoKICAgIC9eKFteXG5dKyg/OlxuKD8haHJ8aGVhZGluZ3xsaGVhZGluZ3xibG9ja3F1b3RlfGZlbmNlc3xsaXN0fGh0bWx8dGFibGV8ICtcbilbXlxuXSspKikvLAogIHRleHQ6IC9eW15cbl0rLywKfQoKYmxvY2suX2xhYmVsID0gLyg/IVxzKlxdKSg/OlxcLnxbXlxbXF1cXF0pKy8KYmxvY2suX3RpdGxlID0gLyg/OiIoPzpcXCI/fFteIlxcXSkqInwnW14nXG5dKig/OlxuW14nXG5dKykqXG4/J3xcKFteKCldKlwpKS8KYmxvY2suZGVmID0gZWRpdChibG9jay5kZWYpCiAgLnJlcGxhY2UoImxhYmVsIiwgYmxvY2suX2xhYmVsKQogIC5yZXBsYWNlKCJ0aXRsZSIsIGJsb2NrLl90aXRsZSkKICAuZ2V0UmVnZXgoKQoKYmxvY2suYnVsbGV0ID0gLyg/OlsqKy1dfFxkezEsOX1bLildKS8KYmxvY2subGlzdEl0ZW1TdGFydCA9IGVkaXQoL14oICopKGJ1bGwpICovKQogIC5yZXBsYWNlKCJidWxsIiwgYmxvY2suYnVsbGV0KQogIC5nZXRSZWdleCgpCgpibG9jay5saXN0ID0gZWRpdChibG9jay5saXN0KQogIC5yZXBsYWNlKC9idWxsL2csIGJsb2NrLmJ1bGxldCkKICAucmVwbGFjZSgKICAgICJociIsCiAgICAiXFxuKyg/PVxcMT8oPzooPzotICopezMsfXwoPzpfICopezMsfXwoPzpcXCogKil7Myx9KSg/Olxcbit8JCkpIgogICkKICAucmVwbGFjZSgiZGVmIiwgIlxcbisoPz0iICsgYmxvY2suZGVmLnNvdXJjZSArICIpIikKICAuZ2V0UmVnZXgoKQoKYmxvY2suX3RhZyA9CiAgImFkZHJlc3N8YXJ0aWNsZXxhc2lkZXxiYXNlfGJhc2Vmb250fGJsb2NrcXVvdGV8Ym9keXxjYXB0aW9uIiArCiAgInxjZW50ZXJ8Y29sfGNvbGdyb3VwfGRkfGRldGFpbHN8ZGlhbG9nfGRpcnxkaXZ8ZGx8ZHR8ZmllbGRzZXR8ZmlnY2FwdGlvbiIgKwogICJ8ZmlndXJlfGZvb3Rlcnxmb3JtfGZyYW1lfGZyYW1lc2V0fGhbMS02XXxoZWFkfGhlYWRlcnxocnxodG1sfGlmcmFtZSIgKwogICJ8bGVnZW5kfGxpfGxpbmt8bWFpbnxtZW51fG1lbnVpdGVtfG1ldGF8bmF2fG5vZnJhbWVzfG9sfG9wdGdyb3VwfG9wdGlvbiIgKwogICJ8cHxwYXJhbXxzZWN0aW9ufHNvdXJjZXxzdW1tYXJ5fHRhYmxlfHRib2R5fHRkfHRmb290fHRofHRoZWFkfHRpdGxlfHRyIiArCiAgInx0cmFja3x1bCIKYmxvY2suX2NvbW1lbnQgPSAvPCEtLSg/IS0/PilbXHNcU10qPyg/Oi0tPnwkKS8KYmxvY2suaHRtbCA9IGVkaXQoYmxvY2suaHRtbCwgImkiKQogIC5yZXBsYWNlKCJjb21tZW50IiwgYmxvY2suX2NvbW1lbnQpCiAgLnJlcGxhY2UoInRhZyIsIGJsb2NrLl90YWcpCiAgLnJlcGxhY2UoCiAgICAiYXR0cmlidXRlIiwKICAgIC8gK1thLXpBLVo6X11bXHcuOi1dKig/OiAqPSAqIlteIlxuXSoifCAqPSAqJ1teJ1xuXSonfCAqPSAqW15ccyInPTw+YF0rKT8vCiAgKQogIC5nZXRSZWdleCgpCgpibG9jay5wYXJhZ3JhcGggPSBlZGl0KGJsb2NrLl9wYXJhZ3JhcGgpCiAgLnJlcGxhY2UoImhyIiwgYmxvY2suaHIpCiAgLnJlcGxhY2UoImhlYWRpbmciLCAiIHswLDN9I3sxLDZ9ICIpCiAgLnJlcGxhY2UoInxsaGVhZGluZyIsICIiKSAvLyBzZXRleCBoZWFkaW5ncyBkb24ndCBpbnRlcnJ1cHQgY29tbW9ubWFyayBwYXJhZ3JhcGhzCiAgLnJlcGxhY2UoInx0YWJsZSIsICIiKQogIC5yZXBsYWNlKCJibG9ja3F1b3RlIiwgIiB7MCwzfT4iKQogIC5yZXBsYWNlKCJmZW5jZXMiLCAiIHswLDN9KD86YHszLH0oPz1bXmBcXG5dKlxcbil8fnszLH0pW15cXG5dKlxcbiIpCiAgLnJlcGxhY2UoImxpc3QiLCAiIHswLDN9KD86WyorLV18MVsuKV0pICIpIC8vIG9ubHkgbGlzdHMgc3RhcnRpbmcgZnJvbSAxIGNhbiBpbnRlcnJ1cHQKICAucmVwbGFjZSgKICAgICJodG1sIiwKICAgICI8Lz8oPzp0YWcpKD86ICt8XFxufC8/Pil8PCg/OnNjcmlwdHxwcmV8c3R5bGV8dGV4dGFyZWF8IS0tKSIKICApCiAgLnJlcGxhY2UoInRhZyIsIGJsb2NrLl90YWcpIC8vIHBhcnMgY2FuIGJlIGludGVycnVwdGVkIGJ5IHR5cGUgKDYpIGh0bWwgYmxvY2tzCiAgLmdldFJlZ2V4KCkKCmJsb2NrLmJsb2NrcXVvdGUgPSBlZGl0KGJsb2NrLmJsb2NrcXVvdGUpCiAgLnJlcGxhY2UoInBhcmFncmFwaCIsIGJsb2NrLnBhcmFncmFwaCkKICAuZ2V0UmVnZXgoKQoKLyoqCiAqIE5vcm1hbCBCbG9jayBHcmFtbWFyCiAqLwoKYmxvY2subm9ybWFsID0gbWVyZ2Uoe30sIGJsb2NrKQoKLyoqCiAqIEdGTSBCbG9jayBHcmFtbWFyCiAqLwoKYmxvY2suZ2ZtID0gbWVyZ2Uoe30sIGJsb2NrLm5vcm1hbCwgewogIHRhYmxlOgogICAgIl4gKihbXlxcbiBdLipcXHwuKilcXG4iICsgLy8gSGVhZGVyCiAgICAiIHswLDN9KD86XFx8ICopPyg6Py0rOj8gKig/OlxcfCAqOj8tKzo/ICopKikoPzpcXHwgKik/IiArIC8vIEFsaWduCiAgICAiKD86XFxuKCg/Oig/ISAqXFxufGhyfGhlYWRpbmd8YmxvY2txdW90ZXxjb2RlfGZlbmNlc3xsaXN0fGh0bWwpLiooPzpcXG58JCkpKilcXG4qfCQpIiwgLy8gQ2VsbHMKfSkKCmJsb2NrLmdmbS50YWJsZSA9IGVkaXQoYmxvY2suZ2ZtLnRhYmxlKQogIC5yZXBsYWNlKCJociIsIGJsb2NrLmhyKQogIC5yZXBsYWNlKCJoZWFkaW5nIiwgIiB7MCwzfSN7MSw2fSAiKQogIC5yZXBsYWNlKCJibG9ja3F1b3RlIiwgIiB7MCwzfT4iKQogIC5yZXBsYWNlKCJjb2RlIiwgIiB7NH1bXlxcbl0iKQogIC5yZXBsYWNlKCJmZW5jZXMiLCAiIHswLDN9KD86YHszLH0oPz1bXmBcXG5dKlxcbil8fnszLH0pW15cXG5dKlxcbiIpCiAgLnJlcGxhY2UoImxpc3QiLCAiIHswLDN9KD86WyorLV18MVsuKV0pICIpIC8vIG9ubHkgbGlzdHMgc3RhcnRpbmcgZnJvbSAxIGNhbiBpbnRlcnJ1cHQKICAucmVwbGFjZSgKICAgICJodG1sIiwKICAgICI8Lz8oPzp0YWcpKD86ICt8XFxufC8/Pil8PCg/OnNjcmlwdHxwcmV8c3R5bGV8dGV4dGFyZWF8IS0tKSIKICApCiAgLnJlcGxhY2UoInRhZyIsIGJsb2NrLl90YWcpIC8vIHRhYmxlcyBjYW4gYmUgaW50ZXJydXB0ZWQgYnkgdHlwZSAoNikgaHRtbCBibG9ja3MKICAuZ2V0UmVnZXgoKQoKYmxvY2suZ2ZtLnBhcmFncmFwaCA9IGVkaXQoYmxvY2suX3BhcmFncmFwaCkKICAucmVwbGFjZSgiaHIiLCBibG9jay5ocikKICAucmVwbGFjZSgiaGVhZGluZyIsICIgezAsM30jezEsNn0gIikKICAucmVwbGFjZSgifGxoZWFkaW5nIiwgIiIpIC8vIHNldGV4IGhlYWRpbmdzIGRvbid0IGludGVycnVwdCBjb21tb25tYXJrIHBhcmFncmFwaHMKICAucmVwbGFjZSgidGFibGUiLCBibG9jay5nZm0udGFibGUpIC8vIGludGVycnVwdCBwYXJhZ3JhcGhzIHdpdGggdGFibGUKICAucmVwbGFjZSgiYmxvY2txdW90ZSIsICIgezAsM30+IikKICAucmVwbGFjZSgiZmVuY2VzIiwgIiB7MCwzfSg/OmB7Myx9KD89W15gXFxuXSpcXG4pfH57Myx9KVteXFxuXSpcXG4iKQogIC5yZXBsYWNlKCJsaXN0IiwgIiB7MCwzfSg/OlsqKy1dfDFbLildKSAiKSAvLyBvbmx5IGxpc3RzIHN0YXJ0aW5nIGZyb20gMSBjYW4gaW50ZXJydXB0CiAgLnJlcGxhY2UoCiAgICAiaHRtbCIsCiAgICAiPC8/KD86dGFnKSg/OiArfFxcbnwvPz4pfDwoPzpzY3JpcHR8cHJlfHN0eWxlfHRleHRhcmVhfCEtLSkiCiAgKQogIC5yZXBsYWNlKCJ0YWciLCBibG9jay5fdGFnKSAvLyBwYXJzIGNhbiBiZSBpbnRlcnJ1cHRlZCBieSB0eXBlICg2KSBodG1sIGJsb2NrcwogIC5nZXRSZWdleCgpCi8qKgogKiBQZWRhbnRpYyBncmFtbWFyIChvcmlnaW5hbCBKb2huIEdydWJlcidzIGxvb3NlIG1hcmtkb3duIHNwZWNpZmljYXRpb24pCiAqLwoKYmxvY2sucGVkYW50aWMgPSBtZXJnZSh7fSwgYmxvY2subm9ybWFsLCB7CiAgaHRtbDogZWRpdCgKICAgICJeICooPzpjb21tZW50ICooPzpcXG58XFxzKiQpIiArCiAgICAgICJ8PCh0YWcpW1xcc1xcU10rPzwvXFwxPiAqKD86XFxuezIsfXxcXHMqJCkiICsgLy8gY2xvc2VkIHRhZwogICAgICAifDx0YWcoPzpcIlteXCJdKlwifCdbXiddKid8XFxzW14nXCIvPlxcc10qKSo/Lz8+ICooPzpcXG57Mix9fFxccyokKSkiCiAgKQogICAgLnJlcGxhY2UoImNvbW1lbnQiLCBibG9jay5fY29tbWVudCkKICAgIC5yZXBsYWNlKAogICAgICAvdGFnL2csCiAgICAgICIoPyEoPzoiICsKICAgICAgICAiYXxlbXxzdHJvbmd8c21hbGx8c3xjaXRlfHF8ZGZufGFiYnJ8ZGF0YXx0aW1lfGNvZGV8dmFyfHNhbXB8a2JkfHN1YiIgKwogICAgICAgICJ8c3VwfGl8Ynx1fG1hcmt8cnVieXxydHxycHxiZGl8YmRvfHNwYW58YnJ8d2JyfGluc3xkZWx8aW1nKSIgKwogICAgICAgICJcXGIpXFx3Kyg/ITp8W15cXHdcXHNAXSpAKVxcYiIKICAgICkKICAgIC5nZXRSZWdleCgpLAogIGRlZjogL14gKlxbKFteXF1dKylcXTogKjw/KFteXHM+XSspPj8oPzogKyhbIihdW15cbl0rWyIpXSkpPyAqKD86XG4rfCQpLywKICBoZWFkaW5nOiAvXigjezEsNn0pKC4qKSg/OlxuK3wkKS8sCiAgZmVuY2VzOiBub29wVGVzdCwgLy8gZmVuY2VzIG5vdCBzdXBwb3J0ZWQKICBwYXJhZ3JhcGg6IGVkaXQoYmxvY2subm9ybWFsLl9wYXJhZ3JhcGgpCiAgICAucmVwbGFjZSgiaHIiLCBibG9jay5ocikKICAgIC5yZXBsYWNlKCJoZWFkaW5nIiwgIiAqI3sxLDZ9ICpbXlxuXSIpCiAgICAucmVwbGFjZSgibGhlYWRpbmciLCBibG9jay5saGVhZGluZykKICAgIC5yZXBsYWNlKCJibG9ja3F1b3RlIiwgIiB7MCwzfT4iKQogICAgLnJlcGxhY2UoInxmZW5jZXMiLCAiIikKICAgIC5yZXBsYWNlKCJ8bGlzdCIsICIiKQogICAgLnJlcGxhY2UoInxodG1sIiwgIiIpCiAgICAuZ2V0UmVnZXgoKSwKfSkKCi8qKgogKiBJbmxpbmUtTGV2ZWwgR3JhbW1hcgogKi8KY29uc3QgaW5saW5lID0gewogIGVzY2FwZTogL15cXChbISIjJCUmJygpKissXC0uLzo7PD0+P0BcW1xdXFxeX2B7fH1+XSkvLAogIGF1dG9saW5rOiAvXjwoc2NoZW1lOlteXHNceDAwLVx4MWY8Pl0qfGVtYWlsKT4vLAogIHVybDogbm9vcFRlc3QsCiAgdGFnOgogICAgIl5jb21tZW50IiArCiAgICAifF48L1thLXpBLVpdW1xcdzotXSpcXHMqPiIgKyAvLyBzZWxmLWNsb3NpbmcgdGFnCiAgICAifF48W2EtekEtWl1bXFx3LV0qKD86YXR0cmlidXRlKSo/XFxzKi8/PiIgKyAvLyBvcGVuIHRhZwogICAgInxePFxcP1tcXHNcXFNdKj9cXD8+IiArIC8vIHByb2Nlc3NpbmcgaW5zdHJ1Y3Rpb24sIGUuZy4gPD9waHAgPz4KICAgICJ8XjwhW2EtekEtWl0rXFxzW1xcc1xcU10qPz4iICsgLy8gZGVjbGFyYXRpb24sIGUuZy4gPCFET0NUWVBFIGh0bWw+CiAgICAifF48IVxcW0NEQVRBXFxbW1xcc1xcU10qP1xcXVxcXT4iLCAvLyBDREFUQSBzZWN0aW9uCiAgbGluazogL14hP1xbKGxhYmVsKVxdXChccyooaHJlZikoPzpccysodGl0bGUpKT9ccypcKS8sCiAgcmVmbGluazogL14hP1xbKGxhYmVsKVxdXFsocmVmKVxdLywKICBub2xpbms6IC9eIT9cWyhyZWYpXF0oPzpcW1xdKT8vLAogIHJlZmxpbmtTZWFyY2g6ICJyZWZsaW5rfG5vbGluayg/IVxcKCkiLAogIGVtU3Ryb25nOiB7CiAgICBsRGVsaW06IC9eKD86XCorKD86KFtwdW5jdF9dKXxbXlxzKl0pKXxeXysoPzooW3B1bmN0Kl0pfChbXlxzX10pKS8sCiAgICAvLyAgICAgICAgKDEpIGFuZCAoMikgY2FuIG9ubHkgYmUgYSBSaWdodCBEZWxpbWl0ZXIuICgzKSBhbmQgKDQpIGNhbiBvbmx5IGJlIExlZnQuICAoNSkgYW5kICg2KSBjYW4gYmUgZWl0aGVyIExlZnQgb3IgUmlnaHQuCiAgICAvLyAgICAgICAgICAoKSBTa2lwIG9ycGhhbiBpbnNpZGUgc3Ryb25nICAoKSBDb25zdW1lIHRvIGRlbGltICgxKSAjKioqICAgICAgICAgICAgICAgICgyKSBhKioqIywgYSoqKiAgICAgICAgICAgICAgICAgICAoMykgIyoqKmEsICoqKmEgICAgICAgICAgICAgICAgICg0KSAqKiojICAgICAgICAgICAgICAoNSkgIyoqKiMgICAgICAgICAgICAgICAgICg2KSBhKioqYQogICAgckRlbGltQXN0OgogICAgICAvXlteXypdKj9cX1xfW15fKl0qP1wqW15fKl0qPyg/PVxfXF8pfFteKl0rKD89W14qXSl8W3B1bmN0X10oXCorKSg/PVtcc118JCl8W15wdW5jdCpfXHNdKFwqKykoPz1bcHVuY3RfXHNdfCQpfFtwdW5jdF9cc10oXCorKSg/PVtecHVuY3QqX1xzXSl8W1xzXShcKispKD89W3B1bmN0X10pfFtwdW5jdF9dKFwqKykoPz1bcHVuY3RfXSl8W15wdW5jdCpfXHNdKFwqKykoPz1bXnB1bmN0Kl9cc10pLywKICAgIHJEZWxpbVVuZDoKICAgICAgL15bXl8qXSo/XCpcKlteXypdKj9cX1teXypdKj8oPz1cKlwqKXxbXl9dKyg/PVteX10pfFtwdW5jdCpdKFxfKykoPz1bXHNdfCQpfFtecHVuY3QqX1xzXShcXyspKD89W3B1bmN0KlxzXXwkKXxbcHVuY3QqXHNdKFxfKykoPz1bXnB1bmN0Kl9cc10pfFtcc10oXF8rKSg/PVtwdW5jdCpdKXxbcHVuY3QqXShcXyspKD89W3B1bmN0Kl0pLywgLy8gXi0gTm90IGFsbG93ZWQgZm9yIF8KICB9LAogIGNvZGU6IC9eKGArKShbXmBdfFteYF1bXHNcU10qP1teYF0pXDEoPyFgKS8sCiAgYnI6IC9eKCB7Mix9fFxcKVxuKD8hXHMqJCkvLAogIGRlbDogbm9vcFRlc3QsCiAgdGV4dDogL14oYCt8W15gXSkoPzooPz0gezIsfVxuKXxbXHNcU10qPyg/Oig/PVtcXDwhXFtgKl9dfFxiX3wkKXxbXiBdKD89IHsyLH1cbikpKS8sCiAgcHVuY3R1YXRpb246IC9eKFtcc3B1bmN0dWF0aW9uXSkvLAp9CgovLyBsaXN0IG9mIHB1bmN0dWF0aW9uIG1hcmtzIGZyb20gQ29tbW9uTWFyayBzcGVjCi8vIHdpdGhvdXQgKiBhbmQgXyB0byBoYW5kbGUgdGhlIGRpZmZlcmVudCBlbXBoYXNpcyBtYXJrZXJzICogYW5kIF8KaW5saW5lLl9wdW5jdHVhdGlvbiA9ICIhXCIjJCUmJygpK1xcLS4sLzo7PD0+P0BcXFtcXF1gXnt8fX4iCmlubGluZS5wdW5jdHVhdGlvbiA9IGVkaXQoaW5saW5lLnB1bmN0dWF0aW9uKQogIC5yZXBsYWNlKC9wdW5jdHVhdGlvbi9nLCBpbmxpbmUuX3B1bmN0dWF0aW9uKQogIC5nZXRSZWdleCgpCgovLyBzZXF1ZW5jZXMgZW0gc2hvdWxkIHNraXAgb3ZlciBbdGl0bGVdKGxpbmspLCBgY29kZWAsIDxodG1sPgppbmxpbmUuYmxvY2tTa2lwID0gL1xbW15cXV0qP1xdXChbXlwpXSo/XCl8YFteYF0qP2B8PFtePl0qPz4vZwppbmxpbmUuZXNjYXBlZEVtU3QgPSAvXFxcKnxcXF8vZwoKaW5saW5lLl9jb21tZW50ID0gZWRpdChibG9jay5fY29tbWVudCkucmVwbGFjZSgiKD86LS0+fCQpIiwgIi0tPiIpLmdldFJlZ2V4KCkKCmlubGluZS5lbVN0cm9uZy5sRGVsaW0gPSBlZGl0KGlubGluZS5lbVN0cm9uZy5sRGVsaW0pCiAgLnJlcGxhY2UoL3B1bmN0L2csIGlubGluZS5fcHVuY3R1YXRpb24pCiAgLmdldFJlZ2V4KCkKCmlubGluZS5lbVN0cm9uZy5yRGVsaW1Bc3QgPSBlZGl0KGlubGluZS5lbVN0cm9uZy5yRGVsaW1Bc3QsICJnIikKICAucmVwbGFjZSgvcHVuY3QvZywgaW5saW5lLl9wdW5jdHVhdGlvbikKICAuZ2V0UmVnZXgoKQoKaW5saW5lLmVtU3Ryb25nLnJEZWxpbVVuZCA9IGVkaXQoaW5saW5lLmVtU3Ryb25nLnJEZWxpbVVuZCwgImciKQogIC5yZXBsYWNlKC9wdW5jdC9nLCBpbmxpbmUuX3B1bmN0dWF0aW9uKQogIC5nZXRSZWdleCgpCgppbmxpbmUuX2VzY2FwZXMgPSAvXFwoWyEiIyQlJicoKSorLFwtLi86Ozw9Pj9AXFtcXVxcXl9ge3x9fl0pL2cKCmlubGluZS5fc2NoZW1lID0gL1thLXpBLVpdW2EtekEtWjAtOSsuLV17MSwzMX0vCmlubGluZS5fZW1haWwgPQogIC9bYS16QS1aMC05LiEjJCUmJyorLz0/Xl9ge3x9fi1dKyhAKVthLXpBLVowLTldKD86W2EtekEtWjAtOS1dezAsNjF9W2EtekEtWjAtOV0pPyg/OlwuW2EtekEtWjAtOV0oPzpbYS16QS1aMC05LV17MCw2MX1bYS16QS1aMC05XSk/KSsoPyFbLV9dKS8KaW5saW5lLmF1dG9saW5rID0gZWRpdChpbmxpbmUuYXV0b2xpbmspCiAgLnJlcGxhY2UoInNjaGVtZSIsIGlubGluZS5fc2NoZW1lKQogIC5yZXBsYWNlKCJlbWFpbCIsIGlubGluZS5fZW1haWwpCiAgLmdldFJlZ2V4KCkKCmlubGluZS5fYXR0cmlidXRlID0KICAvXHMrW2EtekEtWjpfXVtcdy46LV0qKD86XHMqPVxzKiJbXiJdKiJ8XHMqPVxzKidbXiddKid8XHMqPVxzKlteXHMiJz08PmBdKyk/LwoKaW5saW5lLnRhZyA9IGVkaXQoaW5saW5lLnRhZykKICAucmVwbGFjZSgiY29tbWVudCIsIGlubGluZS5fY29tbWVudCkKICAucmVwbGFjZSgiYXR0cmlidXRlIiwgaW5saW5lLl9hdHRyaWJ1dGUpCiAgLmdldFJlZ2V4KCkKCmlubGluZS5fbGFiZWwgPSAvKD86XFsoPzpcXC58W15cW1xdXFxdKSpcXXxcXC58YFteYF0qYHxbXlxbXF1cXGBdKSo/LwppbmxpbmUuX2hyZWYgPSAvPCg/OlxcLnxbXlxuPD5cXF0pKz58W15cc1x4MDAtXHgxZl0qLwppbmxpbmUuX3RpdGxlID0gLyIoPzpcXCI/fFteIlxcXSkqInwnKD86XFwnP3xbXidcXF0pKid8XCgoPzpcXFwpP3xbXilcXF0pKlwpLwoKaW5saW5lLmxpbmsgPSBlZGl0KGlubGluZS5saW5rKQogIC5yZXBsYWNlKCJsYWJlbCIsIGlubGluZS5fbGFiZWwpCiAgLnJlcGxhY2UoImhyZWYiLCBpbmxpbmUuX2hyZWYpCiAgLnJlcGxhY2UoInRpdGxlIiwgaW5saW5lLl90aXRsZSkKICAuZ2V0UmVnZXgoKQoKaW5saW5lLnJlZmxpbmsgPSBlZGl0KGlubGluZS5yZWZsaW5rKQogIC5yZXBsYWNlKCJsYWJlbCIsIGlubGluZS5fbGFiZWwpCiAgLnJlcGxhY2UoInJlZiIsIGJsb2NrLl9sYWJlbCkKICAuZ2V0UmVnZXgoKQoKaW5saW5lLm5vbGluayA9IGVkaXQoaW5saW5lLm5vbGluaykucmVwbGFjZSgicmVmIiwgYmxvY2suX2xhYmVsKS5nZXRSZWdleCgpCgppbmxpbmUucmVmbGlua1NlYXJjaCA9IGVkaXQoaW5saW5lLnJlZmxpbmtTZWFyY2gsICJnIikKICAucmVwbGFjZSgicmVmbGluayIsIGlubGluZS5yZWZsaW5rKQogIC5yZXBsYWNlKCJub2xpbmsiLCBpbmxpbmUubm9saW5rKQogIC5nZXRSZWdleCgpCgovKioKICogTm9ybWFsIElubGluZSBHcmFtbWFyCiAqLwoKaW5saW5lLm5vcm1hbCA9IG1lcmdlKHt9LCBpbmxpbmUpCgovKioKICogUGVkYW50aWMgSW5saW5lIEdyYW1tYXIKICovCgppbmxpbmUucGVkYW50aWMgPSBtZXJnZSh7fSwgaW5saW5lLm5vcm1hbCwgewogIHN0cm9uZzogewogICAgc3RhcnQ6IC9eX198XCpcKi8sCiAgICBtaWRkbGU6IC9eX18oPz1cUykoW1xzXFNdKj9cUylfXyg/IV8pfF5cKlwqKD89XFMpKFtcc1xTXSo/XFMpXCpcKig/IVwqKS8sCiAgICBlbmRBc3Q6IC9cKlwqKD8hXCopL2csCiAgICBlbmRVbmQ6IC9fXyg/IV8pL2csCiAgfSwKICBlbTogewogICAgc3RhcnQ6IC9eX3xcKi8sCiAgICBtaWRkbGU6IC9eKClcKig/PVxTKShbXHNcU10qP1xTKVwqKD8hXCopfF5fKD89XFMpKFtcc1xTXSo/XFMpXyg/IV8pLywKICAgIGVuZEFzdDogL1wqKD8hXCopL2csCiAgICBlbmRVbmQ6IC9fKD8hXykvZywKICB9LAogIGxpbms6IGVkaXQoL14hP1xbKGxhYmVsKVxdXCgoLio/KVwpLykKICAgIC5yZXBsYWNlKCJsYWJlbCIsIGlubGluZS5fbGFiZWwpCiAgICAuZ2V0UmVnZXgoKSwKICByZWZsaW5rOiBlZGl0KC9eIT9cWyhsYWJlbClcXVxzKlxbKFteXF1dKilcXS8pCiAgICAucmVwbGFjZSgibGFiZWwiLCBpbmxpbmUuX2xhYmVsKQogICAgLmdldFJlZ2V4KCksCn0pCgovKioKICogR0ZNIElubGluZSBHcmFtbWFyCiAqLwoKaW5saW5lLmdmbSA9IG1lcmdlKHt9LCBpbmxpbmUubm9ybWFsLCB7CiAgZXNjYXBlOiBlZGl0KGlubGluZS5lc2NhcGUpLnJlcGxhY2UoIl0pIiwgIn58XSkiKS5nZXRSZWdleCgpLAogIF9leHRlbmRlZF9lbWFpbDoKICAgIC9bQS1aYS16MC05Ll8rLV0rKEApW2EtekEtWjAtOS1fXSsoPzpcLlthLXpBLVowLTktX10qW2EtekEtWjAtOV0pKyg/IVstX10pLywKICB1cmw6IC9eKCg/OmZ0cHxodHRwcz8pOlwvXC98d3d3XC4pKD86W2EtekEtWjAtOVwtXStcLj8pK1teXHM8XSp8XmVtYWlsLywKICBfYmFja3BlZGFsOgogICAgLyg/OltePyEuLDo7Kl9+KCkmXSt8XChbXildKlwpfCYoPyFbYS16QS1aMC05XSs7JCl8Wz8hLiw6OypffildKyg/ISQpKSsvLAogIGRlbDogL14ofn4/KSg/PVteXHN+XSkoW1xzXFNdKj9bXlxzfl0pXDEoPz1bXn5dfCQpLywKICB0ZXh0OiAvXihbYH5dK3xbXmB+XSkoPzooPz0gezIsfVxuKXwoPz1bYS16QS1aMC05LiEjJCUmJyorXC89P19ge1x8fX4tXStAKXxbXHNcU10qPyg/Oig/PVtcXDwhXFtgKn5fXXxcYl98aHR0cHM/OlwvXC98ZnRwOlwvXC98d3d3XC58JCl8W14gXSg/PSB7Mix9XG4pfFteYS16QS1aMC05LiEjJCUmJyorXC89P19ge1x8fX4tXSg/PVthLXpBLVowLTkuISMkJSYnKitcLz0/X2B7XHx9fi1dK0ApKSkvLAp9KQoKaW5saW5lLmdmbS51cmwgPSBlZGl0KGlubGluZS5nZm0udXJsLCAiaSIpCiAgLnJlcGxhY2UoImVtYWlsIiwgaW5saW5lLmdmbS5fZXh0ZW5kZWRfZW1haWwpCiAgLmdldFJlZ2V4KCkKLyoqCiAqIEdGTSArIExpbmUgQnJlYWtzIElubGluZSBHcmFtbWFyCiAqLwoKaW5saW5lLmJyZWFrcyA9IG1lcmdlKHt9LCBpbmxpbmUuZ2ZtLCB7CiAgYnI6IGVkaXQoaW5saW5lLmJyKS5yZXBsYWNlKCJ7Mix9IiwgIioiKS5nZXRSZWdleCgpLAogIHRleHQ6IGVkaXQoaW5saW5lLmdmbS50ZXh0KQogICAgLnJlcGxhY2UoIlxcYl8iLCAiXFxiX3wgezIsfVxcbiIpCiAgICAucmVwbGFjZSgvXHsyLFx9L2csICIqIikKICAgIC5nZXRSZWdleCgpLAp9KQoKLyoqCiAqIHNtYXJ0eXBhbnRzIHRleHQgcmVwbGFjZW1lbnQKICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICovCmZ1bmN0aW9uIHNtYXJ0eXBhbnRzKHRleHQpIHsKICByZXR1cm4gKAogICAgdGV4dAogICAgICAvLyBlbS1kYXNoZXMKICAgICAgLnJlcGxhY2UoLy0tLS9nLCAiXHUyMDE0IikKICAgICAgLy8gZW4tZGFzaGVzCiAgICAgIC5yZXBsYWNlKC8tLS9nLCAiXHUyMDEzIikKICAgICAgLy8gb3BlbmluZyBzaW5nbGVzCiAgICAgIC5yZXBsYWNlKC8oXnxbLVx1MjAxNC8oXFt7IlxzXSknL2csICIkMVx1MjAxOCIpCiAgICAgIC8vIGNsb3Npbmcgc2luZ2xlcyAmIGFwb3N0cm9waGVzCiAgICAgIC5yZXBsYWNlKC8nL2csICJcdTIwMTkiKQogICAgICAvLyBvcGVuaW5nIGRvdWJsZXMKICAgICAgLnJlcGxhY2UoLyhefFstXHUyMDE0LyhcW3tcdTIwMThcc10pIi9nLCAiJDFcdTIwMWMiKQogICAgICAvLyBjbG9zaW5nIGRvdWJsZXMKICAgICAgLnJlcGxhY2UoLyIvZywgIlx1MjAxZCIpCiAgICAgIC8vIGVsbGlwc2VzCiAgICAgIC5yZXBsYWNlKC9cLnszfS9nLCAiXHUyMDI2IikKICApCn0KCi8qKgogKiBtYW5nbGUgZW1haWwgYWRkcmVzc2VzCiAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAqLwpmdW5jdGlvbiBtYW5nbGUodGV4dCkgewogIGxldCBvdXQgPSAiIiwKICAgIGksCiAgICBjaAoKICBjb25zdCBsID0gdGV4dC5sZW5ndGgKICBmb3IgKGkgPSAwOyBpIDwgbDsgaSsrKSB7CiAgICBjaCA9IHRleHQuY2hhckNvZGVBdChpKQogICAgaWYgKE1hdGgucmFuZG9tKCkgPiAwLjUpIHsKICAgICAgY2ggPSAieCIgKyBjaC50b1N0cmluZygxNikKICAgIH0KICAgIG91dCArPSAiJiMiICsgY2ggKyAiOyIKICB9CgogIHJldHVybiBvdXQKfQoKLyoqCiAqIEJsb2NrIExleGVyCiAqLwpjbGFzcyBMZXhlciB7CiAgY29uc3RydWN0b3Iob3B0aW9ucykgewogICAgdGhpcy50b2tlbnMgPSBbXQogICAgdGhpcy50b2tlbnMubGlua3MgPSBPYmplY3QuY3JlYXRlKG51bGwpCiAgICB0aGlzLm9wdGlvbnMgPSBvcHRpb25zIHx8IGRlZmF1bHRzCiAgICB0aGlzLm9wdGlvbnMudG9rZW5pemVyID0gdGhpcy5vcHRpb25zLnRva2VuaXplciB8fCBuZXcgVG9rZW5pemVyKCkKICAgIHRoaXMudG9rZW5pemVyID0gdGhpcy5vcHRpb25zLnRva2VuaXplcgogICAgdGhpcy50b2tlbml6ZXIub3B0aW9ucyA9IHRoaXMub3B0aW9ucwogICAgdGhpcy50b2tlbml6ZXIubGV4ZXIgPSB0aGlzCiAgICB0aGlzLmlubGluZVF1ZXVlID0gW10KICAgIHRoaXMuc3RhdGUgPSB7CiAgICAgIGluTGluazogZmFsc2UsCiAgICAgIGluUmF3QmxvY2s6IGZhbHNlLAogICAgICB0b3A6IHRydWUsCiAgICB9CgogICAgY29uc3QgcnVsZXMgPSB7CiAgICAgIGJsb2NrOiBibG9jay5ub3JtYWwsCiAgICAgIGlubGluZTogaW5saW5lLm5vcm1hbCwKICAgIH0KCiAgICBpZiAodGhpcy5vcHRpb25zLnBlZGFudGljKSB7CiAgICAgIHJ1bGVzLmJsb2NrID0gYmxvY2sucGVkYW50aWMKICAgICAgcnVsZXMuaW5saW5lID0gaW5saW5lLnBlZGFudGljCiAgICB9IGVsc2UgaWYgKHRoaXMub3B0aW9ucy5nZm0pIHsKICAgICAgcnVsZXMuYmxvY2sgPSBibG9jay5nZm0KICAgICAgaWYgKHRoaXMub3B0aW9ucy5icmVha3MpIHsKICAgICAgICBydWxlcy5pbmxpbmUgPSBpbmxpbmUuYnJlYWtzCiAgICAgIH0gZWxzZSB7CiAgICAgICAgcnVsZXMuaW5saW5lID0gaW5saW5lLmdmbQogICAgICB9CiAgICB9CiAgICB0aGlzLnRva2VuaXplci5ydWxlcyA9IHJ1bGVzCiAgfQoKICAvKioKICAgKiBFeHBvc2UgUnVsZXMKICAgKi8KICBzdGF0aWMgZ2V0IHJ1bGVzKCkgewogICAgcmV0dXJuIHsKICAgICAgYmxvY2ssCiAgICAgIGlubGluZSwKICAgIH0KICB9CgogIC8qKgogICAqIFN0YXRpYyBMZXggTWV0aG9kCiAgICovCiAgc3RhdGljIGxleChzcmMsIG9wdGlvbnMpIHsKICAgIGNvbnN0IGxleGVyID0gbmV3IExleGVyKG9wdGlvbnMpCiAgICByZXR1cm4gbGV4ZXIubGV4KHNyYykKICB9CgogIC8qKgogICAqIFN0YXRpYyBMZXggSW5saW5lIE1ldGhvZAogICAqLwogIHN0YXRpYyBsZXhJbmxpbmUoc3JjLCBvcHRpb25zKSB7CiAgICBjb25zdCBsZXhlciA9IG5ldyBMZXhlcihvcHRpb25zKQogICAgcmV0dXJuIGxleGVyLmlubGluZVRva2VucyhzcmMpCiAgfQoKICAvKioKICAgKiBQcmVwcm9jZXNzaW5nCiAgICovCiAgbGV4KHNyYykgewogICAgc3JjID0gc3JjLnJlcGxhY2UoL1xyXG58XHIvZywgIlxuIikKCiAgICB0aGlzLmJsb2NrVG9rZW5zKHNyYywgdGhpcy50b2tlbnMpCgogICAgbGV0IG5leHQKICAgIHdoaWxlICgobmV4dCA9IHRoaXMuaW5saW5lUXVldWUuc2hpZnQoKSkpIHsKICAgICAgdGhpcy5pbmxpbmVUb2tlbnMobmV4dC5zcmMsIG5leHQudG9rZW5zKQogICAgfQoKICAgIHJldHVybiB0aGlzLnRva2VucwogIH0KCiAgLyoqCiAgICogTGV4aW5nCiAgICovCiAgYmxvY2tUb2tlbnMoc3JjLCB0b2tlbnMgPSBbXSkgewogICAgaWYgKHRoaXMub3B0aW9ucy5wZWRhbnRpYykgewogICAgICBzcmMgPSBzcmMucmVwbGFjZSgvXHQvZywgIiAgICAiKS5yZXBsYWNlKC9eICskL2dtLCAiIikKICAgIH0gZWxzZSB7CiAgICAgIHNyYyA9IHNyYy5yZXBsYWNlKC9eKCAqKShcdCspL2dtLCAoXywgbGVhZGluZywgdGFicykgPT4gewogICAgICAgIHJldHVybiBsZWFkaW5nICsgIiAgICAiLnJlcGVhdCh0YWJzLmxlbmd0aCkKICAgICAgfSkKICAgIH0KCiAgICBsZXQgdG9rZW4sIGxhc3RUb2tlbiwgY3V0U3JjLCBsYXN0UGFyYWdyYXBoQ2xpcHBlZAoKICAgIHdoaWxlIChzcmMpIHsKICAgICAgaWYgKAogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zICYmCiAgICAgICAgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMuYmxvY2sgJiYKICAgICAgICB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucy5ibG9jay5zb21lKGV4dFRva2VuaXplciA9PiB7CiAgICAgICAgICBpZiAoKHRva2VuID0gZXh0VG9rZW5pemVyLmNhbGwoeyBsZXhlcjogdGhpcyB9LCBzcmMsIHRva2VucykpKSB7CiAgICAgICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgICAgIHJldHVybiB0cnVlCiAgICAgICAgICB9CiAgICAgICAgICByZXR1cm4gZmFsc2UKICAgICAgICB9KQogICAgICApIHsKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBuZXdsaW5lCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5zcGFjZShzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICBpZiAodG9rZW4ucmF3Lmxlbmd0aCA9PT0gMSAmJiB0b2tlbnMubGVuZ3RoID4gMCkgewogICAgICAgICAgLy8gaWYgdGhlcmUncyBhIHNpbmdsZSBcbiBhcyBhIHNwYWNlciwgaXQncyB0ZXJtaW5hdGluZyB0aGUgbGFzdCBsaW5lLAogICAgICAgICAgLy8gc28gbW92ZSBpdCB0aGVyZSBzbyB0aGF0IHdlIGRvbid0IGdldCB1bmVjZXNzYXJ5IHBhcmFncmFwaCB0YWdzCiAgICAgICAgICB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdLnJhdyArPSAiXG4iCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIH0KICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBjb2RlCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5jb2RlKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIGxhc3RUb2tlbiA9IHRva2Vuc1t0b2tlbnMubGVuZ3RoIC0gMV0KICAgICAgICAvLyBBbiBpbmRlbnRlZCBjb2RlIGJsb2NrIGNhbm5vdCBpbnRlcnJ1cHQgYSBwYXJhZ3JhcGguCiAgICAgICAgaWYgKAogICAgICAgICAgbGFzdFRva2VuICYmCiAgICAgICAgICAobGFzdFRva2VuLnR5cGUgPT09ICJwYXJhZ3JhcGgiIHx8IGxhc3RUb2tlbi50eXBlID09PSAidGV4dCIpCiAgICAgICAgKSB7CiAgICAgICAgICBsYXN0VG9rZW4ucmF3ICs9ICJcbiIgKyB0b2tlbi5yYXcKICAgICAgICAgIGxhc3RUb2tlbi50ZXh0ICs9ICJcbiIgKyB0b2tlbi50ZXh0CiAgICAgICAgICB0aGlzLmlubGluZVF1ZXVlW3RoaXMuaW5saW5lUXVldWUubGVuZ3RoIC0gMV0uc3JjID0gbGFzdFRva2VuLnRleHQKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgfQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGZlbmNlcwogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIuZmVuY2VzKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGhlYWRpbmcKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmhlYWRpbmcoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gaHIKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmhyKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGJsb2NrcXVvdGUKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmJsb2NrcXVvdGUoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gbGlzdAogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIubGlzdChzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICB0b2tlbnMucHVzaCh0b2tlbikKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBodG1sCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5odG1sKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGRlZgogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIuZGVmKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIGxhc3RUb2tlbiA9IHRva2Vuc1t0b2tlbnMubGVuZ3RoIC0gMV0KICAgICAgICBpZiAoCiAgICAgICAgICBsYXN0VG9rZW4gJiYKICAgICAgICAgIChsYXN0VG9rZW4udHlwZSA9PT0gInBhcmFncmFwaCIgfHwgbGFzdFRva2VuLnR5cGUgPT09ICJ0ZXh0IikKICAgICAgICApIHsKICAgICAgICAgIGxhc3RUb2tlbi5yYXcgKz0gIlxuIiArIHRva2VuLnJhdwogICAgICAgICAgbGFzdFRva2VuLnRleHQgKz0gIlxuIiArIHRva2VuLnJhdwogICAgICAgICAgdGhpcy5pbmxpbmVRdWV1ZVt0aGlzLmlubGluZVF1ZXVlLmxlbmd0aCAtIDFdLnNyYyA9IGxhc3RUb2tlbi50ZXh0CiAgICAgICAgfSBlbHNlIGlmICghdGhpcy50b2tlbnMubGlua3NbdG9rZW4udGFnXSkgewogICAgICAgICAgdGhpcy50b2tlbnMubGlua3NbdG9rZW4udGFnXSA9IHsKICAgICAgICAgICAgaHJlZjogdG9rZW4uaHJlZiwKICAgICAgICAgICAgdGl0bGU6IHRva2VuLnRpdGxlLAogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyB0YWJsZSAoZ2ZtKQogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIudGFibGUoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gbGhlYWRpbmcKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmxoZWFkaW5nKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIHRvcC1sZXZlbCBwYXJhZ3JhcGgKICAgICAgLy8gcHJldmVudCBwYXJhZ3JhcGggY29uc3VtaW5nIGV4dGVuc2lvbnMgYnkgY2xpcHBpbmcgJ3NyYycgdG8gZXh0ZW5zaW9uIHN0YXJ0CiAgICAgIGN1dFNyYyA9IHNyYwogICAgICBpZiAodGhpcy5vcHRpb25zLmV4dGVuc2lvbnMgJiYgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMuc3RhcnRCbG9jaykgewogICAgICAgIGxldCBzdGFydEluZGV4ID0gSW5maW5pdHkKICAgICAgICBjb25zdCB0ZW1wU3JjID0gc3JjLnNsaWNlKDEpCiAgICAgICAgbGV0IHRlbXBTdGFydAogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLnN0YXJ0QmxvY2suZm9yRWFjaChmdW5jdGlvbiAoZ2V0U3RhcnRJbmRleCkgewogICAgICAgICAgdGVtcFN0YXJ0ID0gZ2V0U3RhcnRJbmRleC5jYWxsKHsgbGV4ZXI6IHRoaXMgfSwgdGVtcFNyYykKICAgICAgICAgIGlmICh0eXBlb2YgdGVtcFN0YXJ0ID09PSAibnVtYmVyIiAmJiB0ZW1wU3RhcnQgPj0gMCkgewogICAgICAgICAgICBzdGFydEluZGV4ID0gTWF0aC5taW4oc3RhcnRJbmRleCwgdGVtcFN0YXJ0KQogICAgICAgICAgfQogICAgICAgIH0pCiAgICAgICAgaWYgKHN0YXJ0SW5kZXggPCBJbmZpbml0eSAmJiBzdGFydEluZGV4ID49IDApIHsKICAgICAgICAgIGN1dFNyYyA9IHNyYy5zdWJzdHJpbmcoMCwgc3RhcnRJbmRleCArIDEpCiAgICAgICAgfQogICAgICB9CiAgICAgIGlmICh0aGlzLnN0YXRlLnRvcCAmJiAodG9rZW4gPSB0aGlzLnRva2VuaXplci5wYXJhZ3JhcGgoY3V0U3JjKSkpIHsKICAgICAgICBsYXN0VG9rZW4gPSB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdCiAgICAgICAgaWYgKGxhc3RQYXJhZ3JhcGhDbGlwcGVkICYmIGxhc3RUb2tlbi50eXBlID09PSAicGFyYWdyYXBoIikgewogICAgICAgICAgbGFzdFRva2VuLnJhdyArPSAiXG4iICsgdG9rZW4ucmF3CiAgICAgICAgICBsYXN0VG9rZW4udGV4dCArPSAiXG4iICsgdG9rZW4udGV4dAogICAgICAgICAgdGhpcy5pbmxpbmVRdWV1ZS5wb3AoKQogICAgICAgICAgdGhpcy5pbmxpbmVRdWV1ZVt0aGlzLmlubGluZVF1ZXVlLmxlbmd0aCAtIDFdLnNyYyA9IGxhc3RUb2tlbi50ZXh0CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIH0KICAgICAgICBsYXN0UGFyYWdyYXBoQ2xpcHBlZCA9IGN1dFNyYy5sZW5ndGggIT09IHNyYy5sZW5ndGgKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gdGV4dAogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIudGV4dChzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICBsYXN0VG9rZW4gPSB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdCiAgICAgICAgaWYgKGxhc3RUb2tlbiAmJiBsYXN0VG9rZW4udHlwZSA9PT0gInRleHQiKSB7CiAgICAgICAgICBsYXN0VG9rZW4ucmF3ICs9ICJcbiIgKyB0b2tlbi5yYXcKICAgICAgICAgIGxhc3RUb2tlbi50ZXh0ICs9ICJcbiIgKyB0b2tlbi50ZXh0CiAgICAgICAgICB0aGlzLmlubGluZVF1ZXVlLnBvcCgpCiAgICAgICAgICB0aGlzLmlubGluZVF1ZXVlW3RoaXMuaW5saW5lUXVldWUubGVuZ3RoIC0gMV0uc3JjID0gbGFzdFRva2VuLnRleHQKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgfQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIGlmIChzcmMpIHsKICAgICAgICBjb25zdCBlcnJNc2cgPSAiSW5maW5pdGUgbG9vcCBvbiBieXRlOiAiICsgc3JjLmNoYXJDb2RlQXQoMCkKICAgICAgICBpZiAodGhpcy5vcHRpb25zLnNpbGVudCkgewogICAgICAgICAgY29uc29sZS5lcnJvcihlcnJNc2cpCiAgICAgICAgICBicmVhawogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZXJyTXNnKQogICAgICAgIH0KICAgICAgfQogICAgfQoKICAgIHRoaXMuc3RhdGUudG9wID0gdHJ1ZQogICAgcmV0dXJuIHRva2VucwogIH0KCiAgaW5saW5lKHNyYywgdG9rZW5zKSB7CiAgICB0aGlzLmlubGluZVF1ZXVlLnB1c2goeyBzcmMsIHRva2VucyB9KQogIH0KCiAgLyoqCiAgICogTGV4aW5nL0NvbXBpbGluZwogICAqLwogIGlubGluZVRva2VucyhzcmMsIHRva2VucyA9IFtdKSB7CiAgICBsZXQgdG9rZW4sIGxhc3RUb2tlbiwgY3V0U3JjCgogICAgLy8gU3RyaW5nIHdpdGggbGlua3MgbWFza2VkIHRvIGF2b2lkIGludGVyZmVyZW5jZSB3aXRoIGVtIGFuZCBzdHJvbmcKICAgIGxldCBtYXNrZWRTcmMgPSBzcmMKICAgIGxldCBtYXRjaAogICAgbGV0IGtlZXBQcmV2Q2hhciwgcHJldkNoYXIKCiAgICAvLyBNYXNrIG91dCByZWZsaW5rcwogICAgaWYgKHRoaXMudG9rZW5zLmxpbmtzKSB7CiAgICAgIGNvbnN0IGxpbmtzID0gT2JqZWN0LmtleXModGhpcy50b2tlbnMubGlua3MpCiAgICAgIGlmIChsaW5rcy5sZW5ndGggPiAwKSB7CiAgICAgICAgd2hpbGUgKAogICAgICAgICAgKG1hdGNoID0gdGhpcy50b2tlbml6ZXIucnVsZXMuaW5saW5lLnJlZmxpbmtTZWFyY2guZXhlYyhtYXNrZWRTcmMpKSAhPQogICAgICAgICAgbnVsbAogICAgICAgICkgewogICAgICAgICAgaWYgKAogICAgICAgICAgICBsaW5rcy5pbmNsdWRlcyhtYXRjaFswXS5zbGljZShtYXRjaFswXS5sYXN0SW5kZXhPZigiWyIpICsgMSwgLTEpKQogICAgICAgICAgKSB7CiAgICAgICAgICAgIG1hc2tlZFNyYyA9CiAgICAgICAgICAgICAgbWFza2VkU3JjLnNsaWNlKDAsIG1hdGNoLmluZGV4KSArCiAgICAgICAgICAgICAgIlsiICsKICAgICAgICAgICAgICByZXBlYXRTdHJpbmcoImEiLCBtYXRjaFswXS5sZW5ndGggLSAyKSArCiAgICAgICAgICAgICAgIl0iICsKICAgICAgICAgICAgICBtYXNrZWRTcmMuc2xpY2UoCiAgICAgICAgICAgICAgICB0aGlzLnRva2VuaXplci5ydWxlcy5pbmxpbmUucmVmbGlua1NlYXJjaC5sYXN0SW5kZXgKICAgICAgICAgICAgICApCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9CiAgICAvLyBNYXNrIG91dCBvdGhlciBibG9ja3MKICAgIHdoaWxlICgKICAgICAgKG1hdGNoID0gdGhpcy50b2tlbml6ZXIucnVsZXMuaW5saW5lLmJsb2NrU2tpcC5leGVjKG1hc2tlZFNyYykpICE9IG51bGwKICAgICkgewogICAgICBtYXNrZWRTcmMgPQogICAgICAgIG1hc2tlZFNyYy5zbGljZSgwLCBtYXRjaC5pbmRleCkgKwogICAgICAgICJbIiArCiAgICAgICAgcmVwZWF0U3RyaW5nKCJhIiwgbWF0Y2hbMF0ubGVuZ3RoIC0gMikgKwogICAgICAgICJdIiArCiAgICAgICAgbWFza2VkU3JjLnNsaWNlKHRoaXMudG9rZW5pemVyLnJ1bGVzLmlubGluZS5ibG9ja1NraXAubGFzdEluZGV4KQogICAgfQoKICAgIC8vIE1hc2sgb3V0IGVzY2FwZWQgZW0gJiBzdHJvbmcgZGVsaW1pdGVycwogICAgd2hpbGUgKAogICAgICAobWF0Y2ggPSB0aGlzLnRva2VuaXplci5ydWxlcy5pbmxpbmUuZXNjYXBlZEVtU3QuZXhlYyhtYXNrZWRTcmMpKSAhPSBudWxsCiAgICApIHsKICAgICAgbWFza2VkU3JjID0KICAgICAgICBtYXNrZWRTcmMuc2xpY2UoMCwgbWF0Y2guaW5kZXgpICsKICAgICAgICAiKysiICsKICAgICAgICBtYXNrZWRTcmMuc2xpY2UodGhpcy50b2tlbml6ZXIucnVsZXMuaW5saW5lLmVzY2FwZWRFbVN0Lmxhc3RJbmRleCkKICAgIH0KCiAgICB3aGlsZSAoc3JjKSB7CiAgICAgIGlmICgha2VlcFByZXZDaGFyKSB7CiAgICAgICAgcHJldkNoYXIgPSAiIgogICAgICB9CiAgICAgIGtlZXBQcmV2Q2hhciA9IGZhbHNlCgogICAgICAvLyBleHRlbnNpb25zCiAgICAgIGlmICgKICAgICAgICB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucyAmJgogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLmlubGluZSAmJgogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLmlubGluZS5zb21lKGV4dFRva2VuaXplciA9PiB7CiAgICAgICAgICBpZiAoKHRva2VuID0gZXh0VG9rZW5pemVyLmNhbGwoeyBsZXhlcjogdGhpcyB9LCBzcmMsIHRva2VucykpKSB7CiAgICAgICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgICAgIHJldHVybiB0cnVlCiAgICAgICAgICB9CiAgICAgICAgICByZXR1cm4gZmFsc2UKICAgICAgICB9KQogICAgICApIHsKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBlc2NhcGUKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmVzY2FwZShzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICB0b2tlbnMucHVzaCh0b2tlbikKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyB0YWcKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLnRhZyhzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICBsYXN0VG9rZW4gPSB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdCiAgICAgICAgaWYgKGxhc3RUb2tlbiAmJiB0b2tlbi50eXBlID09PSAidGV4dCIgJiYgbGFzdFRva2VuLnR5cGUgPT09ICJ0ZXh0IikgewogICAgICAgICAgbGFzdFRva2VuLnJhdyArPSB0b2tlbi5yYXcKICAgICAgICAgIGxhc3RUb2tlbi50ZXh0ICs9IHRva2VuLnRleHQKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgfQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGxpbmsKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmxpbmsoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gcmVmbGluaywgbm9saW5rCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5yZWZsaW5rKHNyYywgdGhpcy50b2tlbnMubGlua3MpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICBsYXN0VG9rZW4gPSB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdCiAgICAgICAgaWYgKGxhc3RUb2tlbiAmJiB0b2tlbi50eXBlID09PSAidGV4dCIgJiYgbGFzdFRva2VuLnR5cGUgPT09ICJ0ZXh0IikgewogICAgICAgICAgbGFzdFRva2VuLnJhdyArPSB0b2tlbi5yYXcKICAgICAgICAgIGxhc3RUb2tlbi50ZXh0ICs9IHRva2VuLnRleHQKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgfQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGVtICYgc3Ryb25nCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5lbVN0cm9uZyhzcmMsIG1hc2tlZFNyYywgcHJldkNoYXIpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICB0b2tlbnMucHVzaCh0b2tlbikKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBjb2RlCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5jb2Rlc3BhbihzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICB0b2tlbnMucHVzaCh0b2tlbikKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBicgogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIuYnIoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gZGVsIChnZm0pCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5kZWwoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gYXV0b2xpbmsKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmF1dG9saW5rKHNyYywgbWFuZ2xlKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gdXJsIChnZm0pCiAgICAgIGlmICghdGhpcy5zdGF0ZS5pbkxpbmsgJiYgKHRva2VuID0gdGhpcy50b2tlbml6ZXIudXJsKHNyYywgbWFuZ2xlKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gdGV4dAogICAgICAvLyBwcmV2ZW50IGlubGluZVRleHQgY29uc3VtaW5nIGV4dGVuc2lvbnMgYnkgY2xpcHBpbmcgJ3NyYycgdG8gZXh0ZW5zaW9uIHN0YXJ0CiAgICAgIGN1dFNyYyA9IHNyYwogICAgICBpZiAodGhpcy5vcHRpb25zLmV4dGVuc2lvbnMgJiYgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMuc3RhcnRJbmxpbmUpIHsKICAgICAgICBsZXQgc3RhcnRJbmRleCA9IEluZmluaXR5CiAgICAgICAgY29uc3QgdGVtcFNyYyA9IHNyYy5zbGljZSgxKQogICAgICAgIGxldCB0ZW1wU3RhcnQKICAgICAgICB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucy5zdGFydElubGluZS5mb3JFYWNoKGZ1bmN0aW9uIChnZXRTdGFydEluZGV4KSB7CiAgICAgICAgICB0ZW1wU3RhcnQgPSBnZXRTdGFydEluZGV4LmNhbGwoeyBsZXhlcjogdGhpcyB9LCB0ZW1wU3JjKQogICAgICAgICAgaWYgKHR5cGVvZiB0ZW1wU3RhcnQgPT09ICJudW1iZXIiICYmIHRlbXBTdGFydCA+PSAwKSB7CiAgICAgICAgICAgIHN0YXJ0SW5kZXggPSBNYXRoLm1pbihzdGFydEluZGV4LCB0ZW1wU3RhcnQpCiAgICAgICAgICB9CiAgICAgICAgfSkKICAgICAgICBpZiAoc3RhcnRJbmRleCA8IEluZmluaXR5ICYmIHN0YXJ0SW5kZXggPj0gMCkgewogICAgICAgICAgY3V0U3JjID0gc3JjLnN1YnN0cmluZygwLCBzdGFydEluZGV4ICsgMSkKICAgICAgICB9CiAgICAgIH0KICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmlubGluZVRleHQoY3V0U3JjLCBzbWFydHlwYW50cykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIGlmICh0b2tlbi5yYXcuc2xpY2UoLTEpICE9PSAiXyIpIHsKICAgICAgICAgIC8vIFRyYWNrIHByZXZDaGFyIGJlZm9yZSBzdHJpbmcgb2YgX19fXyBzdGFydGVkCiAgICAgICAgICBwcmV2Q2hhciA9IHRva2VuLnJhdy5zbGljZSgtMSkKICAgICAgICB9CiAgICAgICAga2VlcFByZXZDaGFyID0gdHJ1ZQogICAgICAgIGxhc3RUb2tlbiA9IHRva2Vuc1t0b2tlbnMubGVuZ3RoIC0gMV0KICAgICAgICBpZiAobGFzdFRva2VuICYmIGxhc3RUb2tlbi50eXBlID09PSAidGV4dCIpIHsKICAgICAgICAgIGxhc3RUb2tlbi5yYXcgKz0gdG9rZW4ucmF3CiAgICAgICAgICBsYXN0VG9rZW4udGV4dCArPSB0b2tlbi50ZXh0CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIH0KICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICBpZiAoc3JjKSB7CiAgICAgICAgY29uc3QgZXJyTXNnID0gIkluZmluaXRlIGxvb3Agb24gYnl0ZTogIiArIHNyYy5jaGFyQ29kZUF0KDApCiAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5zaWxlbnQpIHsKICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyTXNnKQogICAgICAgICAgYnJlYWsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGVyck1zZykKICAgICAgICB9CiAgICAgIH0KICAgIH0KCiAgICByZXR1cm4gdG9rZW5zCiAgfQp9CgovKioKICogUmVuZGVyZXIKICovCmNsYXNzIFJlbmRlcmVyIHsKICBjb25zdHJ1Y3RvcihvcHRpb25zKSB7CiAgICB0aGlzLm9wdGlvbnMgPSBvcHRpb25zIHx8IGRlZmF1bHRzCiAgfQoKICBjb2RlKGNvZGUsIGluZm9zdHJpbmcsIGVzY2FwZWQpIHsKICAgIGNvbnN0IGxhbmcgPSAoaW5mb3N0cmluZyB8fCAiIikubWF0Y2goL1xTKi8pWzBdCiAgICBpZiAodGhpcy5vcHRpb25zLmhpZ2hsaWdodCkgewogICAgICBjb25zdCBvdXQgPSB0aGlzLm9wdGlvbnMuaGlnaGxpZ2h0KGNvZGUsIGxhbmcpCiAgICAgIGlmIChvdXQgIT0gbnVsbCAmJiBvdXQgIT09IGNvZGUpIHsKICAgICAgICBlc2NhcGVkID0gdHJ1ZQogICAgICAgIGNvZGUgPSBvdXQKICAgICAgfQogICAgfQoKICAgIGNvZGUgPSBjb2RlLnJlcGxhY2UoL1xuJC8sICIiKSArICJcbiIKCiAgICBpZiAoIWxhbmcpIHsKICAgICAgcmV0dXJuICgKICAgICAgICAiPHByZT48Y29kZT4iICsKICAgICAgICAoZXNjYXBlZCA/IGNvZGUgOiBlc2NhcGUoY29kZSwgdHJ1ZSkpICsKICAgICAgICAiPC9jb2RlPjwvcHJlPlxuIgogICAgICApCiAgICB9CgogICAgcmV0dXJuICgKICAgICAgJzxwcmU+PGNvZGUgY2xhc3M9IicgKwogICAgICB0aGlzLm9wdGlvbnMubGFuZ1ByZWZpeCArCiAgICAgIGVzY2FwZShsYW5nLCB0cnVlKSArCiAgICAgICciPicgKwogICAgICAoZXNjYXBlZCA/IGNvZGUgOiBlc2NhcGUoY29kZSwgdHJ1ZSkpICsKICAgICAgIjwvY29kZT48L3ByZT5cbiIKICAgICkKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSBxdW90ZQogICAqLwogIGJsb2NrcXVvdGUocXVvdGUpIHsKICAgIHJldHVybiBgPGJsb2NrcXVvdGU+XG4ke3F1b3RlfTwvYmxvY2txdW90ZT5cbmAKICB9CgogIGh0bWwoaHRtbCkgewogICAgcmV0dXJuIGh0bWwKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAgICogQHBhcmFtIHtzdHJpbmd9IGxldmVsCiAgICogQHBhcmFtIHtzdHJpbmd9IHJhdwogICAqIEBwYXJhbSB7YW55fSBzbHVnZ2VyCiAgICovCiAgaGVhZGluZyh0ZXh0LCBsZXZlbCwgcmF3LCBzbHVnZ2VyKSB7CiAgICBpZiAodGhpcy5vcHRpb25zLmhlYWRlcklkcykgewogICAgICBjb25zdCBpZCA9IHRoaXMub3B0aW9ucy5oZWFkZXJQcmVmaXggKyBzbHVnZ2VyLnNsdWcocmF3KQogICAgICByZXR1cm4gYDxoJHtsZXZlbH0gaWQ9IiR7aWR9Ij4ke3RleHR9PC9oJHtsZXZlbH0+XG5gCiAgICB9CgogICAgLy8gaWdub3JlIElEcwogICAgcmV0dXJuIGA8aCR7bGV2ZWx9PiR7dGV4dH08L2gke2xldmVsfT5cbmAKICB9CgogIGhyKCkgewogICAgcmV0dXJuIHRoaXMub3B0aW9ucy54aHRtbCA/ICI8aHIvPlxuIiA6ICI8aHI+XG4iCiAgfQoKICBsaXN0KGJvZHksIG9yZGVyZWQsIHN0YXJ0KSB7CiAgICBjb25zdCB0eXBlID0gb3JkZXJlZCA/ICJvbCIgOiAidWwiLAogICAgICBzdGFydGF0dCA9IG9yZGVyZWQgJiYgc3RhcnQgIT09IDEgPyAnIHN0YXJ0PSInICsgc3RhcnQgKyAnIicgOiAiIgogICAgcmV0dXJuICI8IiArIHR5cGUgKyBzdGFydGF0dCArICI+XG4iICsgYm9keSArICI8LyIgKyB0eXBlICsgIj5cbiIKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAgICovCiAgbGlzdGl0ZW0odGV4dCkgewogICAgcmV0dXJuIGA8bGk+JHt0ZXh0fTwvbGk+XG5gCiAgfQoKICBjaGVja2JveChjaGVja2VkKSB7CiAgICByZXR1cm4gKAogICAgICAiPGlucHV0ICIgKwogICAgICAoY2hlY2tlZCA/ICdjaGVja2VkPSIiICcgOiAiIikgKwogICAgICAnZGlzYWJsZWQ9IiIgdHlwZT0iY2hlY2tib3giJyArCiAgICAgICh0aGlzLm9wdGlvbnMueGh0bWwgPyAiIC8iIDogIiIpICsKICAgICAgIj4gIgogICAgKQogIH0KCiAgLyoqCiAgICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICAgKi8KICBwYXJhZ3JhcGgodGV4dCkgewogICAgcmV0dXJuIGA8cD4ke3RleHR9PC9wPlxuYAogIH0KCiAgLyoqCiAgICogQHBhcmFtIHtzdHJpbmd9IGhlYWRlcgogICAqIEBwYXJhbSB7c3RyaW5nfSBib2R5CiAgICovCiAgdGFibGUoaGVhZGVyLCBib2R5KSB7CiAgICBpZiAoYm9keSkgYm9keSA9IGA8dGJvZHk+JHtib2R5fTwvdGJvZHk+YAoKICAgIHJldHVybiAoCiAgICAgICI8dGFibGU+XG4iICsgIjx0aGVhZD5cbiIgKyBoZWFkZXIgKyAiPC90aGVhZD5cbiIgKyBib2R5ICsgIjwvdGFibGU+XG4iCiAgICApCiAgfQoKICAvKioKICAgKiBAcGFyYW0ge3N0cmluZ30gY29udGVudAogICAqLwogIHRhYmxlcm93KGNvbnRlbnQpIHsKICAgIHJldHVybiBgPHRyPlxuJHtjb250ZW50fTwvdHI+XG5gCiAgfQoKICB0YWJsZWNlbGwoY29udGVudCwgZmxhZ3MpIHsKICAgIGNvbnN0IHR5cGUgPSBmbGFncy5oZWFkZXIgPyAidGgiIDogInRkIgogICAgY29uc3QgdGFnID0gZmxhZ3MuYWxpZ24gPyBgPCR7dHlwZX0gYWxpZ249IiR7ZmxhZ3MuYWxpZ259Ij5gIDogYDwke3R5cGV9PmAKICAgIHJldHVybiB0YWcgKyBjb250ZW50ICsgYDwvJHt0eXBlfT5cbmAKICB9CgogIC8qKgogICAqIHNwYW4gbGV2ZWwgcmVuZGVyZXIKICAgKiBAcGFyYW0ge3N0cmluZ30gdGV4dAogICAqLwogIHN0cm9uZyh0ZXh0KSB7CiAgICByZXR1cm4gYDxzdHJvbmc+JHt0ZXh0fTwvc3Ryb25nPmAKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAgICovCiAgZW0odGV4dCkgewogICAgcmV0dXJuIGA8ZW0+JHt0ZXh0fTwvZW0+YAogIH0KCiAgLyoqCiAgICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICAgKi8KICBjb2Rlc3Bhbih0ZXh0KSB7CiAgICByZXR1cm4gYDxjb2RlPiR7dGV4dH08L2NvZGU+YAogIH0KCiAgYnIoKSB7CiAgICByZXR1cm4gdGhpcy5vcHRpb25zLnhodG1sID8gIjxici8+IiA6ICI8YnI+IgogIH0KCiAgLyoqCiAgICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICAgKi8KICBkZWwodGV4dCkgewogICAgcmV0dXJuIGA8ZGVsPiR7dGV4dH08L2RlbD5gCiAgfQoKICAvKioKICAgKiBAcGFyYW0ge3N0cmluZ30gaHJlZgogICAqIEBwYXJhbSB7c3RyaW5nfSB0aXRsZQogICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAgICovCiAgbGluayhocmVmLCB0aXRsZSwgdGV4dCkgewogICAgaHJlZiA9IGNsZWFuVXJsKHRoaXMub3B0aW9ucy5zYW5pdGl6ZSwgdGhpcy5vcHRpb25zLmJhc2VVcmwsIGhyZWYpCiAgICBpZiAoaHJlZiA9PT0gbnVsbCkgewogICAgICByZXR1cm4gdGV4dAogICAgfQogICAgbGV0IG91dCA9ICc8YSBocmVmPSInICsgZXNjYXBlKGhyZWYpICsgJyInCiAgICBpZiAodGl0bGUpIHsKICAgICAgb3V0ICs9ICcgdGl0bGU9IicgKyB0aXRsZSArICciJwogICAgfQogICAgb3V0ICs9ICI+IiArIHRleHQgKyAiPC9hPiIKICAgIHJldHVybiBvdXQKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSBocmVmCiAgICogQHBhcmFtIHtzdHJpbmd9IHRpdGxlCiAgICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICAgKi8KICBpbWFnZShocmVmLCB0aXRsZSwgdGV4dCkgewogICAgaHJlZiA9IGNsZWFuVXJsKHRoaXMub3B0aW9ucy5zYW5pdGl6ZSwgdGhpcy5vcHRpb25zLmJhc2VVcmwsIGhyZWYpCiAgICBpZiAoaHJlZiA9PT0gbnVsbCkgewogICAgICByZXR1cm4gdGV4dAogICAgfQoKICAgIGxldCBvdXQgPSBgPGltZyBzcmM9IiR7aHJlZn0iIGFsdD0iJHt0ZXh0fSJgCiAgICBpZiAodGl0bGUpIHsKICAgICAgb3V0ICs9IGAgdGl0bGU9IiR7dGl0bGV9ImAKICAgIH0KICAgIG91dCArPSB0aGlzLm9wdGlvbnMueGh0bWwgPyAiLz4iIDogIj4iCiAgICByZXR1cm4gb3V0CiAgfQoKICB0ZXh0KHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQp9CgovKioKICogVGV4dFJlbmRlcmVyCiAqIHJldHVybnMgb25seSB0aGUgdGV4dHVhbCBwYXJ0IG9mIHRoZSB0b2tlbgogKi8KY2xhc3MgVGV4dFJlbmRlcmVyIHsKICAvLyBubyBuZWVkIGZvciBibG9jayBsZXZlbCByZW5kZXJlcnMKICBzdHJvbmcodGV4dCkgewogICAgcmV0dXJuIHRleHQKICB9CgogIGVtKHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICBjb2Rlc3Bhbih0ZXh0KSB7CiAgICByZXR1cm4gdGV4dAogIH0KCiAgZGVsKHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICBodG1sKHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICB0ZXh0KHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICBsaW5rKGhyZWYsIHRpdGxlLCB0ZXh0KSB7CiAgICByZXR1cm4gIiIgKyB0ZXh0CiAgfQoKICBpbWFnZShocmVmLCB0aXRsZSwgdGV4dCkgewogICAgcmV0dXJuICIiICsgdGV4dAogIH0KCiAgYnIoKSB7CiAgICByZXR1cm4gIiIKICB9Cn0KCi8qKgogKiBTbHVnZ2VyIGdlbmVyYXRlcyBoZWFkZXIgaWQKICovCmNsYXNzIFNsdWdnZXIgewogIGNvbnN0cnVjdG9yKCkgewogICAgdGhpcy5zZWVuID0ge30KICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSB2YWx1ZQogICAqLwogIHNlcmlhbGl6ZSh2YWx1ZSkgewogICAgcmV0dXJuICgKICAgICAgdmFsdWUKICAgICAgICAudG9Mb3dlckNhc2UoKQogICAgICAgIC50cmltKCkKICAgICAgICAvLyByZW1vdmUgaHRtbCB0YWdzCiAgICAgICAgLnJlcGxhY2UoLzxbIVwvYS16XS4qPz4vZ2ksICIiKQogICAgICAgIC8vIHJlbW92ZSB1bndhbnRlZCBjaGFycwogICAgICAgIC5yZXBsYWNlKAogICAgICAgICAgL1tcdTIwMDAtXHUyMDZGXHUyRTAwLVx1MkU3RlxcJyEiIyQlJigpKissLi86Ozw9Pj9AW1xdXmB7fH1+XS9nLAogICAgICAgICAgIiIKICAgICAgICApCiAgICAgICAgLnJlcGxhY2UoL1xzL2csICItIikKICAgICkKICB9CgogIC8qKgogICAqIEZpbmRzIHRoZSBuZXh0IHNhZmUgKHVuaXF1ZSkgc2x1ZyB0byB1c2UKICAgKiBAcGFyYW0ge3N0cmluZ30gb3JpZ2luYWxTbHVnCiAgICogQHBhcmFtIHtib29sZWFufSBpc0RyeVJ1bgogICAqLwogIGdldE5leHRTYWZlU2x1ZyhvcmlnaW5hbFNsdWcsIGlzRHJ5UnVuKSB7CiAgICBsZXQgc2x1ZyA9IG9yaWdpbmFsU2x1ZwogICAgbGV0IG9jY3VyZW5jZUFjY3VtdWxhdG9yID0gMAogICAgaWYgKHRoaXMuc2Vlbi5oYXNPd25Qcm9wZXJ0eShzbHVnKSkgewogICAgICBvY2N1cmVuY2VBY2N1bXVsYXRvciA9IHRoaXMuc2VlbltvcmlnaW5hbFNsdWddCiAgICAgIGRvIHsKICAgICAgICBvY2N1cmVuY2VBY2N1bXVsYXRvcisrCiAgICAgICAgc2x1ZyA9IG9yaWdpbmFsU2x1ZyArICItIiArIG9jY3VyZW5jZUFjY3VtdWxhdG9yCiAgICAgIH0gd2hpbGUgKHRoaXMuc2Vlbi5oYXNPd25Qcm9wZXJ0eShzbHVnKSkKICAgIH0KICAgIGlmICghaXNEcnlSdW4pIHsKICAgICAgdGhpcy5zZWVuW29yaWdpbmFsU2x1Z10gPSBvY2N1cmVuY2VBY2N1bXVsYXRvcgogICAgICB0aGlzLnNlZW5bc2x1Z10gPSAwCiAgICB9CiAgICByZXR1cm4gc2x1ZwogIH0KCiAgLyoqCiAgICogQ29udmVydCBzdHJpbmcgdG8gdW5pcXVlIGlkCiAgICogQHBhcmFtIHtvYmplY3R9IFtvcHRpb25zXQogICAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMuZHJ5cnVuXSBHZW5lcmF0ZXMgdGhlIG5leHQgdW5pcXVlIHNsdWcgd2l0aG91dAogICAqIHVwZGF0aW5nIHRoZSBpbnRlcm5hbCBhY2N1bXVsYXRvci4KICAgKi8KICBzbHVnKHZhbHVlLCBvcHRpb25zID0ge30pIHsKICAgIGNvbnN0IHNsdWcgPSB0aGlzLnNlcmlhbGl6ZSh2YWx1ZSkKICAgIHJldHVybiB0aGlzLmdldE5leHRTYWZlU2x1ZyhzbHVnLCBvcHRpb25zLmRyeXJ1bikKICB9Cn0KCi8qKgogKiBQYXJzaW5nICYgQ29tcGlsaW5nCiAqLwpjbGFzcyBQYXJzZXIgewogIGNvbnN0cnVjdG9yKG9wdGlvbnMpIHsKICAgIHRoaXMub3B0aW9ucyA9IG9wdGlvbnMgfHwgZGVmYXVsdHMKICAgIHRoaXMub3B0aW9ucy5yZW5kZXJlciA9IHRoaXMub3B0aW9ucy5yZW5kZXJlciB8fCBuZXcgUmVuZGVyZXIoKQogICAgdGhpcy5yZW5kZXJlciA9IHRoaXMub3B0aW9ucy5yZW5kZXJlcgogICAgdGhpcy5yZW5kZXJlci5vcHRpb25zID0gdGhpcy5vcHRpb25zCiAgICB0aGlzLnRleHRSZW5kZXJlciA9IG5ldyBUZXh0UmVuZGVyZXIoKQogICAgdGhpcy5zbHVnZ2VyID0gbmV3IFNsdWdnZXIoKQogIH0KCiAgLyoqCiAgICogU3RhdGljIFBhcnNlIE1ldGhvZAogICAqLwogIHN0YXRpYyBwYXJzZSh0b2tlbnMsIG9wdGlvbnMpIHsKICAgIGNvbnN0IHBhcnNlciA9IG5ldyBQYXJzZXIob3B0aW9ucykKICAgIHJldHVybiBwYXJzZXIucGFyc2UodG9rZW5zKQogIH0KCiAgLyoqCiAgICogU3RhdGljIFBhcnNlIElubGluZSBNZXRob2QKICAgKi8KICBzdGF0aWMgcGFyc2VJbmxpbmUodG9rZW5zLCBvcHRpb25zKSB7CiAgICBjb25zdCBwYXJzZXIgPSBuZXcgUGFyc2VyKG9wdGlvbnMpCiAgICByZXR1cm4gcGFyc2VyLnBhcnNlSW5saW5lKHRva2VucykKICB9CgogIC8qKgogICAqIFBhcnNlIExvb3AKICAgKi8KICBwYXJzZSh0b2tlbnMsIHRvcCA9IHRydWUpIHsKICAgIGxldCBvdXQgPSAiIiwKICAgICAgaSwKICAgICAgaiwKICAgICAgaywKICAgICAgbDIsCiAgICAgIGwzLAogICAgICByb3csCiAgICAgIGNlbGwsCiAgICAgIGhlYWRlciwKICAgICAgYm9keSwKICAgICAgdG9rZW4sCiAgICAgIG9yZGVyZWQsCiAgICAgIHN0YXJ0LAogICAgICBsb29zZSwKICAgICAgaXRlbUJvZHksCiAgICAgIGl0ZW0sCiAgICAgIGNoZWNrZWQsCiAgICAgIHRhc2ssCiAgICAgIGNoZWNrYm94LAogICAgICByZXQKCiAgICBjb25zdCBsID0gdG9rZW5zLmxlbmd0aAogICAgZm9yIChpID0gMDsgaSA8IGw7IGkrKykgewogICAgICB0b2tlbiA9IHRva2Vuc1tpXQoKICAgICAgLy8gUnVuIGFueSByZW5kZXJlciBleHRlbnNpb25zCiAgICAgIGlmICgKICAgICAgICB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucyAmJgogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLnJlbmRlcmVycyAmJgogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLnJlbmRlcmVyc1t0b2tlbi50eXBlXQogICAgICApIHsKICAgICAgICByZXQgPSB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucy5yZW5kZXJlcnNbdG9rZW4udHlwZV0uY2FsbCgKICAgICAgICAgIHsgcGFyc2VyOiB0aGlzIH0sCiAgICAgICAgICB0b2tlbgogICAgICAgICkKICAgICAgICBpZiAoCiAgICAgICAgICByZXQgIT09IGZhbHNlIHx8CiAgICAgICAgICAhWwogICAgICAgICAgICAic3BhY2UiLAogICAgICAgICAgICAiaHIiLAogICAgICAgICAgICAiaGVhZGluZyIsCiAgICAgICAgICAgICJjb2RlIiwKICAgICAgICAgICAgInRhYmxlIiwKICAgICAgICAgICAgImJsb2NrcXVvdGUiLAogICAgICAgICAgICAibGlzdCIsCiAgICAgICAgICAgICJodG1sIiwKICAgICAgICAgICAgInBhcmFncmFwaCIsCiAgICAgICAgICAgICJ0ZXh0IiwKICAgICAgICAgIF0uaW5jbHVkZXModG9rZW4udHlwZSkKICAgICAgICApIHsKICAgICAgICAgIG91dCArPSByZXQgfHwgIiIKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICB9CgogICAgICBzd2l0Y2ggKHRva2VuLnR5cGUpIHsKICAgICAgICBjYXNlICJzcGFjZSI6IHsKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICAgIGNhc2UgImhyIjogewogICAgICAgICAgb3V0ICs9IHRoaXMucmVuZGVyZXIuaHIoKQogICAgICAgICAgY29udGludWUKICAgICAgICB9CiAgICAgICAgY2FzZSAiaGVhZGluZyI6IHsKICAgICAgICAgIG91dCArPSB0aGlzLnJlbmRlcmVyLmhlYWRpbmcoCiAgICAgICAgICAgIHRoaXMucGFyc2VJbmxpbmUodG9rZW4udG9rZW5zKSwKICAgICAgICAgICAgdG9rZW4uZGVwdGgsCiAgICAgICAgICAgIHVuZXNjYXBlKHRoaXMucGFyc2VJbmxpbmUodG9rZW4udG9rZW5zLCB0aGlzLnRleHRSZW5kZXJlcikpLAogICAgICAgICAgICB0aGlzLnNsdWdnZXIKICAgICAgICAgICkKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICAgIGNhc2UgImNvZGUiOiB7CiAgICAgICAgICBvdXQgKz0gdGhpcy5yZW5kZXJlci5jb2RlKHRva2VuLnRleHQsIHRva2VuLmxhbmcsIHRva2VuLmVzY2FwZWQpCiAgICAgICAgICBjb250aW51ZQogICAgICAgIH0KICAgICAgICBjYXNlICJ0YWJsZSI6IHsKICAgICAgICAgIGhlYWRlciA9ICIiCgogICAgICAgICAgLy8gaGVhZGVyCiAgICAgICAgICBjZWxsID0gIiIKICAgICAgICAgIGwyID0gdG9rZW4uaGVhZGVyLmxlbmd0aAogICAgICAgICAgZm9yIChqID0gMDsgaiA8IGwyOyBqKyspIHsKICAgICAgICAgICAgY2VsbCArPSB0aGlzLnJlbmRlcmVyLnRhYmxlY2VsbCgKICAgICAgICAgICAgICB0aGlzLnBhcnNlSW5saW5lKHRva2VuLmhlYWRlcltqXS50b2tlbnMpLAogICAgICAgICAgICAgIHsgaGVhZGVyOiB0cnVlLCBhbGlnbjogdG9rZW4uYWxpZ25bal0gfQogICAgICAgICAgICApCiAgICAgICAgICB9CiAgICAgICAgICBoZWFkZXIgKz0gdGhpcy5yZW5kZXJlci50YWJsZXJvdyhjZWxsKQoKICAgICAgICAgIGJvZHkgPSAiIgogICAgICAgICAgbDIgPSB0b2tlbi5yb3dzLmxlbmd0aAogICAgICAgICAgZm9yIChqID0gMDsgaiA8IGwyOyBqKyspIHsKICAgICAgICAgICAgcm93ID0gdG9rZW4ucm93c1tqXQoKICAgICAgICAgICAgY2VsbCA9ICIiCiAgICAgICAgICAgIGwzID0gcm93Lmxlbmd0aAogICAgICAgICAgICBmb3IgKGsgPSAwOyBrIDwgbDM7IGsrKykgewogICAgICAgICAgICAgIGNlbGwgKz0gdGhpcy5yZW5kZXJlci50YWJsZWNlbGwodGhpcy5wYXJzZUlubGluZShyb3dba10udG9rZW5zKSwgewogICAgICAgICAgICAgICAgaGVhZGVyOiBmYWxzZSwKICAgICAgICAgICAgICAgIGFsaWduOiB0b2tlbi5hbGlnbltrXSwKICAgICAgICAgICAgICB9KQogICAgICAgICAgICB9CgogICAgICAgICAgICBib2R5ICs9IHRoaXMucmVuZGVyZXIudGFibGVyb3coY2VsbCkKICAgICAgICAgIH0KICAgICAgICAgIG91dCArPSB0aGlzLnJlbmRlcmVyLnRhYmxlKGhlYWRlciwgYm9keSkKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICAgIGNhc2UgImJsb2NrcXVvdGUiOiB7CiAgICAgICAgICBib2R5ID0gdGhpcy5wYXJzZSh0b2tlbi50b2tlbnMpCiAgICAgICAgICBvdXQgKz0gdGhpcy5yZW5kZXJlci5ibG9ja3F1b3RlKGJvZHkpCiAgICAgICAgICBjb250aW51ZQogICAgICAgIH0KICAgICAgICBjYXNlICJsaXN0IjogewogICAgICAgICAgb3JkZXJlZCA9IHRva2VuLm9yZGVyZWQKICAgICAgICAgIHN0YXJ0ID0gdG9rZW4uc3RhcnQKICAgICAgICAgIGxvb3NlID0gdG9rZW4ubG9vc2UKICAgICAgICAgIGwyID0gdG9rZW4uaXRlbXMubGVuZ3RoCgogICAgICAgICAgYm9keSA9ICIiCiAgICAgICAgICBmb3IgKGogPSAwOyBqIDwgbDI7IGorKykgewogICAgICAgICAgICBpdGVtID0gdG9rZW4uaXRlbXNbal0KICAgICAgICAgICAgY2hlY2tlZCA9IGl0ZW0uY2hlY2tlZAogICAgICAgICAgICB0YXNrID0gaXRlbS50YXNrCgogICAgICAgICAgICBpdGVtQm9keSA9ICIiCiAgICAgICAgICAgIGlmIChpdGVtLnRhc2spIHsKICAgICAgICAgICAgICBjaGVja2JveCA9IHRoaXMucmVuZGVyZXIuY2hlY2tib3goY2hlY2tlZCkKICAgICAgICAgICAgICBpZiAobG9vc2UpIHsKICAgICAgICAgICAgICAgIGlmICgKICAgICAgICAgICAgICAgICAgaXRlbS50b2tlbnMubGVuZ3RoID4gMCAmJgogICAgICAgICAgICAgICAgICBpdGVtLnRva2Vuc1swXS50eXBlID09PSAicGFyYWdyYXBoIgogICAgICAgICAgICAgICAgKSB7CiAgICAgICAgICAgICAgICAgIGl0ZW0udG9rZW5zWzBdLnRleHQgPSBjaGVja2JveCArICIgIiArIGl0ZW0udG9rZW5zWzBdLnRleHQKICAgICAgICAgICAgICAgICAgaWYgKAogICAgICAgICAgICAgICAgICAgIGl0ZW0udG9rZW5zWzBdLnRva2VucyAmJgogICAgICAgICAgICAgICAgICAgIGl0ZW0udG9rZW5zWzBdLnRva2Vucy5sZW5ndGggPiAwICYmCiAgICAgICAgICAgICAgICAgICAgaXRlbS50b2tlbnNbMF0udG9rZW5zWzBdLnR5cGUgPT09ICJ0ZXh0IgogICAgICAgICAgICAgICAgICApIHsKICAgICAgICAgICAgICAgICAgICBpdGVtLnRva2Vuc1swXS50b2tlbnNbMF0udGV4dCA9CiAgICAgICAgICAgICAgICAgICAgICBjaGVja2JveCArICIgIiArIGl0ZW0udG9rZW5zWzBdLnRva2Vuc1swXS50ZXh0CiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgIGl0ZW0udG9rZW5zLnVuc2hpZnQoewogICAgICAgICAgICAgICAgICAgIHR5cGU6ICJ0ZXh0IiwKICAgICAgICAgICAgICAgICAgICB0ZXh0OiBjaGVja2JveCwKICAgICAgICAgICAgICAgICAgfSkKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgaXRlbUJvZHkgKz0gY2hlY2tib3gKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGl0ZW1Cb2R5ICs9IHRoaXMucGFyc2UoaXRlbS50b2tlbnMsIGxvb3NlKQogICAgICAgICAgICBib2R5ICs9IHRoaXMucmVuZGVyZXIubGlzdGl0ZW0oaXRlbUJvZHksIHRhc2ssIGNoZWNrZWQpCiAgICAgICAgICB9CgogICAgICAgICAgb3V0ICs9IHRoaXMucmVuZGVyZXIubGlzdChib2R5LCBvcmRlcmVkLCBzdGFydCkKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICAgIGNhc2UgImh0bWwiOiB7CiAgICAgICAgICAvLyBUT0RPIHBhcnNlIGlubGluZSBjb250ZW50IGlmIHBhcmFtZXRlciBtYXJrZG93bj0xCiAgICAgICAgICBvdXQgKz0gdGhpcy5yZW5kZXJlci5odG1sKHRva2VuLnRleHQpCiAgICAgICAgICBjb250aW51ZQogICAgICAgIH0KICAgICAgICBjYXNlICJwYXJhZ3JhcGgiOiB7CiAgICAgICAgICBvdXQgKz0gdGhpcy5yZW5kZXJlci5wYXJhZ3JhcGgodGhpcy5wYXJzZUlubGluZSh0b2tlbi50b2tlbnMpKQogICAgICAgICAgY29udGludWUKICAgICAgICB9CiAgICAgICAgY2FzZSAidGV4dCI6IHsKICAgICAgICAgIGJvZHkgPSB0b2tlbi50b2tlbnMgPyB0aGlzLnBhcnNlSW5saW5lKHRva2VuLnRva2VucykgOiB0b2tlbi50ZXh0CiAgICAgICAgICB3aGlsZSAoaSArIDEgPCBsICYmIHRva2Vuc1tpICsgMV0udHlwZSA9PT0gInRleHQiKSB7CiAgICAgICAgICAgIHRva2VuID0gdG9rZW5zWysraV0KICAgICAgICAgICAgYm9keSArPQogICAgICAgICAgICAgICJcbiIgKwogICAgICAgICAgICAgICh0b2tlbi50b2tlbnMgPyB0aGlzLnBhcnNlSW5saW5lKHRva2VuLnRva2VucykgOiB0b2tlbi50ZXh0KQogICAgICAgICAgfQogICAgICAgICAgb3V0ICs9IHRvcCA/IHRoaXMucmVuZGVyZXIucGFyYWdyYXBoKGJvZHkpIDogYm9keQogICAgICAgICAgY29udGludWUKICAgICAgICB9CgogICAgICAgIGRlZmF1bHQ6IHsKICAgICAgICAgIGNvbnN0IGVyck1zZyA9ICdUb2tlbiB3aXRoICInICsgdG9rZW4udHlwZSArICciIHR5cGUgd2FzIG5vdCBmb3VuZC4nCiAgICAgICAgICBpZiAodGhpcy5vcHRpb25zLnNpbGVudCkgewogICAgICAgICAgICBjb25zb2xlLmVycm9yKGVyck1zZykKICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZXJyTXNnKQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfQoKICAgIHJldHVybiBvdXQKICB9CgogIC8qKgogICAqIFBhcnNlIElubGluZSBUb2tlbnMKICAgKi8KICBwYXJzZUlubGluZSh0b2tlbnMsIHJlbmRlcmVyKSB7CiAgICByZW5kZXJlciA9IHJlbmRlcmVyIHx8IHRoaXMucmVuZGVyZXIKICAgIGxldCBvdXQgPSAiIiwKICAgICAgaSwKICAgICAgdG9rZW4sCiAgICAgIHJldAoKICAgIGNvbnN0IGwgPSB0b2tlbnMubGVuZ3RoCiAgICBmb3IgKGkgPSAwOyBpIDwgbDsgaSsrKSB7CiAgICAgIHRva2VuID0gdG9rZW5zW2ldCgogICAgICAvLyBSdW4gYW55IHJlbmRlcmVyIGV4dGVuc2lvbnMKICAgICAgaWYgKAogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zICYmCiAgICAgICAgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMucmVuZGVyZXJzICYmCiAgICAgICAgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMucmVuZGVyZXJzW3Rva2VuLnR5cGVdCiAgICAgICkgewogICAgICAgIHJldCA9IHRoaXMub3B0aW9ucy5leHRlbnNpb25zLnJlbmRlcmVyc1t0b2tlbi50eXBlXS5jYWxsKAogICAgICAgICAgeyBwYXJzZXI6IHRoaXMgfSwKICAgICAgICAgIHRva2VuCiAgICAgICAgKQogICAgICAgIGlmICgKICAgICAgICAgIHJldCAhPT0gZmFsc2UgfHwKICAgICAgICAgICFbCiAgICAgICAgICAgICJlc2NhcGUiLAogICAgICAgICAgICAiaHRtbCIsCiAgICAgICAgICAgICJsaW5rIiwKICAgICAgICAgICAgImltYWdlIiwKICAgICAgICAgICAgInN0cm9uZyIsCiAgICAgICAgICAgICJlbSIsCiAgICAgICAgICAgICJjb2Rlc3BhbiIsCiAgICAgICAgICAgICJiciIsCiAgICAgICAgICAgICJkZWwiLAogICAgICAgICAgICAidGV4dCIsCiAgICAgICAgICBdLmluY2x1ZGVzKHRva2VuLnR5cGUpCiAgICAgICAgKSB7CiAgICAgICAgICBvdXQgKz0gcmV0IHx8ICIiCiAgICAgICAgICBjb250aW51ZQogICAgICAgIH0KICAgICAgfQoKICAgICAgc3dpdGNoICh0b2tlbi50eXBlKSB7CiAgICAgICAgY2FzZSAiZXNjYXBlIjogewogICAgICAgICAgb3V0ICs9IHJlbmRlcmVyLnRleHQodG9rZW4udGV4dCkKICAgICAgICAgIGJyZWFrCiAgICAgICAgfQogICAgICAgIGNhc2UgImh0bWwiOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIuaHRtbCh0b2tlbi50ZXh0KQogICAgICAgICAgYnJlYWsKICAgICAgICB9CiAgICAgICAgY2FzZSAibGluayI6IHsKICAgICAgICAgIG91dCArPSByZW5kZXJlci5saW5rKAogICAgICAgICAgICB0b2tlbi5ocmVmLAogICAgICAgICAgICB0b2tlbi50aXRsZSwKICAgICAgICAgICAgdGhpcy5wYXJzZUlubGluZSh0b2tlbi50b2tlbnMsIHJlbmRlcmVyKQogICAgICAgICAgKQogICAgICAgICAgYnJlYWsKICAgICAgICB9CiAgICAgICAgY2FzZSAiaW1hZ2UiOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIuaW1hZ2UodG9rZW4uaHJlZiwgdG9rZW4udGl0bGUsIHRva2VuLnRleHQpCiAgICAgICAgICBicmVhawogICAgICAgIH0KICAgICAgICBjYXNlICJzdHJvbmciOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIuc3Ryb25nKHRoaXMucGFyc2VJbmxpbmUodG9rZW4udG9rZW5zLCByZW5kZXJlcikpCiAgICAgICAgICBicmVhawogICAgICAgIH0KICAgICAgICBjYXNlICJlbSI6IHsKICAgICAgICAgIG91dCArPSByZW5kZXJlci5lbSh0aGlzLnBhcnNlSW5saW5lKHRva2VuLnRva2VucywgcmVuZGVyZXIpKQogICAgICAgICAgYnJlYWsKICAgICAgICB9CiAgICAgICAgY2FzZSAiY29kZXNwYW4iOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIuY29kZXNwYW4odG9rZW4udGV4dCkKICAgICAgICAgIGJyZWFrCiAgICAgICAgfQogICAgICAgIGNhc2UgImJyIjogewogICAgICAgICAgb3V0ICs9IHJlbmRlcmVyLmJyKCkKICAgICAgICAgIGJyZWFrCiAgICAgICAgfQogICAgICAgIGNhc2UgImRlbCI6IHsKICAgICAgICAgIG91dCArPSByZW5kZXJlci5kZWwodGhpcy5wYXJzZUlubGluZSh0b2tlbi50b2tlbnMsIHJlbmRlcmVyKSkKICAgICAgICAgIGJyZWFrCiAgICAgICAgfQogICAgICAgIGNhc2UgInRleHQiOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIudGV4dCh0b2tlbi50ZXh0KQogICAgICAgICAgYnJlYWsKICAgICAgICB9CiAgICAgICAgZGVmYXVsdDogewogICAgICAgICAgY29uc3QgZXJyTXNnID0gJ1Rva2VuIHdpdGggIicgKyB0b2tlbi50eXBlICsgJyIgdHlwZSB3YXMgbm90IGZvdW5kLicKICAgICAgICAgIGlmICh0aGlzLm9wdGlvbnMuc2lsZW50KSB7CiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyTXNnKQogICAgICAgICAgICByZXR1cm4KICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihlcnJNc2cpCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9CiAgICByZXR1cm4gb3V0CiAgfQp9CgovKioKICogTWFya2VkCiAqLwpmdW5jdGlvbiBtYXJrZWQoc3JjLCBvcHQsIGNhbGxiYWNrKSB7CiAgLy8gdGhyb3cgZXJyb3IgaW4gY2FzZSBvZiBub24gc3RyaW5nIGlucHV0CiAgaWYgKHR5cGVvZiBzcmMgPT09ICJ1bmRlZmluZWQiIHx8IHNyYyA9PT0gbnVsbCkgewogICAgdGhyb3cgbmV3IEVycm9yKCJtYXJrZWQoKTogaW5wdXQgcGFyYW1ldGVyIGlzIHVuZGVmaW5lZCBvciBudWxsIikKICB9CiAgaWYgKHR5cGVvZiBzcmMgIT09ICJzdHJpbmciKSB7CiAgICB0aHJvdyBuZXcgRXJyb3IoCiAgICAgICJtYXJrZWQoKTogaW5wdXQgcGFyYW1ldGVyIGlzIG9mIHR5cGUgIiArCiAgICAgICAgT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKHNyYykgKwogICAgICAgICIsIHN0cmluZyBleHBlY3RlZCIKICAgICkKICB9CgogIGlmICh0eXBlb2Ygb3B0ID09PSAiZnVuY3Rpb24iKSB7CiAgICBjYWxsYmFjayA9IG9wdAogICAgb3B0ID0gbnVsbAogIH0KCiAgb3B0ID0gbWVyZ2Uoe30sIG1hcmtlZC5kZWZhdWx0cywgb3B0IHx8IHt9KQogIGNoZWNrU2FuaXRpemVEZXByZWNhdGlvbihvcHQpCgogIGlmIChjYWxsYmFjaykgewogICAgY29uc3QgaGlnaGxpZ2h0ID0gb3B0LmhpZ2hsaWdodAogICAgbGV0IHRva2VucwoKICAgIHRyeSB7CiAgICAgIHRva2VucyA9IExleGVyLmxleChzcmMsIG9wdCkKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgcmV0dXJuIGNhbGxiYWNrKGUpCiAgICB9CgogICAgY29uc3QgZG9uZSA9IGZ1bmN0aW9uIChlcnIpIHsKICAgICAgbGV0IG91dAoKICAgICAgaWYgKCFlcnIpIHsKICAgICAgICB0cnkgewogICAgICAgICAgaWYgKG9wdC53YWxrVG9rZW5zKSB7CiAgICAgICAgICAgIG1hcmtlZC53YWxrVG9rZW5zKHRva2Vucywgb3B0LndhbGtUb2tlbnMpCiAgICAgICAgICB9CiAgICAgICAgICBvdXQgPSBQYXJzZXIucGFyc2UodG9rZW5zLCBvcHQpCiAgICAgICAgfSBjYXRjaCAoZSkgewogICAgICAgICAgZXJyID0gZQogICAgICAgIH0KICAgICAgfQoKICAgICAgb3B0LmhpZ2hsaWdodCA9IGhpZ2hsaWdodAoKICAgICAgcmV0dXJuIGVyciA/IGNhbGxiYWNrKGVycikgOiBjYWxsYmFjayhudWxsLCBvdXQpCiAgICB9CgogICAgaWYgKCFoaWdobGlnaHQgfHwgaGlnaGxpZ2h0Lmxlbmd0aCA8IDMpIHsKICAgICAgcmV0dXJuIGRvbmUoKQogICAgfQoKICAgIGRlbGV0ZSBvcHQuaGlnaGxpZ2h0CgogICAgaWYgKCF0b2tlbnMubGVuZ3RoKSByZXR1cm4gZG9uZSgpCgogICAgbGV0IHBlbmRpbmcgPSAwCiAgICBtYXJrZWQud2Fsa1Rva2Vucyh0b2tlbnMsIGZ1bmN0aW9uICh0b2tlbikgewogICAgICBpZiAodG9rZW4udHlwZSA9PT0gImNvZGUiKSB7CiAgICAgICAgcGVuZGluZysrCiAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7CiAgICAgICAgICBoaWdobGlnaHQodG9rZW4udGV4dCwgdG9rZW4ubGFuZywgZnVuY3Rpb24gKGVyciwgY29kZSkgewogICAgICAgICAgICBpZiAoZXJyKSB7CiAgICAgICAgICAgICAgcmV0dXJuIGRvbmUoZXJyKQogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmIChjb2RlICE9IG51bGwgJiYgY29kZSAhPT0gdG9rZW4udGV4dCkgewogICAgICAgICAgICAgIHRva2VuLnRleHQgPSBjb2RlCiAgICAgICAgICAgICAgdG9rZW4uZXNjYXBlZCA9IHRydWUKICAgICAgICAgICAgfQoKICAgICAgICAgICAgcGVuZGluZy0tCiAgICAgICAgICAgIGlmIChwZW5kaW5nID09PSAwKSB7CiAgICAgICAgICAgICAgZG9uZSgpCiAgICAgICAgICAgIH0KICAgICAgICAgIH0pCiAgICAgICAgfSwgMCkKICAgICAgfQogICAgfSkKCiAgICBpZiAocGVuZGluZyA9PT0gMCkgewogICAgICBkb25lKCkKICAgIH0KCiAgICByZXR1cm4KICB9CgogIHRyeSB7CiAgICBjb25zdCB0b2tlbnMgPSBMZXhlci5sZXgoc3JjLCBvcHQpCiAgICBpZiAob3B0LndhbGtUb2tlbnMpIHsKICAgICAgbWFya2VkLndhbGtUb2tlbnModG9rZW5zLCBvcHQud2Fsa1Rva2VucykKICAgIH0KICAgIHJldHVybiBQYXJzZXIucGFyc2UodG9rZW5zLCBvcHQpCiAgfSBjYXRjaCAoZSkgewogICAgZS5tZXNzYWdlICs9ICJcblBsZWFzZSByZXBvcnQgdGhpcyB0byBodHRwczovL2dpdGh1Yi5jb20vbWFya2VkanMvbWFya2VkLiIKICAgIGlmIChvcHQuc2lsZW50KSB7CiAgICAgIHJldHVybiAoCiAgICAgICAgIjxwPkFuIGVycm9yIG9jY3VycmVkOjwvcD48cHJlPiIgKwogICAgICAgIGVzY2FwZShlLm1lc3NhZ2UgKyAiIiwgdHJ1ZSkgKwogICAgICAgICI8L3ByZT4iCiAgICAgICkKICAgIH0KICAgIHRocm93IGUKICB9Cn0KCi8qKgogKiBPcHRpb25zCiAqLwoKbWFya2VkLm9wdGlvbnMgPSBtYXJrZWQuc2V0T3B0aW9ucyA9IGZ1bmN0aW9uIChvcHQpIHsKICBtZXJnZShtYXJrZWQuZGVmYXVsdHMsIG9wdCkKICBjaGFuZ2VEZWZhdWx0cyhtYXJrZWQuZGVmYXVsdHMpCiAgcmV0dXJuIG1hcmtlZAp9CgptYXJrZWQuZ2V0RGVmYXVsdHMgPSBnZXREZWZhdWx0cwoKbWFya2VkLmRlZmF1bHRzID0gZGVmYXVsdHMKCi8qKgogKiBVc2UgRXh0ZW5zaW9uCiAqLwoKbWFya2VkLnVzZSA9IGZ1bmN0aW9uICguLi5hcmdzKSB7CiAgY29uc3Qgb3B0cyA9IG1lcmdlKHt9LCAuLi5hcmdzKQogIGNvbnN0IGV4dGVuc2lvbnMgPSBtYXJrZWQuZGVmYXVsdHMuZXh0ZW5zaW9ucyB8fCB7CiAgICByZW5kZXJlcnM6IHt9LAogICAgY2hpbGRUb2tlbnM6IHt9LAogIH0KICBsZXQgaGFzRXh0ZW5zaW9ucwoKICBhcmdzLmZvckVhY2gocGFjayA9PiB7CiAgICAvLyA9PS0tIFBhcnNlICJhZGRvbiIgZXh0ZW5zaW9ucyAtLT09IC8vCiAgICBpZiAocGFjay5leHRlbnNpb25zKSB7CiAgICAgIGhhc0V4dGVuc2lvbnMgPSB0cnVlCiAgICAgIHBhY2suZXh0ZW5zaW9ucy5mb3JFYWNoKGV4dCA9PiB7CiAgICAgICAgaWYgKCFleHQubmFtZSkgewogICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCJleHRlbnNpb24gbmFtZSByZXF1aXJlZCIpCiAgICAgICAgfQogICAgICAgIGlmIChleHQucmVuZGVyZXIpIHsKICAgICAgICAgIC8vIFJlbmRlcmVyIGV4dGVuc2lvbnMKICAgICAgICAgIGNvbnN0IHByZXZSZW5kZXJlciA9IGV4dGVuc2lvbnMucmVuZGVyZXJzCiAgICAgICAgICAgID8gZXh0ZW5zaW9ucy5yZW5kZXJlcnNbZXh0Lm5hbWVdCiAgICAgICAgICAgIDogbnVsbAogICAgICAgICAgaWYgKHByZXZSZW5kZXJlcikgewogICAgICAgICAgICAvLyBSZXBsYWNlIGV4dGVuc2lvbiB3aXRoIGZ1bmMgdG8gcnVuIG5ldyBleHRlbnNpb24gYnV0IGZhbGwgYmFjayBpZiBmYWxzZQogICAgICAgICAgICBleHRlbnNpb25zLnJlbmRlcmVyc1tleHQubmFtZV0gPSBmdW5jdGlvbiAoLi4uYXJncykgewogICAgICAgICAgICAgIGxldCByZXQgPSBleHQucmVuZGVyZXIuYXBwbHkodGhpcywgYXJncykKICAgICAgICAgICAgICBpZiAocmV0ID09PSBmYWxzZSkgewogICAgICAgICAgICAgICAgcmV0ID0gcHJldlJlbmRlcmVyLmFwcGx5KHRoaXMsIGFyZ3MpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHJldHVybiByZXQKICAgICAgICAgICAgfQogICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgZXh0ZW5zaW9ucy5yZW5kZXJlcnNbZXh0Lm5hbWVdID0gZXh0LnJlbmRlcmVyCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIGlmIChleHQudG9rZW5pemVyKSB7CiAgICAgICAgICAvLyBUb2tlbml6ZXIgRXh0ZW5zaW9ucwogICAgICAgICAgaWYgKCFleHQubGV2ZWwgfHwgKGV4dC5sZXZlbCAhPT0gImJsb2NrIiAmJiBleHQubGV2ZWwgIT09ICJpbmxpbmUiKSkgewogICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoImV4dGVuc2lvbiBsZXZlbCBtdXN0IGJlICdibG9jaycgb3IgJ2lubGluZSciKQogICAgICAgICAgfQogICAgICAgICAgaWYgKGV4dGVuc2lvbnNbZXh0LmxldmVsXSkgewogICAgICAgICAgICBleHRlbnNpb25zW2V4dC5sZXZlbF0udW5zaGlmdChleHQudG9rZW5pemVyKQogICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgZXh0ZW5zaW9uc1tleHQubGV2ZWxdID0gW2V4dC50b2tlbml6ZXJdCiAgICAgICAgICB9CiAgICAgICAgICBpZiAoZXh0LnN0YXJ0KSB7CiAgICAgICAgICAgIC8vIEZ1bmN0aW9uIHRvIGNoZWNrIGZvciBzdGFydCBvZiB0b2tlbgogICAgICAgICAgICBpZiAoZXh0LmxldmVsID09PSAiYmxvY2siKSB7CiAgICAgICAgICAgICAgaWYgKGV4dGVuc2lvbnMuc3RhcnRCbG9jaykgewogICAgICAgICAgICAgICAgZXh0ZW5zaW9ucy5zdGFydEJsb2NrLnB1c2goZXh0LnN0YXJ0KQogICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICBleHRlbnNpb25zLnN0YXJ0QmxvY2sgPSBbZXh0LnN0YXJ0XQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSBlbHNlIGlmIChleHQubGV2ZWwgPT09ICJpbmxpbmUiKSB7CiAgICAgICAgICAgICAgaWYgKGV4dGVuc2lvbnMuc3RhcnRJbmxpbmUpIHsKICAgICAgICAgICAgICAgIGV4dGVuc2lvbnMuc3RhcnRJbmxpbmUucHVzaChleHQuc3RhcnQpCiAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIGV4dGVuc2lvbnMuc3RhcnRJbmxpbmUgPSBbZXh0LnN0YXJ0XQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBpZiAoZXh0LmNoaWxkVG9rZW5zKSB7CiAgICAgICAgICAvLyBDaGlsZCB0b2tlbnMgdG8gYmUgdmlzaXRlZCBieSB3YWxrVG9rZW5zCiAgICAgICAgICBleHRlbnNpb25zLmNoaWxkVG9rZW5zW2V4dC5uYW1lXSA9IGV4dC5jaGlsZFRva2VucwogICAgICAgIH0KICAgICAgfSkKICAgIH0KCiAgICAvLyA9PS0tIFBhcnNlICJvdmVyd3JpdGUiIGV4dGVuc2lvbnMgLS09PSAvLwogICAgaWYgKHBhY2sucmVuZGVyZXIpIHsKICAgICAgY29uc3QgcmVuZGVyZXIgPSBtYXJrZWQuZGVmYXVsdHMucmVuZGVyZXIgfHwgbmV3IFJlbmRlcmVyKCkKICAgICAgZm9yIChjb25zdCBwcm9wIGluIHBhY2sucmVuZGVyZXIpIHsKICAgICAgICBjb25zdCBwcmV2UmVuZGVyZXIgPSByZW5kZXJlcltwcm9wXQogICAgICAgIC8vIFJlcGxhY2UgcmVuZGVyZXIgd2l0aCBmdW5jIHRvIHJ1biBleHRlbnNpb24sIGJ1dCBmYWxsIGJhY2sgaWYgZmFsc2UKICAgICAgICByZW5kZXJlcltwcm9wXSA9ICguLi5hcmdzKSA9PiB7CiAgICAgICAgICBsZXQgcmV0ID0gcGFjay5yZW5kZXJlcltwcm9wXS5hcHBseShyZW5kZXJlciwgYXJncykKICAgICAgICAgIGlmIChyZXQgPT09IGZhbHNlKSB7CiAgICAgICAgICAgIHJldCA9IHByZXZSZW5kZXJlci5hcHBseShyZW5kZXJlciwgYXJncykKICAgICAgICAgIH0KICAgICAgICAgIHJldHVybiByZXQKICAgICAgICB9CiAgICAgIH0KICAgICAgb3B0cy5yZW5kZXJlciA9IHJlbmRlcmVyCiAgICB9CiAgICBpZiAocGFjay50b2tlbml6ZXIpIHsKICAgICAgY29uc3QgdG9rZW5pemVyID0gbWFya2VkLmRlZmF1bHRzLnRva2VuaXplciB8fCBuZXcgVG9rZW5pemVyKCkKICAgICAgZm9yIChjb25zdCBwcm9wIGluIHBhY2sudG9rZW5pemVyKSB7CiAgICAgICAgY29uc3QgcHJldlRva2VuaXplciA9IHRva2VuaXplcltwcm9wXQogICAgICAgIC8vIFJlcGxhY2UgdG9rZW5pemVyIHdpdGggZnVuYyB0byBydW4gZXh0ZW5zaW9uLCBidXQgZmFsbCBiYWNrIGlmIGZhbHNlCiAgICAgICAgdG9rZW5pemVyW3Byb3BdID0gKC4uLmFyZ3MpID0+IHsKICAgICAgICAgIGxldCByZXQgPSBwYWNrLnRva2VuaXplcltwcm9wXS5hcHBseSh0b2tlbml6ZXIsIGFyZ3MpCiAgICAgICAgICBpZiAocmV0ID09PSBmYWxzZSkgewogICAgICAgICAgICByZXQgPSBwcmV2VG9rZW5pemVyLmFwcGx5KHRva2VuaXplciwgYXJncykKICAgICAgICAgIH0KICAgICAgICAgIHJldHVybiByZXQKICAgICAgICB9CiAgICAgIH0KICAgICAgb3B0cy50b2tlbml6ZXIgPSB0b2tlbml6ZXIKICAgIH0KCiAgICAvLyA9PS0tIFBhcnNlIFdhbGtUb2tlbnMgZXh0ZW5zaW9ucyAtLT09IC8vCiAgICBpZiAocGFjay53YWxrVG9rZW5zKSB7CiAgICAgIGNvbnN0IHdhbGtUb2tlbnMgPSBtYXJrZWQuZGVmYXVsdHMud2Fsa1Rva2VucwogICAgICBvcHRzLndhbGtUb2tlbnMgPSBmdW5jdGlvbiAodG9rZW4pIHsKICAgICAgICBwYWNrLndhbGtUb2tlbnMuY2FsbCh0aGlzLCB0b2tlbikKICAgICAgICBpZiAod2Fsa1Rva2VucykgewogICAgICAgICAgd2Fsa1Rva2Vucy5jYWxsKHRoaXMsIHRva2VuKQogICAgICAgIH0KICAgICAgfQogICAgfQoKICAgIGlmIChoYXNFeHRlbnNpb25zKSB7CiAgICAgIG9wdHMuZXh0ZW5zaW9ucyA9IGV4dGVuc2lvbnMKICAgIH0KCiAgICBtYXJrZWQuc2V0T3B0aW9ucyhvcHRzKQogIH0pCn0KCi8qKgogKiBSdW4gY2FsbGJhY2sgZm9yIGV2ZXJ5IHRva2VuCiAqLwoKbWFya2VkLndhbGtUb2tlbnMgPSBmdW5jdGlvbiAodG9rZW5zLCBjYWxsYmFjaykgewogIGZvciAoY29uc3QgdG9rZW4gb2YgdG9rZW5zKSB7CiAgICBjYWxsYmFjay5jYWxsKG1hcmtlZCwgdG9rZW4pCiAgICBzd2l0Y2ggKHRva2VuLnR5cGUpIHsKICAgICAgY2FzZSAidGFibGUiOiB7CiAgICAgICAgZm9yIChjb25zdCBjZWxsIG9mIHRva2VuLmhlYWRlcikgewogICAgICAgICAgbWFya2VkLndhbGtUb2tlbnMoY2VsbC50b2tlbnMsIGNhbGxiYWNrKQogICAgICAgIH0KICAgICAgICBmb3IgKGNvbnN0IHJvdyBvZiB0b2tlbi5yb3dzKSB7CiAgICAgICAgICBmb3IgKGNvbnN0IGNlbGwgb2Ygcm93KSB7CiAgICAgICAgICAgIG1hcmtlZC53YWxrVG9rZW5zKGNlbGwudG9rZW5zLCBjYWxsYmFjaykKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgYnJlYWsKICAgICAgfQogICAgICBjYXNlICJsaXN0IjogewogICAgICAgIG1hcmtlZC53YWxrVG9rZW5zKHRva2VuLml0ZW1zLCBjYWxsYmFjaykKICAgICAgICBicmVhawogICAgICB9CiAgICAgIGRlZmF1bHQ6IHsKICAgICAgICBpZiAoCiAgICAgICAgICBtYXJrZWQuZGVmYXVsdHMuZXh0ZW5zaW9ucyAmJgogICAgICAgICAgbWFya2VkLmRlZmF1bHRzLmV4dGVuc2lvbnMuY2hpbGRUb2tlbnMgJiYKICAgICAgICAgIG1hcmtlZC5kZWZhdWx0cy5leHRlbnNpb25zLmNoaWxkVG9rZW5zW3Rva2VuLnR5cGVdCiAgICAgICAgKSB7CiAgICAgICAgICAvLyBXYWxrIGFueSBleHRlbnNpb25zCiAgICAgICAgICBtYXJrZWQuZGVmYXVsdHMuZXh0ZW5zaW9ucy5jaGlsZFRva2Vuc1t0b2tlbi50eXBlXS5mb3JFYWNoKGZ1bmN0aW9uICgKICAgICAgICAgICAgY2hpbGRUb2tlbnMKICAgICAgICAgICkgewogICAgICAgICAgICBtYXJrZWQud2Fsa1Rva2Vucyh0b2tlbltjaGlsZFRva2Vuc10sIGNhbGxiYWNrKQogICAgICAgICAgfSkKICAgICAgICB9IGVsc2UgaWYgKHRva2VuLnRva2VucykgewogICAgICAgICAgbWFya2VkLndhbGtUb2tlbnModG9rZW4udG9rZW5zLCBjYWxsYmFjaykKICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9Cn0KCi8qKgogKiBQYXJzZSBJbmxpbmUKICogQHBhcmFtIHtzdHJpbmd9IHNyYwogKi8KbWFya2VkLnBhcnNlSW5saW5lID0gZnVuY3Rpb24gKHNyYywgb3B0KSB7CiAgLy8gdGhyb3cgZXJyb3IgaW4gY2FzZSBvZiBub24gc3RyaW5nIGlucHV0CiAgaWYgKHR5cGVvZiBzcmMgPT09ICJ1bmRlZmluZWQiIHx8IHNyYyA9PT0gbnVsbCkgewogICAgdGhyb3cgbmV3IEVycm9yKAogICAgICAibWFya2VkLnBhcnNlSW5saW5lKCk6IGlucHV0IHBhcmFtZXRlciBpcyB1bmRlZmluZWQgb3IgbnVsbCIKICAgICkKICB9CiAgaWYgKHR5cGVvZiBzcmMgIT09ICJzdHJpbmciKSB7CiAgICB0aHJvdyBuZXcgRXJyb3IoCiAgICAgICJtYXJrZWQucGFyc2VJbmxpbmUoKTogaW5wdXQgcGFyYW1ldGVyIGlzIG9mIHR5cGUgIiArCiAgICAgICAgT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKHNyYykgKwogICAgICAgICIsIHN0cmluZyBleHBlY3RlZCIKICAgICkKICB9CgogIG9wdCA9IG1lcmdlKHt9LCBtYXJrZWQuZGVmYXVsdHMsIG9wdCB8fCB7fSkKICBjaGVja1Nhbml0aXplRGVwcmVjYXRpb24ob3B0KQoKICB0cnkgewogICAgY29uc3QgdG9rZW5zID0gTGV4ZXIubGV4SW5saW5lKHNyYywgb3B0KQogICAgaWYgKG9wdC53YWxrVG9rZW5zKSB7CiAgICAgIG1hcmtlZC53YWxrVG9rZW5zKHRva2Vucywgb3B0LndhbGtUb2tlbnMpCiAgICB9CiAgICByZXR1cm4gUGFyc2VyLnBhcnNlSW5saW5lKHRva2Vucywgb3B0KQogIH0gY2F0Y2ggKGUpIHsKICAgIGUubWVzc2FnZSArPSAiXG5QbGVhc2UgcmVwb3J0IHRoaXMgdG8gaHR0cHM6Ly9naXRodWIuY29tL21hcmtlZGpzL21hcmtlZC4iCiAgICBpZiAob3B0LnNpbGVudCkgewogICAgICByZXR1cm4gKAogICAgICAgICI8cD5BbiBlcnJvciBvY2N1cnJlZDo8L3A+PHByZT4iICsKICAgICAgICBlc2NhcGUoZS5tZXNzYWdlICsgIiIsIHRydWUpICsKICAgICAgICAiPC9wcmU+IgogICAgICApCiAgICB9CiAgICB0aHJvdyBlCiAgfQp9CgovKioKICogRXhwb3NlCiAqLwptYXJrZWQuUGFyc2VyID0gUGFyc2VyCm1hcmtlZC5wYXJzZXIgPSBQYXJzZXIucGFyc2UKbWFya2VkLlJlbmRlcmVyID0gUmVuZGVyZXIKbWFya2VkLlRleHRSZW5kZXJlciA9IFRleHRSZW5kZXJlcgptYXJrZWQuTGV4ZXIgPSBMZXhlcgptYXJrZWQubGV4ZXIgPSBMZXhlci5sZXgKbWFya2VkLlRva2VuaXplciA9IFRva2VuaXplcgptYXJrZWQuU2x1Z2dlciA9IFNsdWdnZXIKbWFya2VkLnBhcnNlID0gbWFya2VkCgpjb25zdCBvcHRpb25zID0gbWFya2VkLm9wdGlvbnMKY29uc3Qgc2V0T3B0aW9ucyA9IG1hcmtlZC5zZXRPcHRpb25zCmNvbnN0IHVzZSA9IG1hcmtlZC51c2UKY29uc3Qgd2Fsa1Rva2VucyA9IG1hcmtlZC53YWxrVG9rZW5zCmNvbnN0IHBhcnNlSW5saW5lID0gbWFya2VkLnBhcnNlSW5saW5lCmNvbnN0IHBhcnNlID0gbWFya2VkCmNvbnN0IHBhcnNlciA9IFBhcnNlci5wYXJzZQpjb25zdCBsZXhlciA9IExleGVyLmxleAoKY29uc3QgZW1haWwgPSB0cmlnZ2VyLnJvdwpyZXR1cm4gbWFya2VkKGVtYWlsLk1lc3NhZ2Up` diff --git a/packages/string-templates/src/iife.js b/packages/string-templates/src/iife.js new file mode 100644 index 0000000000..d043c14565 --- /dev/null +++ b/packages/string-templates/src/iife.js @@ -0,0 +1,3 @@ +module.exports.iifeWrapper = script => { + return `(function(){\n${script}\n})();` +} diff --git a/packages/string-templates/src/index.js b/packages/string-templates/src/index.js index 0125b9e0ab..5ae773516f 100644 --- a/packages/string-templates/src/index.js +++ b/packages/string-templates/src/index.js @@ -3,6 +3,7 @@ const handlebars = require("handlebars") const { registerAll, registerMinimum } = require("./helpers/index") const processors = require("./processors") const { atob, btoa, isBackendService } = require("./utilities") +const { iifeWrapper } = require("./iife") const manifest = require("../manifest.json") const { FIND_HBS_REGEX, @@ -426,3 +427,4 @@ function defaultJSSetup() { defaultJSSetup() module.exports.defaultJSSetup = defaultJSSetup +module.exports.iifeWrapper = iifeWrapper From 2189e3ee0fbc5491e2527d64e341629dabcbdad8 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 1 Mar 2024 15:26:02 +0000 Subject: [PATCH 02/59] Fix duped import --- packages/server/src/threads/query.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/server/src/threads/query.ts b/packages/server/src/threads/query.ts index 40caa6dfd8..f7384ab9bb 100644 --- a/packages/server/src/threads/query.ts +++ b/packages/server/src/threads/query.ts @@ -8,9 +8,8 @@ import { QueryResponse, } from "./definitions" import { IsolatedVM } from "../jsRunner/vm" -import { iifeWrapper } from "@budibase/string-templates" +import { iifeWrapper, processStringSync } from "@budibase/string-templates" import { getIntegration } from "../integrations" -import { processStringSync } from "@budibase/string-templates" import { context, cache, auth } from "@budibase/backend-core" import { getGlobalIDFromUserMetadataID } from "../db/utils" import sdk from "../sdk" From 483fcbe30af5d0d444d68967dddc719e9cd30dbb Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 1 Mar 2024 15:48:39 +0000 Subject: [PATCH 03/59] Add example of crazy long snippet for performance testing --- packages/string-templates/package.json | 3 ++- packages/string-templates/src/helpers/javascript.js | 2 -- packages/string-templates/src/helpers/snippet.js | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 340d74ef8a..1f3e1b618a 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -13,7 +13,8 @@ }, "./package.json": "./package.json", "./test/utils": "./test/utils.js", - "./iife": "./src/iife.js" + "./iife": "./src/iife.js", + "./snippet": "./src/helpers/snippet.js" }, "files": [ "dist", diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index 65c70cc330..7f3693c1c4 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -3,7 +3,6 @@ const cloneDeep = require("lodash.clonedeep") const { LITERAL_MARKER } = require("../helpers/constants") const { getJsHelperList } = require("./list") const { iifeWrapper } = require("../iife") -const { CrazyLongSnippet } = require("./snippet") // The method of executing JS scripts depends on the bundle being built. // This setter is used in the entrypoint (either index.js or index.mjs). @@ -51,7 +50,6 @@ const snippets = { HelloWorld: ` return "Hello, world!" `, - CrazyLongSnippet: atob(CrazyLongSnippet), } // Evaluates JS code against a certain context diff --git a/packages/string-templates/src/helpers/snippet.js b/packages/string-templates/src/helpers/snippet.js index 950019a69f..b7269b56cc 100644 --- a/packages/string-templates/src/helpers/snippet.js +++ b/packages/string-templates/src/helpers/snippet.js @@ -1 +1,2 @@ -module.exports.CrazyLongSnippet = `LyoqCiAqIG1hcmtlZCAtIGEgbWFya2Rvd24gcGFyc2VyCiAqIENvcHlyaWdodCAoYykgMjAxMS0yMDIyLCBDaHJpc3RvcGhlciBKZWZmcmV5LiAoTUlUIExpY2Vuc2VkKQogKiBodHRwczovL2dpdGh1Yi5jb20vbWFya2VkanMvbWFya2VkCiAqLwoKLyoqCiAqIERPIE5PVCBFRElUIFRISVMgRklMRQogKiBUaGUgY29kZSBpbiB0aGlzIGZpbGUgaXMgZ2VuZXJhdGVkIGZyb20gZmlsZXMgaW4gLi9zcmMvCiAqLwoKZnVuY3Rpb24gZ2V0RGVmYXVsdHMoKSB7CiAgcmV0dXJuIHsKICAgIGJhc2VVcmw6IG51bGwsCiAgICBicmVha3M6IGZhbHNlLAogICAgZXh0ZW5zaW9uczogbnVsbCwKICAgIGdmbTogdHJ1ZSwKICAgIGhlYWRlcklkczogdHJ1ZSwKICAgIGhlYWRlclByZWZpeDogIiIsCiAgICBoaWdobGlnaHQ6IG51bGwsCiAgICBsYW5nUHJlZml4OiAibGFuZ3VhZ2UtIiwKICAgIG1hbmdsZTogdHJ1ZSwKICAgIHBlZGFudGljOiBmYWxzZSwKICAgIHJlbmRlcmVyOiBudWxsLAogICAgc2FuaXRpemU6IGZhbHNlLAogICAgc2FuaXRpemVyOiBudWxsLAogICAgc2lsZW50OiBmYWxzZSwKICAgIHNtYXJ0TGlzdHM6IGZhbHNlLAogICAgc21hcnR5cGFudHM6IGZhbHNlLAogICAgdG9rZW5pemVyOiBudWxsLAogICAgd2Fsa1Rva2VuczogbnVsbCwKICAgIHhodG1sOiBmYWxzZSwKICB9Cn0KCmxldCBkZWZhdWx0cyA9IGdldERlZmF1bHRzKCkKCmZ1bmN0aW9uIGNoYW5nZURlZmF1bHRzKG5ld0RlZmF1bHRzKSB7CiAgZGVmYXVsdHMgPSBuZXdEZWZhdWx0cwp9CgovKioKICogSGVscGVycwogKi8KY29uc3QgZXNjYXBlVGVzdCA9IC9bJjw+IiddLwpjb25zdCBlc2NhcGVSZXBsYWNlID0gL1smPD4iJ10vZwpjb25zdCBlc2NhcGVUZXN0Tm9FbmNvZGUgPSAvWzw+IiddfCYoPyEjP1x3KzspLwpjb25zdCBlc2NhcGVSZXBsYWNlTm9FbmNvZGUgPSAvWzw+IiddfCYoPyEjP1x3KzspL2cKY29uc3QgZXNjYXBlUmVwbGFjZW1lbnRzID0gewogICImIjogIiZhbXA7IiwKICAiPCI6ICImbHQ7IiwKICAiPiI6ICImZ3Q7IiwKICAnIic6ICImcXVvdDsiLAogICInIjogIiYjMzk7IiwKfQpjb25zdCBnZXRFc2NhcGVSZXBsYWNlbWVudCA9IGNoID0+IGVzY2FwZVJlcGxhY2VtZW50c1tjaF0KZnVuY3Rpb24gZXNjYXBlKGh0bWwsIGVuY29kZSkgewogIGlmIChlbmNvZGUpIHsKICAgIGlmIChlc2NhcGVUZXN0LnRlc3QoaHRtbCkpIHsKICAgICAgcmV0dXJuIGh0bWwucmVwbGFjZShlc2NhcGVSZXBsYWNlLCBnZXRFc2NhcGVSZXBsYWNlbWVudCkKICAgIH0KICB9IGVsc2UgewogICAgaWYgKGVzY2FwZVRlc3ROb0VuY29kZS50ZXN0KGh0bWwpKSB7CiAgICAgIHJldHVybiBodG1sLnJlcGxhY2UoZXNjYXBlUmVwbGFjZU5vRW5jb2RlLCBnZXRFc2NhcGVSZXBsYWNlbWVudCkKICAgIH0KICB9CgogIHJldHVybiBodG1sCn0KCmNvbnN0IHVuZXNjYXBlVGVzdCA9IC8mKCMoPzpcZCspfCg/OiN4WzAtOUEtRmEtZl0rKXwoPzpcdyspKTs/L2dpCgovKioKICogQHBhcmFtIHtzdHJpbmd9IGh0bWwKICovCmZ1bmN0aW9uIHVuZXNjYXBlKGh0bWwpIHsKICAvLyBleHBsaWNpdGx5IG1hdGNoIGRlY2ltYWwsIGhleCwgYW5kIG5hbWVkIEhUTUwgZW50aXRpZXMKICByZXR1cm4gaHRtbC5yZXBsYWNlKHVuZXNjYXBlVGVzdCwgKF8sIG4pID0+IHsKICAgIG4gPSBuLnRvTG93ZXJDYXNlKCkKICAgIGlmIChuID09PSAiY29sb24iKSByZXR1cm4gIjoiCiAgICBpZiAobi5jaGFyQXQoMCkgPT09ICIjIikgewogICAgICByZXR1cm4gbi5jaGFyQXQoMSkgPT09ICJ4IgogICAgICAgID8gU3RyaW5nLmZyb21DaGFyQ29kZShwYXJzZUludChuLnN1YnN0cmluZygyKSwgMTYpKQogICAgICAgIDogU3RyaW5nLmZyb21DaGFyQ29kZSgrbi5zdWJzdHJpbmcoMSkpCiAgICB9CiAgICByZXR1cm4gIiIKICB9KQp9Cgpjb25zdCBjYXJldCA9IC8oXnxbXlxbXSlcXi9nCgovKioKICogQHBhcmFtIHtzdHJpbmcgfCBSZWdFeHB9IHJlZ2V4CiAqIEBwYXJhbSB7c3RyaW5nfSBvcHQKICovCmZ1bmN0aW9uIGVkaXQocmVnZXgsIG9wdCkgewogIHJlZ2V4ID0gdHlwZW9mIHJlZ2V4ID09PSAic3RyaW5nIiA/IHJlZ2V4IDogcmVnZXguc291cmNlCiAgb3B0ID0gb3B0IHx8ICIiCiAgY29uc3Qgb2JqID0gewogICAgcmVwbGFjZTogKG5hbWUsIHZhbCkgPT4gewogICAgICB2YWwgPSB2YWwuc291cmNlIHx8IHZhbAogICAgICB2YWwgPSB2YWwucmVwbGFjZShjYXJldCwgIiQxIikKICAgICAgcmVnZXggPSByZWdleC5yZXBsYWNlKG5hbWUsIHZhbCkKICAgICAgcmV0dXJuIG9iagogICAgfSwKICAgIGdldFJlZ2V4OiAoKSA9PiB7CiAgICAgIHJldHVybiBuZXcgUmVnRXhwKHJlZ2V4LCBvcHQpCiAgICB9LAogIH0KICByZXR1cm4gb2JqCn0KCmNvbnN0IG5vbldvcmRBbmRDb2xvblRlc3QgPSAvW15cdzpdL2cKY29uc3Qgb3JpZ2luSW5kZXBlbmRlbnRVcmwgPSAvXiR8XlthLXpdW2EtejAtOSsuLV0qOnxeWz8jXS9pCgovKioKICogQHBhcmFtIHtib29sZWFufSBzYW5pdGl6ZQogKiBAcGFyYW0ge3N0cmluZ30gYmFzZQogKiBAcGFyYW0ge3N0cmluZ30gaHJlZgogKi8KZnVuY3Rpb24gY2xlYW5Vcmwoc2FuaXRpemUsIGJhc2UsIGhyZWYpIHsKICBpZiAoc2FuaXRpemUpIHsKICAgIGxldCBwcm90CiAgICB0cnkgewogICAgICBwcm90ID0gZGVjb2RlVVJJQ29tcG9uZW50KHVuZXNjYXBlKGhyZWYpKQogICAgICAgIC5yZXBsYWNlKG5vbldvcmRBbmRDb2xvblRlc3QsICIiKQogICAgICAgIC50b0xvd2VyQ2FzZSgpCiAgICB9IGNhdGNoIChlKSB7CiAgICAgIHJldHVybiBudWxsCiAgICB9CiAgICBpZiAoCiAgICAgIHByb3QuaW5kZXhPZigiamF2YXNjcmlwdDoiKSA9PT0gMCB8fAogICAgICBwcm90LmluZGV4T2YoInZic2NyaXB0OiIpID09PSAwIHx8CiAgICAgIHByb3QuaW5kZXhPZigiZGF0YToiKSA9PT0gMAogICAgKSB7CiAgICAgIHJldHVybiBudWxsCiAgICB9CiAgfQogIGlmIChiYXNlICYmICFvcmlnaW5JbmRlcGVuZGVudFVybC50ZXN0KGhyZWYpKSB7CiAgICBocmVmID0gcmVzb2x2ZVVybChiYXNlLCBocmVmKQogIH0KICB0cnkgewogICAgaHJlZiA9IGVuY29kZVVSSShocmVmKS5yZXBsYWNlKC8lMjUvZywgIiUiKQogIH0gY2F0Y2ggKGUpIHsKICAgIHJldHVybiBudWxsCiAgfQogIHJldHVybiBocmVmCn0KCmNvbnN0IGJhc2VVcmxzID0ge30KY29uc3QganVzdERvbWFpbiA9IC9eW146XSs6XC8qW14vXSokLwpjb25zdCBwcm90b2NvbCA9IC9eKFteOl0rOilbXHNcU10qJC8KY29uc3QgZG9tYWluID0gL14oW146XSs6XC8qW14vXSopW1xzXFNdKiQvCgovKioKICogQHBhcmFtIHtzdHJpbmd9IGJhc2UKICogQHBhcmFtIHtzdHJpbmd9IGhyZWYKICovCmZ1bmN0aW9uIHJlc29sdmVVcmwoYmFzZSwgaHJlZikgewogIGlmICghYmFzZVVybHNbIiAiICsgYmFzZV0pIHsKICAgIC8vIHdlIGNhbiBpZ25vcmUgZXZlcnl0aGluZyBpbiBiYXNlIGFmdGVyIHRoZSBsYXN0IHNsYXNoIG9mIGl0cyBwYXRoIGNvbXBvbmVudCwKICAgIC8vIGJ1dCB3ZSBtaWdodCBuZWVkIHRvIGFkZCBfdGhhdF8KICAgIC8vIGh0dHBzOi8vdG9vbHMuaWV0Zi5vcmcvaHRtbC9yZmMzOTg2I3NlY3Rpb24tMwogICAgaWYgKGp1c3REb21haW4udGVzdChiYXNlKSkgewogICAgICBiYXNlVXJsc1siICIgKyBiYXNlXSA9IGJhc2UgKyAiLyIKICAgIH0gZWxzZSB7CiAgICAgIGJhc2VVcmxzWyIgIiArIGJhc2VdID0gcnRyaW0oYmFzZSwgIi8iLCB0cnVlKQogICAgfQogIH0KICBiYXNlID0gYmFzZVVybHNbIiAiICsgYmFzZV0KICBjb25zdCByZWxhdGl2ZUJhc2UgPSBiYXNlLmluZGV4T2YoIjoiKSA9PT0gLTEKCiAgaWYgKGhyZWYuc3Vic3RyaW5nKDAsIDIpID09PSAiLy8iKSB7CiAgICBpZiAocmVsYXRpdmVCYXNlKSB7CiAgICAgIHJldHVybiBocmVmCiAgICB9CiAgICByZXR1cm4gYmFzZS5yZXBsYWNlKHByb3RvY29sLCAiJDEiKSArIGhyZWYKICB9IGVsc2UgaWYgKGhyZWYuY2hhckF0KDApID09PSAiLyIpIHsKICAgIGlmIChyZWxhdGl2ZUJhc2UpIHsKICAgICAgcmV0dXJuIGhyZWYKICAgIH0KICAgIHJldHVybiBiYXNlLnJlcGxhY2UoZG9tYWluLCAiJDEiKSArIGhyZWYKICB9IGVsc2UgewogICAgcmV0dXJuIGJhc2UgKyBocmVmCiAgfQp9Cgpjb25zdCBub29wVGVzdCA9IHsgZXhlYzogZnVuY3Rpb24gbm9vcFRlc3QoKSB7fSB9CgpmdW5jdGlvbiBtZXJnZShvYmopIHsKICBsZXQgaSA9IDEsCiAgICB0YXJnZXQsCiAgICBrZXkKCiAgZm9yICg7IGkgPCBhcmd1bWVudHMubGVuZ3RoOyBpKyspIHsKICAgIHRhcmdldCA9IGFyZ3VtZW50c1tpXQogICAgZm9yIChrZXkgaW4gdGFyZ2V0KSB7CiAgICAgIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwodGFyZ2V0LCBrZXkpKSB7CiAgICAgICAgb2JqW2tleV0gPSB0YXJnZXRba2V5XQogICAgICB9CiAgICB9CiAgfQoKICByZXR1cm4gb2JqCn0KCmZ1bmN0aW9uIHNwbGl0Q2VsbHModGFibGVSb3csIGNvdW50KSB7CiAgLy8gZW5zdXJlIHRoYXQgZXZlcnkgY2VsbC1kZWxpbWl0aW5nIHBpcGUgaGFzIGEgc3BhY2UKICAvLyBiZWZvcmUgaXQgdG8gZGlzdGluZ3Vpc2ggaXQgZnJvbSBhbiBlc2NhcGVkIHBpcGUKICBjb25zdCByb3cgPSB0YWJsZVJvdy5yZXBsYWNlKC9cfC9nLCAobWF0Y2gsIG9mZnNldCwgc3RyKSA9PiB7CiAgICAgIGxldCBlc2NhcGVkID0gZmFsc2UsCiAgICAgICAgY3VyciA9IG9mZnNldAogICAgICB3aGlsZSAoLS1jdXJyID49IDAgJiYgc3RyW2N1cnJdID09PSAiXFwiKSBlc2NhcGVkID0gIWVzY2FwZWQKICAgICAgaWYgKGVzY2FwZWQpIHsKICAgICAgICAvLyBvZGQgbnVtYmVyIG9mIHNsYXNoZXMgbWVhbnMgfCBpcyBlc2NhcGVkCiAgICAgICAgLy8gc28gd2UgbGVhdmUgaXQgYWxvbmUKICAgICAgICByZXR1cm4gInwiCiAgICAgIH0gZWxzZSB7CiAgICAgICAgLy8gYWRkIHNwYWNlIGJlZm9yZSB1bmVzY2FwZWQgfAogICAgICAgIHJldHVybiAiIHwiCiAgICAgIH0KICAgIH0pLAogICAgY2VsbHMgPSByb3cuc3BsaXQoLyBcfC8pCiAgbGV0IGkgPSAwCgogIC8vIEZpcnN0L2xhc3QgY2VsbCBpbiBhIHJvdyBjYW5ub3QgYmUgZW1wdHkgaWYgaXQgaGFzIG5vIGxlYWRpbmcvdHJhaWxpbmcgcGlwZQogIGlmICghY2VsbHNbMF0udHJpbSgpKSB7CiAgICBjZWxscy5zaGlmdCgpCiAgfQogIGlmIChjZWxscy5sZW5ndGggPiAwICYmICFjZWxsc1tjZWxscy5sZW5ndGggLSAxXS50cmltKCkpIHsKICAgIGNlbGxzLnBvcCgpCiAgfQoKICBpZiAoY2VsbHMubGVuZ3RoID4gY291bnQpIHsKICAgIGNlbGxzLnNwbGljZShjb3VudCkKICB9IGVsc2UgewogICAgd2hpbGUgKGNlbGxzLmxlbmd0aCA8IGNvdW50KSBjZWxscy5wdXNoKCIiKQogIH0KCiAgZm9yICg7IGkgPCBjZWxscy5sZW5ndGg7IGkrKykgewogICAgLy8gbGVhZGluZyBvciB0cmFpbGluZyB3aGl0ZXNwYWNlIGlzIGlnbm9yZWQgcGVyIHRoZSBnZm0gc3BlYwogICAgY2VsbHNbaV0gPSBjZWxsc1tpXS50cmltKCkucmVwbGFjZSgvXFxcfC9nLCAifCIpCiAgfQogIHJldHVybiBjZWxscwp9CgovKioKICogUmVtb3ZlIHRyYWlsaW5nICdjJ3MuIEVxdWl2YWxlbnQgdG8gc3RyLnJlcGxhY2UoL2MqJC8sICcnKS4KICogL2MqJC8gaXMgdnVsbmVyYWJsZSB0byBSRURPUy4KICoKICogQHBhcmFtIHtzdHJpbmd9IHN0cgogKiBAcGFyYW0ge3N0cmluZ30gYwogKiBAcGFyYW0ge2Jvb2xlYW59IGludmVydCBSZW1vdmUgc3VmZml4IG9mIG5vbi1jIGNoYXJzIGluc3RlYWQuIERlZmF1bHQgZmFsc2V5LgogKi8KZnVuY3Rpb24gcnRyaW0oc3RyLCBjLCBpbnZlcnQpIHsKICBjb25zdCBsID0gc3RyLmxlbmd0aAogIGlmIChsID09PSAwKSB7CiAgICByZXR1cm4gIiIKICB9CgogIC8vIExlbmd0aCBvZiBzdWZmaXggbWF0Y2hpbmcgdGhlIGludmVydCBjb25kaXRpb24uCiAgbGV0IHN1ZmZMZW4gPSAwCgogIC8vIFN0ZXAgbGVmdCB1bnRpbCB3ZSBmYWlsIHRvIG1hdGNoIHRoZSBpbnZlcnQgY29uZGl0aW9uLgogIHdoaWxlIChzdWZmTGVuIDwgbCkgewogICAgY29uc3QgY3VyckNoYXIgPSBzdHIuY2hhckF0KGwgLSBzdWZmTGVuIC0gMSkKICAgIGlmIChjdXJyQ2hhciA9PT0gYyAmJiAhaW52ZXJ0KSB7CiAgICAgIHN1ZmZMZW4rKwogICAgfSBlbHNlIGlmIChjdXJyQ2hhciAhPT0gYyAmJiBpbnZlcnQpIHsKICAgICAgc3VmZkxlbisrCiAgICB9IGVsc2UgewogICAgICBicmVhawogICAgfQogIH0KCiAgcmV0dXJuIHN0ci5zbGljZSgwLCBsIC0gc3VmZkxlbikKfQoKZnVuY3Rpb24gZmluZENsb3NpbmdCcmFja2V0KHN0ciwgYikgewogIGlmIChzdHIuaW5kZXhPZihiWzFdKSA9PT0gLTEpIHsKICAgIHJldHVybiAtMQogIH0KICBjb25zdCBsID0gc3RyLmxlbmd0aAogIGxldCBsZXZlbCA9IDAsCiAgICBpID0gMAogIGZvciAoOyBpIDwgbDsgaSsrKSB7CiAgICBpZiAoc3RyW2ldID09PSAiXFwiKSB7CiAgICAgIGkrKwogICAgfSBlbHNlIGlmIChzdHJbaV0gPT09IGJbMF0pIHsKICAgICAgbGV2ZWwrKwogICAgfSBlbHNlIGlmIChzdHJbaV0gPT09IGJbMV0pIHsKICAgICAgbGV2ZWwtLQogICAgICBpZiAobGV2ZWwgPCAwKSB7CiAgICAgICAgcmV0dXJuIGkKICAgICAgfQogICAgfQogIH0KICByZXR1cm4gLTEKfQoKZnVuY3Rpb24gY2hlY2tTYW5pdGl6ZURlcHJlY2F0aW9uKG9wdCkgewogIGlmIChvcHQgJiYgb3B0LnNhbml0aXplICYmICFvcHQuc2lsZW50KSB7CiAgICBjb25zb2xlLndhcm4oCiAgICAgICJtYXJrZWQoKTogc2FuaXRpemUgYW5kIHNhbml0aXplciBwYXJhbWV0ZXJzIGFyZSBkZXByZWNhdGVkIHNpbmNlIHZlcnNpb24gMC43LjAsIHNob3VsZCBub3QgYmUgdXNlZCBhbmQgd2lsbCBiZSByZW1vdmVkIGluIHRoZSBmdXR1cmUuIFJlYWQgbW9yZSBoZXJlOiBodHRwczovL21hcmtlZC5qcy5vcmcvIy9VU0lOR19BRFZBTkNFRC5tZCNvcHRpb25zIgogICAgKQogIH0KfQoKLy8gY29waWVkIGZyb20gaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9hLzU0NTAxMTMvODA2Nzc3Ci8qKgogKiBAcGFyYW0ge3N0cmluZ30gcGF0dGVybgogKiBAcGFyYW0ge251bWJlcn0gY291bnQKICovCmZ1bmN0aW9uIHJlcGVhdFN0cmluZyhwYXR0ZXJuLCBjb3VudCkgewogIGlmIChjb3VudCA8IDEpIHsKICAgIHJldHVybiAiIgogIH0KICBsZXQgcmVzdWx0ID0gIiIKICB3aGlsZSAoY291bnQgPiAxKSB7CiAgICBpZiAoY291bnQgJiAxKSB7CiAgICAgIHJlc3VsdCArPSBwYXR0ZXJuCiAgICB9CiAgICBjb3VudCA+Pj0gMQogICAgcGF0dGVybiArPSBwYXR0ZXJuCiAgfQogIHJldHVybiByZXN1bHQgKyBwYXR0ZXJuCn0KCmZ1bmN0aW9uIG91dHB1dExpbmsoY2FwLCBsaW5rLCByYXcsIGxleGVyKSB7CiAgY29uc3QgaHJlZiA9IGxpbmsuaHJlZgogIGNvbnN0IHRpdGxlID0gbGluay50aXRsZSA/IGVzY2FwZShsaW5rLnRpdGxlKSA6IG51bGwKICBjb25zdCB0ZXh0ID0gY2FwWzFdLnJlcGxhY2UoL1xcKFtcW1xdXSkvZywgIiQxIikKCiAgaWYgKGNhcFswXS5jaGFyQXQoMCkgIT09ICIhIikgewogICAgbGV4ZXIuc3RhdGUuaW5MaW5rID0gdHJ1ZQogICAgY29uc3QgdG9rZW4gPSB7CiAgICAgIHR5cGU6ICJsaW5rIiwKICAgICAgcmF3LAogICAgICBocmVmLAogICAgICB0aXRsZSwKICAgICAgdGV4dCwKICAgICAgdG9rZW5zOiBsZXhlci5pbmxpbmVUb2tlbnModGV4dCwgW10pLAogICAgfQogICAgbGV4ZXIuc3RhdGUuaW5MaW5rID0gZmFsc2UKICAgIHJldHVybiB0b2tlbgogIH0KICByZXR1cm4gewogICAgdHlwZTogImltYWdlIiwKICAgIHJhdywKICAgIGhyZWYsCiAgICB0aXRsZSwKICAgIHRleHQ6IGVzY2FwZSh0ZXh0KSwKICB9Cn0KCmZ1bmN0aW9uIGluZGVudENvZGVDb21wZW5zYXRpb24ocmF3LCB0ZXh0KSB7CiAgY29uc3QgbWF0Y2hJbmRlbnRUb0NvZGUgPSByYXcubWF0Y2goL14oXHMrKSg/OmBgYCkvKQoKICBpZiAobWF0Y2hJbmRlbnRUb0NvZGUgPT09IG51bGwpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICBjb25zdCBpbmRlbnRUb0NvZGUgPSBtYXRjaEluZGVudFRvQ29kZVsxXQoKICByZXR1cm4gdGV4dAogICAgLnNwbGl0KCJcbiIpCiAgICAubWFwKG5vZGUgPT4gewogICAgICBjb25zdCBtYXRjaEluZGVudEluTm9kZSA9IG5vZGUubWF0Y2goL15ccysvKQogICAgICBpZiAobWF0Y2hJbmRlbnRJbk5vZGUgPT09IG51bGwpIHsKICAgICAgICByZXR1cm4gbm9kZQogICAgICB9CgogICAgICBjb25zdCBbaW5kZW50SW5Ob2RlXSA9IG1hdGNoSW5kZW50SW5Ob2RlCgogICAgICBpZiAoaW5kZW50SW5Ob2RlLmxlbmd0aCA+PSBpbmRlbnRUb0NvZGUubGVuZ3RoKSB7CiAgICAgICAgcmV0dXJuIG5vZGUuc2xpY2UoaW5kZW50VG9Db2RlLmxlbmd0aCkKICAgICAgfQoKICAgICAgcmV0dXJuIG5vZGUKICAgIH0pCiAgICAuam9pbigiXG4iKQp9CgovKioKICogVG9rZW5pemVyCiAqLwpjbGFzcyBUb2tlbml6ZXIgewogIGNvbnN0cnVjdG9yKG9wdGlvbnMpIHsKICAgIHRoaXMub3B0aW9ucyA9IG9wdGlvbnMgfHwgZGVmYXVsdHMKICB9CgogIHNwYWNlKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5uZXdsaW5lLmV4ZWMoc3JjKQogICAgaWYgKGNhcCAmJiBjYXBbMF0ubGVuZ3RoID4gMCkgewogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJzcGFjZSIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgIH0KICAgIH0KICB9CgogIGNvZGUoc3JjKSB7CiAgICBjb25zdCBjYXAgPSB0aGlzLnJ1bGVzLmJsb2NrLmNvZGUuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGNvbnN0IHRleHQgPSBjYXBbMF0ucmVwbGFjZSgvXiB7MSw0fS9nbSwgIiIpCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImNvZGUiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGNvZGVCbG9ja1N0eWxlOiAiaW5kZW50ZWQiLAogICAgICAgIHRleHQ6ICF0aGlzLm9wdGlvbnMucGVkYW50aWMgPyBydHJpbSh0ZXh0LCAiXG4iKSA6IHRleHQsCiAgICAgIH0KICAgIH0KICB9CgogIGZlbmNlcyhzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuYmxvY2suZmVuY2VzLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBjb25zdCByYXcgPSBjYXBbMF0KICAgICAgY29uc3QgdGV4dCA9IGluZGVudENvZGVDb21wZW5zYXRpb24ocmF3LCBjYXBbM10gfHwgIiIpCgogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJjb2RlIiwKICAgICAgICByYXcsCiAgICAgICAgbGFuZzogY2FwWzJdID8gY2FwWzJdLnRyaW0oKSA6IGNhcFsyXSwKICAgICAgICB0ZXh0LAogICAgICB9CiAgICB9CiAgfQoKICBoZWFkaW5nKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5oZWFkaW5nLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBsZXQgdGV4dCA9IGNhcFsyXS50cmltKCkKCiAgICAgIC8vIHJlbW92ZSB0cmFpbGluZyAjcwogICAgICBpZiAoLyMkLy50ZXN0KHRleHQpKSB7CiAgICAgICAgY29uc3QgdHJpbW1lZCA9IHJ0cmltKHRleHQsICIjIikKICAgICAgICBpZiAodGhpcy5vcHRpb25zLnBlZGFudGljKSB7CiAgICAgICAgICB0ZXh0ID0gdHJpbW1lZC50cmltKCkKICAgICAgICB9IGVsc2UgaWYgKCF0cmltbWVkIHx8IC8gJC8udGVzdCh0cmltbWVkKSkgewogICAgICAgICAgLy8gQ29tbW9uTWFyayByZXF1aXJlcyBzcGFjZSBiZWZvcmUgdHJhaWxpbmcgI3MKICAgICAgICAgIHRleHQgPSB0cmltbWVkLnRyaW0oKQogICAgICAgIH0KICAgICAgfQoKICAgICAgY29uc3QgdG9rZW4gPSB7CiAgICAgICAgdHlwZTogImhlYWRpbmciLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGRlcHRoOiBjYXBbMV0ubGVuZ3RoLAogICAgICAgIHRleHQsCiAgICAgICAgdG9rZW5zOiBbXSwKICAgICAgfQogICAgICB0aGlzLmxleGVyLmlubGluZSh0b2tlbi50ZXh0LCB0b2tlbi50b2tlbnMpCiAgICAgIHJldHVybiB0b2tlbgogICAgfQogIH0KCiAgaHIoc3JjKSB7CiAgICBjb25zdCBjYXAgPSB0aGlzLnJ1bGVzLmJsb2NrLmhyLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJociIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgIH0KICAgIH0KICB9CgogIGJsb2NrcXVvdGUoc3JjKSB7CiAgICBjb25zdCBjYXAgPSB0aGlzLnJ1bGVzLmJsb2NrLmJsb2NrcXVvdGUuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGNvbnN0IHRleHQgPSBjYXBbMF0ucmVwbGFjZSgvXiAqPlsgXHRdPy9nbSwgIiIpCgogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJibG9ja3F1b3RlIiwKICAgICAgICByYXc6IGNhcFswXSwKICAgICAgICB0b2tlbnM6IHRoaXMubGV4ZXIuYmxvY2tUb2tlbnModGV4dCwgW10pLAogICAgICAgIHRleHQsCiAgICAgIH0KICAgIH0KICB9CgogIGxpc3Qoc3JjKSB7CiAgICBsZXQgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5saXN0LmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBsZXQgcmF3LAogICAgICAgIGlzdGFzaywKICAgICAgICBpc2NoZWNrZWQsCiAgICAgICAgaW5kZW50LAogICAgICAgIGksCiAgICAgICAgYmxhbmtMaW5lLAogICAgICAgIGVuZHNXaXRoQmxhbmtMaW5lLAogICAgICAgIGxpbmUsCiAgICAgICAgbmV4dExpbmUsCiAgICAgICAgcmF3TGluZSwKICAgICAgICBpdGVtQ29udGVudHMsCiAgICAgICAgZW5kRWFybHkKCiAgICAgIGxldCBidWxsID0gY2FwWzFdLnRyaW0oKQogICAgICBjb25zdCBpc29yZGVyZWQgPSBidWxsLmxlbmd0aCA+IDEKCiAgICAgIGNvbnN0IGxpc3QgPSB7CiAgICAgICAgdHlwZTogImxpc3QiLAogICAgICAgIHJhdzogIiIsCiAgICAgICAgb3JkZXJlZDogaXNvcmRlcmVkLAogICAgICAgIHN0YXJ0OiBpc29yZGVyZWQgPyArYnVsbC5zbGljZSgwLCAtMSkgOiAiIiwKICAgICAgICBsb29zZTogZmFsc2UsCiAgICAgICAgaXRlbXM6IFtdLAogICAgICB9CgogICAgICBidWxsID0gaXNvcmRlcmVkID8gYFxcZHsxLDl9XFwke2J1bGwuc2xpY2UoLTEpfWAgOiBgXFwke2J1bGx9YAoKICAgICAgaWYgKHRoaXMub3B0aW9ucy5wZWRhbnRpYykgewogICAgICAgIGJ1bGwgPSBpc29yZGVyZWQgPyBidWxsIDogIlsqKy1dIgogICAgICB9CgogICAgICAvLyBHZXQgbmV4dCBsaXN0IGl0ZW0KICAgICAgY29uc3QgaXRlbVJlZ2V4ID0gbmV3IFJlZ0V4cCgKICAgICAgICBgXiggezAsM30ke2J1bGx9KSgoPzpbXHQgXVteXFxuXSopPyg/OlxcbnwkKSlgCiAgICAgICkKCiAgICAgIC8vIENoZWNrIGlmIGN1cnJlbnQgYnVsbGV0IHBvaW50IGNhbiBzdGFydCBhIG5ldyBMaXN0IEl0ZW0KICAgICAgd2hpbGUgKHNyYykgewogICAgICAgIGVuZEVhcmx5ID0gZmFsc2UKICAgICAgICBpZiAoIShjYXAgPSBpdGVtUmVnZXguZXhlYyhzcmMpKSkgewogICAgICAgICAgYnJlYWsKICAgICAgICB9CgogICAgICAgIGlmICh0aGlzLnJ1bGVzLmJsb2NrLmhyLnRlc3Qoc3JjKSkgewogICAgICAgICAgLy8gRW5kIGxpc3QgaWYgYnVsbGV0IHdhcyBhY3R1YWxseSBIUiAocG9zc2libHkgbW92ZSBpbnRvIGl0ZW1SZWdleD8pCiAgICAgICAgICBicmVhawogICAgICAgIH0KCiAgICAgICAgcmF3ID0gY2FwWzBdCiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyhyYXcubGVuZ3RoKQoKICAgICAgICBsaW5lID0gY2FwWzJdLnNwbGl0KCJcbiIsIDEpWzBdCiAgICAgICAgbmV4dExpbmUgPSBzcmMuc3BsaXQoIlxuIiwgMSlbMF0KCiAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5wZWRhbnRpYykgewogICAgICAgICAgaW5kZW50ID0gMgogICAgICAgICAgaXRlbUNvbnRlbnRzID0gbGluZS50cmltTGVmdCgpCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIGluZGVudCA9IGNhcFsyXS5zZWFyY2goL1teIF0vKSAvLyBGaW5kIGZpcnN0IG5vbi1zcGFjZSBjaGFyCiAgICAgICAgICBpbmRlbnQgPSBpbmRlbnQgPiA0ID8gMSA6IGluZGVudCAvLyBUcmVhdCBpbmRlbnRlZCBjb2RlIGJsb2NrcyAoPiA0IHNwYWNlcykgYXMgaGF2aW5nIG9ubHkgMSBpbmRlbnQKICAgICAgICAgIGl0ZW1Db250ZW50cyA9IGxpbmUuc2xpY2UoaW5kZW50KQogICAgICAgICAgaW5kZW50ICs9IGNhcFsxXS5sZW5ndGgKICAgICAgICB9CgogICAgICAgIGJsYW5rTGluZSA9IGZhbHNlCgogICAgICAgIGlmICghbGluZSAmJiAvXiAqJC8udGVzdChuZXh0TGluZSkpIHsKICAgICAgICAgIC8vIEl0ZW1zIGJlZ2luIHdpdGggYXQgbW9zdCBvbmUgYmxhbmsgbGluZQogICAgICAgICAgcmF3ICs9IG5leHRMaW5lICsgIlxuIgogICAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyhuZXh0TGluZS5sZW5ndGggKyAxKQogICAgICAgICAgZW5kRWFybHkgPSB0cnVlCiAgICAgICAgfQoKICAgICAgICBpZiAoIWVuZEVhcmx5KSB7CiAgICAgICAgICBjb25zdCBuZXh0QnVsbGV0UmVnZXggPSBuZXcgUmVnRXhwKAogICAgICAgICAgICBgXiB7MCwke01hdGgubWluKAogICAgICAgICAgICAgIDMsCiAgICAgICAgICAgICAgaW5kZW50IC0gMQogICAgICAgICAgICApfX0oPzpbKistXXxcXGR7MSw5fVsuKV0pKCg/OiBbXlxcbl0qKT8oPzpcXG58JCkpYAogICAgICAgICAgKQogICAgICAgICAgY29uc3QgaHJSZWdleCA9IG5ldyBSZWdFeHAoCiAgICAgICAgICAgIGBeIHswLCR7TWF0aC5taW4oCiAgICAgICAgICAgICAgMywKICAgICAgICAgICAgICBpbmRlbnQgLSAxCiAgICAgICAgICAgICl9fSgoPzotICopezMsfXwoPzpfICopezMsfXwoPzpcXCogKil7Myx9KSg/Olxcbit8JClgCiAgICAgICAgICApCgogICAgICAgICAgLy8gQ2hlY2sgaWYgZm9sbG93aW5nIGxpbmVzIHNob3VsZCBiZSBpbmNsdWRlZCBpbiBMaXN0IEl0ZW0KICAgICAgICAgIHdoaWxlIChzcmMpIHsKICAgICAgICAgICAgcmF3TGluZSA9IHNyYy5zcGxpdCgiXG4iLCAxKVswXQogICAgICAgICAgICBsaW5lID0gcmF3TGluZQoKICAgICAgICAgICAgLy8gUmUtYWxpZ24gdG8gZm9sbG93IGNvbW1vbm1hcmsgbmVzdGluZyBydWxlcwogICAgICAgICAgICBpZiAodGhpcy5vcHRpb25zLnBlZGFudGljKSB7CiAgICAgICAgICAgICAgbGluZSA9IGxpbmUucmVwbGFjZSgvXiB7MSw0fSg/PSggezR9KSpbXiBdKS9nLCAiICAiKQogICAgICAgICAgICB9CgogICAgICAgICAgICAvLyBFbmQgbGlzdCBpdGVtIGlmIGZvdW5kIHN0YXJ0IG9mIG5ldyBidWxsZXQKICAgICAgICAgICAgaWYgKG5leHRCdWxsZXRSZWdleC50ZXN0KGxpbmUpKSB7CiAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgfQoKICAgICAgICAgICAgLy8gSG9yaXpvbnRhbCBydWxlIGZvdW5kCiAgICAgICAgICAgIGlmIChoclJlZ2V4LnRlc3Qoc3JjKSkgewogICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGlmIChsaW5lLnNlYXJjaCgvW14gXS8pID49IGluZGVudCB8fCAhbGluZS50cmltKCkpIHsKICAgICAgICAgICAgICAvLyBEZWRlbnQgaWYgcG9zc2libGUKICAgICAgICAgICAgICBpdGVtQ29udGVudHMgKz0gIlxuIiArIGxpbmUuc2xpY2UoaW5kZW50KQogICAgICAgICAgICB9IGVsc2UgaWYgKCFibGFua0xpbmUpIHsKICAgICAgICAgICAgICAvLyBVbnRpbCBibGFuayBsaW5lLCBpdGVtIGRvZXNuJ3QgbmVlZCBpbmRlbnRhdGlvbgogICAgICAgICAgICAgIGl0ZW1Db250ZW50cyArPSAiXG4iICsgbGluZQogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgIC8vIE90aGVyd2lzZSwgaW1wcm9wZXIgaW5kZW50YXRpb24gZW5kcyB0aGlzIGl0ZW0KICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICB9CgogICAgICAgICAgICBpZiAoIWJsYW5rTGluZSAmJiAhbGluZS50cmltKCkpIHsKICAgICAgICAgICAgICAvLyBDaGVjayBpZiBjdXJyZW50IGxpbmUgaXMgYmxhbmsKICAgICAgICAgICAgICBibGFua0xpbmUgPSB0cnVlCiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIHJhdyArPSByYXdMaW5lICsgIlxuIgogICAgICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHJhd0xpbmUubGVuZ3RoICsgMSkKICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIGlmICghbGlzdC5sb29zZSkgewogICAgICAgICAgLy8gSWYgdGhlIHByZXZpb3VzIGl0ZW0gZW5kZWQgd2l0aCBhIGJsYW5rIGxpbmUsIHRoZSBsaXN0IGlzIGxvb3NlCiAgICAgICAgICBpZiAoZW5kc1dpdGhCbGFua0xpbmUpIHsKICAgICAgICAgICAgbGlzdC5sb29zZSA9IHRydWUKICAgICAgICAgIH0gZWxzZSBpZiAoL1xuICpcbiAqJC8udGVzdChyYXcpKSB7CiAgICAgICAgICAgIGVuZHNXaXRoQmxhbmtMaW5lID0gdHJ1ZQogICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgLy8gQ2hlY2sgZm9yIHRhc2sgbGlzdCBpdGVtcwogICAgICAgIGlmICh0aGlzLm9wdGlvbnMuZ2ZtKSB7CiAgICAgICAgICBpc3Rhc2sgPSAvXlxbWyB4WF1cXSAvLmV4ZWMoaXRlbUNvbnRlbnRzKQogICAgICAgICAgaWYgKGlzdGFzaykgewogICAgICAgICAgICBpc2NoZWNrZWQgPSBpc3Rhc2tbMF0gIT09ICJbIF0gIgogICAgICAgICAgICBpdGVtQ29udGVudHMgPSBpdGVtQ29udGVudHMucmVwbGFjZSgvXlxbWyB4WF1cXSArLywgIiIpCiAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBsaXN0Lml0ZW1zLnB1c2goewogICAgICAgICAgdHlwZTogImxpc3RfaXRlbSIsCiAgICAgICAgICByYXcsCiAgICAgICAgICB0YXNrOiAhIWlzdGFzaywKICAgICAgICAgIGNoZWNrZWQ6IGlzY2hlY2tlZCwKICAgICAgICAgIGxvb3NlOiBmYWxzZSwKICAgICAgICAgIHRleHQ6IGl0ZW1Db250ZW50cywKICAgICAgICB9KQoKICAgICAgICBsaXN0LnJhdyArPSByYXcKICAgICAgfQoKICAgICAgLy8gRG8gbm90IGNvbnN1bWUgbmV3bGluZXMgYXQgZW5kIG9mIGZpbmFsIGl0ZW0uIEFsdGVybmF0aXZlbHksIG1ha2UgaXRlbVJlZ2V4ICpzdGFydCogd2l0aCBhbnkgbmV3bGluZXMgdG8gc2ltcGxpZnkvc3BlZWQgdXAgZW5kc1dpdGhCbGFua0xpbmUgbG9naWMKICAgICAgbGlzdC5pdGVtc1tsaXN0Lml0ZW1zLmxlbmd0aCAtIDFdLnJhdyA9IHJhdy50cmltUmlnaHQoKQogICAgICBsaXN0Lml0ZW1zW2xpc3QuaXRlbXMubGVuZ3RoIC0gMV0udGV4dCA9IGl0ZW1Db250ZW50cy50cmltUmlnaHQoKQogICAgICBsaXN0LnJhdyA9IGxpc3QucmF3LnRyaW1SaWdodCgpCgogICAgICBjb25zdCBsID0gbGlzdC5pdGVtcy5sZW5ndGgKCiAgICAgIC8vIEl0ZW0gY2hpbGQgdG9rZW5zIGhhbmRsZWQgaGVyZSBhdCBlbmQgYmVjYXVzZSB3ZSBuZWVkZWQgdG8gaGF2ZSB0aGUgZmluYWwgaXRlbSB0byB0cmltIGl0IGZpcnN0CiAgICAgIGZvciAoaSA9IDA7IGkgPCBsOyBpKyspIHsKICAgICAgICB0aGlzLmxleGVyLnN0YXRlLnRvcCA9IGZhbHNlCiAgICAgICAgbGlzdC5pdGVtc1tpXS50b2tlbnMgPSB0aGlzLmxleGVyLmJsb2NrVG9rZW5zKGxpc3QuaXRlbXNbaV0udGV4dCwgW10pCiAgICAgICAgY29uc3Qgc3BhY2VycyA9IGxpc3QuaXRlbXNbaV0udG9rZW5zLmZpbHRlcih0ID0+IHQudHlwZSA9PT0gInNwYWNlIikKICAgICAgICBjb25zdCBoYXNNdWx0aXBsZUxpbmVCcmVha3MgPSBzcGFjZXJzLmV2ZXJ5KHQgPT4gewogICAgICAgICAgY29uc3QgY2hhcnMgPSB0LnJhdy5zcGxpdCgiIikKICAgICAgICAgIGxldCBsaW5lQnJlYWtzID0gMAogICAgICAgICAgZm9yIChjb25zdCBjaGFyIG9mIGNoYXJzKSB7CiAgICAgICAgICAgIGlmIChjaGFyID09PSAiXG4iKSB7CiAgICAgICAgICAgICAgbGluZUJyZWFrcyArPSAxCiAgICAgICAgICAgIH0KICAgICAgICAgICAgaWYgKGxpbmVCcmVha3MgPiAxKSB7CiAgICAgICAgICAgICAgcmV0dXJuIHRydWUKICAgICAgICAgICAgfQogICAgICAgICAgfQoKICAgICAgICAgIHJldHVybiBmYWxzZQogICAgICAgIH0pCgogICAgICAgIGlmICghbGlzdC5sb29zZSAmJiBzcGFjZXJzLmxlbmd0aCAmJiBoYXNNdWx0aXBsZUxpbmVCcmVha3MpIHsKICAgICAgICAgIC8vIEhhdmluZyBhIHNpbmdsZSBsaW5lIGJyZWFrIGRvZXNuJ3QgbWVhbiBhIGxpc3QgaXMgbG9vc2UuIEEgc2luZ2xlIGxpbmUgYnJlYWsgaXMgdGVybWluYXRpbmcgdGhlIGxhc3QgbGlzdCBpdGVtCiAgICAgICAgICBsaXN0Lmxvb3NlID0gdHJ1ZQogICAgICAgICAgbGlzdC5pdGVtc1tpXS5sb29zZSA9IHRydWUKICAgICAgICB9CiAgICAgIH0KCiAgICAgIHJldHVybiBsaXN0CiAgICB9CiAgfQoKICBodG1sKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5odG1sLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBjb25zdCB0b2tlbiA9IHsKICAgICAgICB0eXBlOiAiaHRtbCIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgICAgcHJlOgogICAgICAgICAgIXRoaXMub3B0aW9ucy5zYW5pdGl6ZXIgJiYKICAgICAgICAgIChjYXBbMV0gPT09ICJwcmUiIHx8IGNhcFsxXSA9PT0gInNjcmlwdCIgfHwgY2FwWzFdID09PSAic3R5bGUiKSwKICAgICAgICB0ZXh0OiBjYXBbMF0sCiAgICAgIH0KICAgICAgaWYgKHRoaXMub3B0aW9ucy5zYW5pdGl6ZSkgewogICAgICAgIHRva2VuLnR5cGUgPSAicGFyYWdyYXBoIgogICAgICAgIHRva2VuLnRleHQgPSB0aGlzLm9wdGlvbnMuc2FuaXRpemVyCiAgICAgICAgICA/IHRoaXMub3B0aW9ucy5zYW5pdGl6ZXIoY2FwWzBdKQogICAgICAgICAgOiBlc2NhcGUoY2FwWzBdKQogICAgICAgIHRva2VuLnRva2VucyA9IFtdCiAgICAgICAgdGhpcy5sZXhlci5pbmxpbmUodG9rZW4udGV4dCwgdG9rZW4udG9rZW5zKQogICAgICB9CiAgICAgIHJldHVybiB0b2tlbgogICAgfQogIH0KCiAgZGVmKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5kZWYuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGlmIChjYXBbM10pIGNhcFszXSA9IGNhcFszXS5zdWJzdHJpbmcoMSwgY2FwWzNdLmxlbmd0aCAtIDEpCiAgICAgIGNvbnN0IHRhZyA9IGNhcFsxXS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL1xzKy9nLCAiICIpCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImRlZiIsCiAgICAgICAgdGFnLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGhyZWY6IGNhcFsyXSwKICAgICAgICB0aXRsZTogY2FwWzNdLAogICAgICB9CiAgICB9CiAgfQoKICB0YWJsZShzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuYmxvY2sudGFibGUuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGNvbnN0IGl0ZW0gPSB7CiAgICAgICAgdHlwZTogInRhYmxlIiwKICAgICAgICBoZWFkZXI6IHNwbGl0Q2VsbHMoY2FwWzFdKS5tYXAoYyA9PiB7CiAgICAgICAgICByZXR1cm4geyB0ZXh0OiBjIH0KICAgICAgICB9KSwKICAgICAgICBhbGlnbjogY2FwWzJdLnJlcGxhY2UoL14gKnxcfCAqJC9nLCAiIikuc3BsaXQoLyAqXHwgKi8pLAogICAgICAgIHJvd3M6CiAgICAgICAgICBjYXBbM10gJiYgY2FwWzNdLnRyaW0oKQogICAgICAgICAgICA/IGNhcFszXS5yZXBsYWNlKC9cblsgXHRdKiQvLCAiIikuc3BsaXQoIlxuIikKICAgICAgICAgICAgOiBbXSwKICAgICAgfQoKICAgICAgaWYgKGl0ZW0uaGVhZGVyLmxlbmd0aCA9PT0gaXRlbS5hbGlnbi5sZW5ndGgpIHsKICAgICAgICBpdGVtLnJhdyA9IGNhcFswXQoKICAgICAgICBsZXQgbCA9IGl0ZW0uYWxpZ24ubGVuZ3RoCiAgICAgICAgbGV0IGksIGosIGssIHJvdwogICAgICAgIGZvciAoaSA9IDA7IGkgPCBsOyBpKyspIHsKICAgICAgICAgIGlmICgvXiAqLSs6ICokLy50ZXN0KGl0ZW0uYWxpZ25baV0pKSB7CiAgICAgICAgICAgIGl0ZW0uYWxpZ25baV0gPSAicmlnaHQiCiAgICAgICAgICB9IGVsc2UgaWYgKC9eICo6LSs6ICokLy50ZXN0KGl0ZW0uYWxpZ25baV0pKSB7CiAgICAgICAgICAgIGl0ZW0uYWxpZ25baV0gPSAiY2VudGVyIgogICAgICAgICAgfSBlbHNlIGlmICgvXiAqOi0rICokLy50ZXN0KGl0ZW0uYWxpZ25baV0pKSB7CiAgICAgICAgICAgIGl0ZW0uYWxpZ25baV0gPSAibGVmdCIKICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGl0ZW0uYWxpZ25baV0gPSBudWxsCiAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBsID0gaXRlbS5yb3dzLmxlbmd0aAogICAgICAgIGZvciAoaSA9IDA7IGkgPCBsOyBpKyspIHsKICAgICAgICAgIGl0ZW0ucm93c1tpXSA9IHNwbGl0Q2VsbHMoaXRlbS5yb3dzW2ldLCBpdGVtLmhlYWRlci5sZW5ndGgpLm1hcChjID0+IHsKICAgICAgICAgICAgcmV0dXJuIHsgdGV4dDogYyB9CiAgICAgICAgICB9KQogICAgICAgIH0KCiAgICAgICAgLy8gcGFyc2UgY2hpbGQgdG9rZW5zIGluc2lkZSBoZWFkZXJzIGFuZCBjZWxscwoKICAgICAgICAvLyBoZWFkZXIgY2hpbGQgdG9rZW5zCiAgICAgICAgbCA9IGl0ZW0uaGVhZGVyLmxlbmd0aAogICAgICAgIGZvciAoaiA9IDA7IGogPCBsOyBqKyspIHsKICAgICAgICAgIGl0ZW0uaGVhZGVyW2pdLnRva2VucyA9IFtdCiAgICAgICAgICB0aGlzLmxleGVyLmlubGluZShpdGVtLmhlYWRlcltqXS50ZXh0LCBpdGVtLmhlYWRlcltqXS50b2tlbnMpCiAgICAgICAgfQoKICAgICAgICAvLyBjZWxsIGNoaWxkIHRva2VucwogICAgICAgIGwgPSBpdGVtLnJvd3MubGVuZ3RoCiAgICAgICAgZm9yIChqID0gMDsgaiA8IGw7IGorKykgewogICAgICAgICAgcm93ID0gaXRlbS5yb3dzW2pdCiAgICAgICAgICBmb3IgKGsgPSAwOyBrIDwgcm93Lmxlbmd0aDsgaysrKSB7CiAgICAgICAgICAgIHJvd1trXS50b2tlbnMgPSBbXQogICAgICAgICAgICB0aGlzLmxleGVyLmlubGluZShyb3dba10udGV4dCwgcm93W2tdLnRva2VucykKICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIHJldHVybiBpdGVtCiAgICAgIH0KICAgIH0KICB9CgogIGxoZWFkaW5nKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5saGVhZGluZy5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgY29uc3QgdG9rZW4gPSB7CiAgICAgICAgdHlwZTogImhlYWRpbmciLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGRlcHRoOiBjYXBbMl0uY2hhckF0KDApID09PSAiPSIgPyAxIDogMiwKICAgICAgICB0ZXh0OiBjYXBbMV0sCiAgICAgICAgdG9rZW5zOiBbXSwKICAgICAgfQogICAgICB0aGlzLmxleGVyLmlubGluZSh0b2tlbi50ZXh0LCB0b2tlbi50b2tlbnMpCiAgICAgIHJldHVybiB0b2tlbgogICAgfQogIH0KCiAgcGFyYWdyYXBoKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5ibG9jay5wYXJhZ3JhcGguZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGNvbnN0IHRva2VuID0gewogICAgICAgIHR5cGU6ICJwYXJhZ3JhcGgiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIHRleHQ6CiAgICAgICAgICBjYXBbMV0uY2hhckF0KGNhcFsxXS5sZW5ndGggLSAxKSA9PT0gIlxuIgogICAgICAgICAgICA/IGNhcFsxXS5zbGljZSgwLCAtMSkKICAgICAgICAgICAgOiBjYXBbMV0sCiAgICAgICAgdG9rZW5zOiBbXSwKICAgICAgfQogICAgICB0aGlzLmxleGVyLmlubGluZSh0b2tlbi50ZXh0LCB0b2tlbi50b2tlbnMpCiAgICAgIHJldHVybiB0b2tlbgogICAgfQogIH0KCiAgdGV4dChzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuYmxvY2sudGV4dC5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgY29uc3QgdG9rZW4gPSB7CiAgICAgICAgdHlwZTogInRleHQiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIHRleHQ6IGNhcFswXSwKICAgICAgICB0b2tlbnM6IFtdLAogICAgICB9CiAgICAgIHRoaXMubGV4ZXIuaW5saW5lKHRva2VuLnRleHQsIHRva2VuLnRva2VucykKICAgICAgcmV0dXJuIHRva2VuCiAgICB9CiAgfQoKICBlc2NhcGUoc3JjKSB7CiAgICBjb25zdCBjYXAgPSB0aGlzLnJ1bGVzLmlubGluZS5lc2NhcGUuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImVzY2FwZSIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgICAgdGV4dDogZXNjYXBlKGNhcFsxXSksCiAgICAgIH0KICAgIH0KICB9CgogIHRhZyhzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLnRhZy5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgaWYgKCF0aGlzLmxleGVyLnN0YXRlLmluTGluayAmJiAvXjxhIC9pLnRlc3QoY2FwWzBdKSkgewogICAgICAgIHRoaXMubGV4ZXIuc3RhdGUuaW5MaW5rID0gdHJ1ZQogICAgICB9IGVsc2UgaWYgKHRoaXMubGV4ZXIuc3RhdGUuaW5MaW5rICYmIC9ePFwvYT4vaS50ZXN0KGNhcFswXSkpIHsKICAgICAgICB0aGlzLmxleGVyLnN0YXRlLmluTGluayA9IGZhbHNlCiAgICAgIH0KICAgICAgaWYgKAogICAgICAgICF0aGlzLmxleGVyLnN0YXRlLmluUmF3QmxvY2sgJiYKICAgICAgICAvXjwocHJlfGNvZGV8a2JkfHNjcmlwdCkoXHN8PikvaS50ZXN0KGNhcFswXSkKICAgICAgKSB7CiAgICAgICAgdGhpcy5sZXhlci5zdGF0ZS5pblJhd0Jsb2NrID0gdHJ1ZQogICAgICB9IGVsc2UgaWYgKAogICAgICAgIHRoaXMubGV4ZXIuc3RhdGUuaW5SYXdCbG9jayAmJgogICAgICAgIC9ePFwvKHByZXxjb2RlfGtiZHxzY3JpcHQpKFxzfD4pL2kudGVzdChjYXBbMF0pCiAgICAgICkgewogICAgICAgIHRoaXMubGV4ZXIuc3RhdGUuaW5SYXdCbG9jayA9IGZhbHNlCiAgICAgIH0KCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogdGhpcy5vcHRpb25zLnNhbml0aXplID8gInRleHQiIDogImh0bWwiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIGluTGluazogdGhpcy5sZXhlci5zdGF0ZS5pbkxpbmssCiAgICAgICAgaW5SYXdCbG9jazogdGhpcy5sZXhlci5zdGF0ZS5pblJhd0Jsb2NrLAogICAgICAgIHRleHQ6IHRoaXMub3B0aW9ucy5zYW5pdGl6ZQogICAgICAgICAgPyB0aGlzLm9wdGlvbnMuc2FuaXRpemVyCiAgICAgICAgICAgID8gdGhpcy5vcHRpb25zLnNhbml0aXplcihjYXBbMF0pCiAgICAgICAgICAgIDogZXNjYXBlKGNhcFswXSkKICAgICAgICAgIDogY2FwWzBdLAogICAgICB9CiAgICB9CiAgfQoKICBsaW5rKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5pbmxpbmUubGluay5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgY29uc3QgdHJpbW1lZFVybCA9IGNhcFsyXS50cmltKCkKICAgICAgaWYgKCF0aGlzLm9wdGlvbnMucGVkYW50aWMgJiYgL148Ly50ZXN0KHRyaW1tZWRVcmwpKSB7CiAgICAgICAgLy8gY29tbW9ubWFyayByZXF1aXJlcyBtYXRjaGluZyBhbmdsZSBicmFja2V0cwogICAgICAgIGlmICghLz4kLy50ZXN0KHRyaW1tZWRVcmwpKSB7CiAgICAgICAgICByZXR1cm4KICAgICAgICB9CgogICAgICAgIC8vIGVuZGluZyBhbmdsZSBicmFja2V0IGNhbm5vdCBiZSBlc2NhcGVkCiAgICAgICAgY29uc3QgcnRyaW1TbGFzaCA9IHJ0cmltKHRyaW1tZWRVcmwuc2xpY2UoMCwgLTEpLCAiXFwiKQogICAgICAgIGlmICgodHJpbW1lZFVybC5sZW5ndGggLSBydHJpbVNsYXNoLmxlbmd0aCkgJSAyID09PSAwKSB7CiAgICAgICAgICByZXR1cm4KICAgICAgICB9CiAgICAgIH0gZWxzZSB7CiAgICAgICAgLy8gZmluZCBjbG9zaW5nIHBhcmVudGhlc2lzCiAgICAgICAgY29uc3QgbGFzdFBhcmVuSW5kZXggPSBmaW5kQ2xvc2luZ0JyYWNrZXQoY2FwWzJdLCAiKCkiKQogICAgICAgIGlmIChsYXN0UGFyZW5JbmRleCA+IC0xKSB7CiAgICAgICAgICBjb25zdCBzdGFydCA9IGNhcFswXS5pbmRleE9mKCIhIikgPT09IDAgPyA1IDogNAogICAgICAgICAgY29uc3QgbGlua0xlbiA9IHN0YXJ0ICsgY2FwWzFdLmxlbmd0aCArIGxhc3RQYXJlbkluZGV4CiAgICAgICAgICBjYXBbMl0gPSBjYXBbMl0uc3Vic3RyaW5nKDAsIGxhc3RQYXJlbkluZGV4KQogICAgICAgICAgY2FwWzBdID0gY2FwWzBdLnN1YnN0cmluZygwLCBsaW5rTGVuKS50cmltKCkKICAgICAgICAgIGNhcFszXSA9ICIiCiAgICAgICAgfQogICAgICB9CiAgICAgIGxldCBocmVmID0gY2FwWzJdCiAgICAgIGxldCB0aXRsZSA9ICIiCiAgICAgIGlmICh0aGlzLm9wdGlvbnMucGVkYW50aWMpIHsKICAgICAgICAvLyBzcGxpdCBwZWRhbnRpYyBocmVmIGFuZCB0aXRsZQogICAgICAgIGNvbnN0IGxpbmsgPSAvXihbXiciXSpbXlxzXSlccysoWyciXSkoLiopXDIvLmV4ZWMoaHJlZikKCiAgICAgICAgaWYgKGxpbmspIHsKICAgICAgICAgIGhyZWYgPSBsaW5rWzFdCiAgICAgICAgICB0aXRsZSA9IGxpbmtbM10KICAgICAgICB9CiAgICAgIH0gZWxzZSB7CiAgICAgICAgdGl0bGUgPSBjYXBbM10gPyBjYXBbM10uc2xpY2UoMSwgLTEpIDogIiIKICAgICAgfQoKICAgICAgaHJlZiA9IGhyZWYudHJpbSgpCiAgICAgIGlmICgvXjwvLnRlc3QoaHJlZikpIHsKICAgICAgICBpZiAodGhpcy5vcHRpb25zLnBlZGFudGljICYmICEvPiQvLnRlc3QodHJpbW1lZFVybCkpIHsKICAgICAgICAgIC8vIHBlZGFudGljIGFsbG93cyBzdGFydGluZyBhbmdsZSBicmFja2V0IHdpdGhvdXQgZW5kaW5nIGFuZ2xlIGJyYWNrZXQKICAgICAgICAgIGhyZWYgPSBocmVmLnNsaWNlKDEpCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIGhyZWYgPSBocmVmLnNsaWNlKDEsIC0xKQogICAgICAgIH0KICAgICAgfQogICAgICByZXR1cm4gb3V0cHV0TGluaygKICAgICAgICBjYXAsCiAgICAgICAgewogICAgICAgICAgaHJlZjogaHJlZiA/IGhyZWYucmVwbGFjZSh0aGlzLnJ1bGVzLmlubGluZS5fZXNjYXBlcywgIiQxIikgOiBocmVmLAogICAgICAgICAgdGl0bGU6IHRpdGxlCiAgICAgICAgICAgID8gdGl0bGUucmVwbGFjZSh0aGlzLnJ1bGVzLmlubGluZS5fZXNjYXBlcywgIiQxIikKICAgICAgICAgICAgOiB0aXRsZSwKICAgICAgICB9LAogICAgICAgIGNhcFswXSwKICAgICAgICB0aGlzLmxleGVyCiAgICAgICkKICAgIH0KICB9CgogIHJlZmxpbmsoc3JjLCBsaW5rcykgewogICAgbGV0IGNhcAogICAgaWYgKAogICAgICAoY2FwID0gdGhpcy5ydWxlcy5pbmxpbmUucmVmbGluay5leGVjKHNyYykpIHx8CiAgICAgIChjYXAgPSB0aGlzLnJ1bGVzLmlubGluZS5ub2xpbmsuZXhlYyhzcmMpKQogICAgKSB7CiAgICAgIGxldCBsaW5rID0gKGNhcFsyXSB8fCBjYXBbMV0pLnJlcGxhY2UoL1xzKy9nLCAiICIpCiAgICAgIGxpbmsgPSBsaW5rc1tsaW5rLnRvTG93ZXJDYXNlKCldCiAgICAgIGlmICghbGluayB8fCAhbGluay5ocmVmKSB7CiAgICAgICAgY29uc3QgdGV4dCA9IGNhcFswXS5jaGFyQXQoMCkKICAgICAgICByZXR1cm4gewogICAgICAgICAgdHlwZTogInRleHQiLAogICAgICAgICAgcmF3OiB0ZXh0LAogICAgICAgICAgdGV4dCwKICAgICAgICB9CiAgICAgIH0KICAgICAgcmV0dXJuIG91dHB1dExpbmsoY2FwLCBsaW5rLCBjYXBbMF0sIHRoaXMubGV4ZXIpCiAgICB9CiAgfQoKICBlbVN0cm9uZyhzcmMsIG1hc2tlZFNyYywgcHJldkNoYXIgPSAiIikgewogICAgbGV0IG1hdGNoID0gdGhpcy5ydWxlcy5pbmxpbmUuZW1TdHJvbmcubERlbGltLmV4ZWMoc3JjKQogICAgaWYgKCFtYXRjaCkgcmV0dXJuCgogICAgLy8gXyBjYW4ndCBiZSBiZXR3ZWVuIHR3byBhbHBoYW51bWVyaWNzLiBccHtMfVxwe059IGluY2x1ZGVzIG5vbi1lbmdsaXNoIGFscGhhYmV0L251bWJlcnMgYXMgd2VsbAogICAgaWYgKG1hdGNoWzNdICYmIHByZXZDaGFyLm1hdGNoKC9bXHB7TH1ccHtOfV0vdSkpIHJldHVybgoKICAgIGNvbnN0IG5leHRDaGFyID0gbWF0Y2hbMV0gfHwgbWF0Y2hbMl0gfHwgIiIKCiAgICBpZiAoCiAgICAgICFuZXh0Q2hhciB8fAogICAgICAobmV4dENoYXIgJiYKICAgICAgICAocHJldkNoYXIgPT09ICIiIHx8IHRoaXMucnVsZXMuaW5saW5lLnB1bmN0dWF0aW9uLmV4ZWMocHJldkNoYXIpKSkKICAgICkgewogICAgICBjb25zdCBsTGVuZ3RoID0gbWF0Y2hbMF0ubGVuZ3RoIC0gMQogICAgICBsZXQgckRlbGltLAogICAgICAgIHJMZW5ndGgsCiAgICAgICAgZGVsaW1Ub3RhbCA9IGxMZW5ndGgsCiAgICAgICAgbWlkRGVsaW1Ub3RhbCA9IDAKCiAgICAgIGNvbnN0IGVuZFJlZyA9CiAgICAgICAgbWF0Y2hbMF1bMF0gPT09ICIqIgogICAgICAgICAgPyB0aGlzLnJ1bGVzLmlubGluZS5lbVN0cm9uZy5yRGVsaW1Bc3QKICAgICAgICAgIDogdGhpcy5ydWxlcy5pbmxpbmUuZW1TdHJvbmcuckRlbGltVW5kCiAgICAgIGVuZFJlZy5sYXN0SW5kZXggPSAwCgogICAgICAvLyBDbGlwIG1hc2tlZFNyYyB0byBzYW1lIHNlY3Rpb24gb2Ygc3RyaW5nIGFzIHNyYyAobW92ZSB0byBsZXhlcj8pCiAgICAgIG1hc2tlZFNyYyA9IG1hc2tlZFNyYy5zbGljZSgtMSAqIHNyYy5sZW5ndGggKyBsTGVuZ3RoKQoKICAgICAgd2hpbGUgKChtYXRjaCA9IGVuZFJlZy5leGVjKG1hc2tlZFNyYykpICE9IG51bGwpIHsKICAgICAgICByRGVsaW0gPQogICAgICAgICAgbWF0Y2hbMV0gfHwgbWF0Y2hbMl0gfHwgbWF0Y2hbM10gfHwgbWF0Y2hbNF0gfHwgbWF0Y2hbNV0gfHwgbWF0Y2hbNl0KCiAgICAgICAgaWYgKCFyRGVsaW0pIGNvbnRpbnVlIC8vIHNraXAgc2luZ2xlICogaW4gX19hYmMqYWJjX18KCiAgICAgICAgckxlbmd0aCA9IHJEZWxpbS5sZW5ndGgKCiAgICAgICAgaWYgKG1hdGNoWzNdIHx8IG1hdGNoWzRdKSB7CiAgICAgICAgICAvLyBmb3VuZCBhbm90aGVyIExlZnQgRGVsaW0KICAgICAgICAgIGRlbGltVG90YWwgKz0gckxlbmd0aAogICAgICAgICAgY29udGludWUKICAgICAgICB9IGVsc2UgaWYgKG1hdGNoWzVdIHx8IG1hdGNoWzZdKSB7CiAgICAgICAgICAvLyBlaXRoZXIgTGVmdCBvciBSaWdodCBEZWxpbQogICAgICAgICAgaWYgKGxMZW5ndGggJSAzICYmICEoKGxMZW5ndGggKyByTGVuZ3RoKSAlIDMpKSB7CiAgICAgICAgICAgIG1pZERlbGltVG90YWwgKz0gckxlbmd0aAogICAgICAgICAgICBjb250aW51ZSAvLyBDb21tb25NYXJrIEVtcGhhc2lzIFJ1bGVzIDktMTAKICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIGRlbGltVG90YWwgLT0gckxlbmd0aAoKICAgICAgICBpZiAoZGVsaW1Ub3RhbCA+IDApIGNvbnRpbnVlIC8vIEhhdmVuJ3QgZm91bmQgZW5vdWdoIGNsb3NpbmcgZGVsaW1pdGVycwoKICAgICAgICAvLyBSZW1vdmUgZXh0cmEgY2hhcmFjdGVycy4gKmEqKiogLT4gKmEqCiAgICAgICAgckxlbmd0aCA9IE1hdGgubWluKHJMZW5ndGgsIHJMZW5ndGggKyBkZWxpbVRvdGFsICsgbWlkRGVsaW1Ub3RhbCkKCiAgICAgICAgLy8gQ3JlYXRlIGBlbWAgaWYgc21hbGxlc3QgZGVsaW1pdGVyIGhhcyBvZGQgY2hhciBjb3VudC4gKmEqKioKICAgICAgICBpZiAoTWF0aC5taW4obExlbmd0aCwgckxlbmd0aCkgJSAyKSB7CiAgICAgICAgICBjb25zdCB0ZXh0ID0gc3JjLnNsaWNlKDEsIGxMZW5ndGggKyBtYXRjaC5pbmRleCArIHJMZW5ndGgpCiAgICAgICAgICByZXR1cm4gewogICAgICAgICAgICB0eXBlOiAiZW0iLAogICAgICAgICAgICByYXc6IHNyYy5zbGljZSgwLCBsTGVuZ3RoICsgbWF0Y2guaW5kZXggKyByTGVuZ3RoICsgMSksCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICAgIHRva2VuczogdGhpcy5sZXhlci5pbmxpbmVUb2tlbnModGV4dCwgW10pLAogICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgLy8gQ3JlYXRlICdzdHJvbmcnIGlmIHNtYWxsZXN0IGRlbGltaXRlciBoYXMgZXZlbiBjaGFyIGNvdW50LiAqKmEqKioKICAgICAgICBjb25zdCB0ZXh0ID0gc3JjLnNsaWNlKDIsIGxMZW5ndGggKyBtYXRjaC5pbmRleCArIHJMZW5ndGggLSAxKQogICAgICAgIHJldHVybiB7CiAgICAgICAgICB0eXBlOiAic3Ryb25nIiwKICAgICAgICAgIHJhdzogc3JjLnNsaWNlKDAsIGxMZW5ndGggKyBtYXRjaC5pbmRleCArIHJMZW5ndGggKyAxKSwKICAgICAgICAgIHRleHQsCiAgICAgICAgICB0b2tlbnM6IHRoaXMubGV4ZXIuaW5saW5lVG9rZW5zKHRleHQsIFtdKSwKICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9CgogIGNvZGVzcGFuKHNyYykgewogICAgY29uc3QgY2FwID0gdGhpcy5ydWxlcy5pbmxpbmUuY29kZS5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgbGV0IHRleHQgPSBjYXBbMl0ucmVwbGFjZSgvXG4vZywgIiAiKQogICAgICBjb25zdCBoYXNOb25TcGFjZUNoYXJzID0gL1teIF0vLnRlc3QodGV4dCkKICAgICAgY29uc3QgaGFzU3BhY2VDaGFyc09uQm90aEVuZHMgPSAvXiAvLnRlc3QodGV4dCkgJiYgLyAkLy50ZXN0KHRleHQpCiAgICAgIGlmIChoYXNOb25TcGFjZUNoYXJzICYmIGhhc1NwYWNlQ2hhcnNPbkJvdGhFbmRzKSB7CiAgICAgICAgdGV4dCA9IHRleHQuc3Vic3RyaW5nKDEsIHRleHQubGVuZ3RoIC0gMSkKICAgICAgfQogICAgICB0ZXh0ID0gZXNjYXBlKHRleHQsIHRydWUpCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImNvZGVzcGFuIiwKICAgICAgICByYXc6IGNhcFswXSwKICAgICAgICB0ZXh0LAogICAgICB9CiAgICB9CiAgfQoKICBicihzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLmJyLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJiciIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgIH0KICAgIH0KICB9CgogIGRlbChzcmMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLmRlbC5leGVjKHNyYykKICAgIGlmIChjYXApIHsKICAgICAgcmV0dXJuIHsKICAgICAgICB0eXBlOiAiZGVsIiwKICAgICAgICByYXc6IGNhcFswXSwKICAgICAgICB0ZXh0OiBjYXBbMl0sCiAgICAgICAgdG9rZW5zOiB0aGlzLmxleGVyLmlubGluZVRva2VucyhjYXBbMl0sIFtdKSwKICAgICAgfQogICAgfQogIH0KCiAgYXV0b2xpbmsoc3JjLCBtYW5nbGUpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLmF1dG9saW5rLmV4ZWMoc3JjKQogICAgaWYgKGNhcCkgewogICAgICBsZXQgdGV4dCwgaHJlZgogICAgICBpZiAoY2FwWzJdID09PSAiQCIpIHsKICAgICAgICB0ZXh0ID0gZXNjYXBlKHRoaXMub3B0aW9ucy5tYW5nbGUgPyBtYW5nbGUoY2FwWzFdKSA6IGNhcFsxXSkKICAgICAgICBocmVmID0gIm1haWx0bzoiICsgdGV4dAogICAgICB9IGVsc2UgewogICAgICAgIHRleHQgPSBlc2NhcGUoY2FwWzFdKQogICAgICAgIGhyZWYgPSB0ZXh0CiAgICAgIH0KCiAgICAgIHJldHVybiB7CiAgICAgICAgdHlwZTogImxpbmsiLAogICAgICAgIHJhdzogY2FwWzBdLAogICAgICAgIHRleHQsCiAgICAgICAgaHJlZiwKICAgICAgICB0b2tlbnM6IFsKICAgICAgICAgIHsKICAgICAgICAgICAgdHlwZTogInRleHQiLAogICAgICAgICAgICByYXc6IHRleHQsCiAgICAgICAgICAgIHRleHQsCiAgICAgICAgICB9LAogICAgICAgIF0sCiAgICAgIH0KICAgIH0KICB9CgogIHVybChzcmMsIG1hbmdsZSkgewogICAgbGV0IGNhcAogICAgaWYgKChjYXAgPSB0aGlzLnJ1bGVzLmlubGluZS51cmwuZXhlYyhzcmMpKSkgewogICAgICBsZXQgdGV4dCwgaHJlZgogICAgICBpZiAoY2FwWzJdID09PSAiQCIpIHsKICAgICAgICB0ZXh0ID0gZXNjYXBlKHRoaXMub3B0aW9ucy5tYW5nbGUgPyBtYW5nbGUoY2FwWzBdKSA6IGNhcFswXSkKICAgICAgICBocmVmID0gIm1haWx0bzoiICsgdGV4dAogICAgICB9IGVsc2UgewogICAgICAgIC8vIGRvIGV4dGVuZGVkIGF1dG9saW5rIHBhdGggdmFsaWRhdGlvbgogICAgICAgIGxldCBwcmV2Q2FwWmVybwogICAgICAgIGRvIHsKICAgICAgICAgIHByZXZDYXBaZXJvID0gY2FwWzBdCiAgICAgICAgICBjYXBbMF0gPSB0aGlzLnJ1bGVzLmlubGluZS5fYmFja3BlZGFsLmV4ZWMoY2FwWzBdKVswXQogICAgICAgIH0gd2hpbGUgKHByZXZDYXBaZXJvICE9PSBjYXBbMF0pCiAgICAgICAgdGV4dCA9IGVzY2FwZShjYXBbMF0pCiAgICAgICAgaWYgKGNhcFsxXSA9PT0gInd3dy4iKSB7CiAgICAgICAgICBocmVmID0gImh0dHA6Ly8iICsgdGV4dAogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICBocmVmID0gdGV4dAogICAgICAgIH0KICAgICAgfQogICAgICByZXR1cm4gewogICAgICAgIHR5cGU6ICJsaW5rIiwKICAgICAgICByYXc6IGNhcFswXSwKICAgICAgICB0ZXh0LAogICAgICAgIGhyZWYsCiAgICAgICAgdG9rZW5zOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgIHR5cGU6ICJ0ZXh0IiwKICAgICAgICAgICAgcmF3OiB0ZXh0LAogICAgICAgICAgICB0ZXh0LAogICAgICAgICAgfSwKICAgICAgICBdLAogICAgICB9CiAgICB9CiAgfQoKICBpbmxpbmVUZXh0KHNyYywgc21hcnR5cGFudHMpIHsKICAgIGNvbnN0IGNhcCA9IHRoaXMucnVsZXMuaW5saW5lLnRleHQuZXhlYyhzcmMpCiAgICBpZiAoY2FwKSB7CiAgICAgIGxldCB0ZXh0CiAgICAgIGlmICh0aGlzLmxleGVyLnN0YXRlLmluUmF3QmxvY2spIHsKICAgICAgICB0ZXh0ID0gdGhpcy5vcHRpb25zLnNhbml0aXplCiAgICAgICAgICA/IHRoaXMub3B0aW9ucy5zYW5pdGl6ZXIKICAgICAgICAgICAgPyB0aGlzLm9wdGlvbnMuc2FuaXRpemVyKGNhcFswXSkKICAgICAgICAgICAgOiBlc2NhcGUoY2FwWzBdKQogICAgICAgICAgOiBjYXBbMF0KICAgICAgfSBlbHNlIHsKICAgICAgICB0ZXh0ID0gZXNjYXBlKHRoaXMub3B0aW9ucy5zbWFydHlwYW50cyA/IHNtYXJ0eXBhbnRzKGNhcFswXSkgOiBjYXBbMF0pCiAgICAgIH0KICAgICAgcmV0dXJuIHsKICAgICAgICB0eXBlOiAidGV4dCIsCiAgICAgICAgcmF3OiBjYXBbMF0sCiAgICAgICAgdGV4dCwKICAgICAgfQogICAgfQogIH0KfQoKLyoqCiAqIEJsb2NrLUxldmVsIEdyYW1tYXIKICovCmNvbnN0IGJsb2NrID0gewogIG5ld2xpbmU6IC9eKD86ICooPzpcbnwkKSkrLywKICBjb2RlOiAvXiggezR9W15cbl0rKD86XG4oPzogKig/OlxufCQpKSopPykrLywKICBmZW5jZXM6CiAgICAvXiB7MCwzfShgezMsfSg/PVteYFxuXSpcbil8fnszLH0pKFteXG5dKilcbig/OnwoW1xzXFNdKj8pXG4pKD86IHswLDN9XDFbfmBdKiAqKD89XG58JCl8JCkvLAogIGhyOiAvXiB7MCwzfSgoPzotW1x0IF0qKXszLH18KD86X1sgXHRdKil7Myx9fCg/OlwqWyBcdF0qKXszLH0pKD86XG4rfCQpLywKICBoZWFkaW5nOiAvXiB7MCwzfSgjezEsNn0pKD89XHN8JCkoLiopKD86XG4rfCQpLywKICBibG9ja3F1b3RlOiAvXiggezAsM30+ID8ocGFyYWdyYXBofFteXG5dKikoPzpcbnwkKSkrLywKICBsaXN0OiAvXiggezAsM31idWxsKShbIFx0XVteXG5dKz8pPyg/OlxufCQpLywKICBodG1sOgogICAgIl4gezAsM30oPzoiICsgLy8gb3B0aW9uYWwgaW5kZW50YXRpb24KICAgICI8KHNjcmlwdHxwcmV8c3R5bGV8dGV4dGFyZWEpW1xccz5dW1xcc1xcU10qPyg/OjwvXFwxPlteXFxuXSpcXG4rfCQpIiArIC8vICgxKQogICAgInxjb21tZW50W15cXG5dKihcXG4rfCQpIiArIC8vICgyKQogICAgInw8XFw/W1xcc1xcU10qPyg/OlxcPz5cXG4qfCQpIiArIC8vICgzKQogICAgInw8IVtBLVpdW1xcc1xcU10qPyg/Oj5cXG4qfCQpIiArIC8vICg0KQogICAgInw8IVxcW0NEQVRBXFxbW1xcc1xcU10qPyg/OlxcXVxcXT5cXG4qfCQpIiArIC8vICg1KQogICAgInw8Lz8odGFnKSg/OiArfFxcbnwvPz4pW1xcc1xcU10qPyg/Oig/OlxcbiAqKStcXG58JCkiICsgLy8gKDYpCiAgICAifDwoPyFzY3JpcHR8cHJlfHN0eWxlfHRleHRhcmVhKShbYS16XVtcXHctXSopKD86YXR0cmlidXRlKSo/ICovPz4oPz1bIFxcdF0qKD86XFxufCQpKVtcXHNcXFNdKj8oPzooPzpcXG4gKikrXFxufCQpIiArIC8vICg3KSBvcGVuIHRhZwogICAgInw8Lyg/IXNjcmlwdHxwcmV8c3R5bGV8dGV4dGFyZWEpW2Etel1bXFx3LV0qXFxzKj4oPz1bIFxcdF0qKD86XFxufCQpKVtcXHNcXFNdKj8oPzooPzpcXG4gKikrXFxufCQpIiArIC8vICg3KSBjbG9zaW5nIHRhZwogICAgIikiLAogIGRlZjogL14gezAsM31cWyhsYWJlbClcXTogKig/OlxuICopPzw/KFteXHM+XSspPj8oPzooPzogKyg/OlxuICopP3wgKlxuICopKHRpdGxlKSk/ICooPzpcbit8JCkvLAogIHRhYmxlOiBub29wVGVzdCwKICBsaGVhZGluZzogL14oW15cbl0rKVxuIHswLDN9KD0rfC0rKSAqKD86XG4rfCQpLywKICAvLyByZWdleCB0ZW1wbGF0ZSwgcGxhY2Vob2xkZXJzIHdpbGwgYmUgcmVwbGFjZWQgYWNjb3JkaW5nIHRvIGRpZmZlcmVudCBwYXJhZ3JhcGgKICAvLyBpbnRlcnJ1cHRpb24gcnVsZXMgb2YgY29tbW9ubWFyayBhbmQgdGhlIG9yaWdpbmFsIG1hcmtkb3duIHNwZWM6CiAgX3BhcmFncmFwaDoKICAgIC9eKFteXG5dKyg/OlxuKD8haHJ8aGVhZGluZ3xsaGVhZGluZ3xibG9ja3F1b3RlfGZlbmNlc3xsaXN0fGh0bWx8dGFibGV8ICtcbilbXlxuXSspKikvLAogIHRleHQ6IC9eW15cbl0rLywKfQoKYmxvY2suX2xhYmVsID0gLyg/IVxzKlxdKSg/OlxcLnxbXlxbXF1cXF0pKy8KYmxvY2suX3RpdGxlID0gLyg/OiIoPzpcXCI/fFteIlxcXSkqInwnW14nXG5dKig/OlxuW14nXG5dKykqXG4/J3xcKFteKCldKlwpKS8KYmxvY2suZGVmID0gZWRpdChibG9jay5kZWYpCiAgLnJlcGxhY2UoImxhYmVsIiwgYmxvY2suX2xhYmVsKQogIC5yZXBsYWNlKCJ0aXRsZSIsIGJsb2NrLl90aXRsZSkKICAuZ2V0UmVnZXgoKQoKYmxvY2suYnVsbGV0ID0gLyg/OlsqKy1dfFxkezEsOX1bLildKS8KYmxvY2subGlzdEl0ZW1TdGFydCA9IGVkaXQoL14oICopKGJ1bGwpICovKQogIC5yZXBsYWNlKCJidWxsIiwgYmxvY2suYnVsbGV0KQogIC5nZXRSZWdleCgpCgpibG9jay5saXN0ID0gZWRpdChibG9jay5saXN0KQogIC5yZXBsYWNlKC9idWxsL2csIGJsb2NrLmJ1bGxldCkKICAucmVwbGFjZSgKICAgICJociIsCiAgICAiXFxuKyg/PVxcMT8oPzooPzotICopezMsfXwoPzpfICopezMsfXwoPzpcXCogKil7Myx9KSg/Olxcbit8JCkpIgogICkKICAucmVwbGFjZSgiZGVmIiwgIlxcbisoPz0iICsgYmxvY2suZGVmLnNvdXJjZSArICIpIikKICAuZ2V0UmVnZXgoKQoKYmxvY2suX3RhZyA9CiAgImFkZHJlc3N8YXJ0aWNsZXxhc2lkZXxiYXNlfGJhc2Vmb250fGJsb2NrcXVvdGV8Ym9keXxjYXB0aW9uIiArCiAgInxjZW50ZXJ8Y29sfGNvbGdyb3VwfGRkfGRldGFpbHN8ZGlhbG9nfGRpcnxkaXZ8ZGx8ZHR8ZmllbGRzZXR8ZmlnY2FwdGlvbiIgKwogICJ8ZmlndXJlfGZvb3Rlcnxmb3JtfGZyYW1lfGZyYW1lc2V0fGhbMS02XXxoZWFkfGhlYWRlcnxocnxodG1sfGlmcmFtZSIgKwogICJ8bGVnZW5kfGxpfGxpbmt8bWFpbnxtZW51fG1lbnVpdGVtfG1ldGF8bmF2fG5vZnJhbWVzfG9sfG9wdGdyb3VwfG9wdGlvbiIgKwogICJ8cHxwYXJhbXxzZWN0aW9ufHNvdXJjZXxzdW1tYXJ5fHRhYmxlfHRib2R5fHRkfHRmb290fHRofHRoZWFkfHRpdGxlfHRyIiArCiAgInx0cmFja3x1bCIKYmxvY2suX2NvbW1lbnQgPSAvPCEtLSg/IS0/PilbXHNcU10qPyg/Oi0tPnwkKS8KYmxvY2suaHRtbCA9IGVkaXQoYmxvY2suaHRtbCwgImkiKQogIC5yZXBsYWNlKCJjb21tZW50IiwgYmxvY2suX2NvbW1lbnQpCiAgLnJlcGxhY2UoInRhZyIsIGJsb2NrLl90YWcpCiAgLnJlcGxhY2UoCiAgICAiYXR0cmlidXRlIiwKICAgIC8gK1thLXpBLVo6X11bXHcuOi1dKig/OiAqPSAqIlteIlxuXSoifCAqPSAqJ1teJ1xuXSonfCAqPSAqW15ccyInPTw+YF0rKT8vCiAgKQogIC5nZXRSZWdleCgpCgpibG9jay5wYXJhZ3JhcGggPSBlZGl0KGJsb2NrLl9wYXJhZ3JhcGgpCiAgLnJlcGxhY2UoImhyIiwgYmxvY2suaHIpCiAgLnJlcGxhY2UoImhlYWRpbmciLCAiIHswLDN9I3sxLDZ9ICIpCiAgLnJlcGxhY2UoInxsaGVhZGluZyIsICIiKSAvLyBzZXRleCBoZWFkaW5ncyBkb24ndCBpbnRlcnJ1cHQgY29tbW9ubWFyayBwYXJhZ3JhcGhzCiAgLnJlcGxhY2UoInx0YWJsZSIsICIiKQogIC5yZXBsYWNlKCJibG9ja3F1b3RlIiwgIiB7MCwzfT4iKQogIC5yZXBsYWNlKCJmZW5jZXMiLCAiIHswLDN9KD86YHszLH0oPz1bXmBcXG5dKlxcbil8fnszLH0pW15cXG5dKlxcbiIpCiAgLnJlcGxhY2UoImxpc3QiLCAiIHswLDN9KD86WyorLV18MVsuKV0pICIpIC8vIG9ubHkgbGlzdHMgc3RhcnRpbmcgZnJvbSAxIGNhbiBpbnRlcnJ1cHQKICAucmVwbGFjZSgKICAgICJodG1sIiwKICAgICI8Lz8oPzp0YWcpKD86ICt8XFxufC8/Pil8PCg/OnNjcmlwdHxwcmV8c3R5bGV8dGV4dGFyZWF8IS0tKSIKICApCiAgLnJlcGxhY2UoInRhZyIsIGJsb2NrLl90YWcpIC8vIHBhcnMgY2FuIGJlIGludGVycnVwdGVkIGJ5IHR5cGUgKDYpIGh0bWwgYmxvY2tzCiAgLmdldFJlZ2V4KCkKCmJsb2NrLmJsb2NrcXVvdGUgPSBlZGl0KGJsb2NrLmJsb2NrcXVvdGUpCiAgLnJlcGxhY2UoInBhcmFncmFwaCIsIGJsb2NrLnBhcmFncmFwaCkKICAuZ2V0UmVnZXgoKQoKLyoqCiAqIE5vcm1hbCBCbG9jayBHcmFtbWFyCiAqLwoKYmxvY2subm9ybWFsID0gbWVyZ2Uoe30sIGJsb2NrKQoKLyoqCiAqIEdGTSBCbG9jayBHcmFtbWFyCiAqLwoKYmxvY2suZ2ZtID0gbWVyZ2Uoe30sIGJsb2NrLm5vcm1hbCwgewogIHRhYmxlOgogICAgIl4gKihbXlxcbiBdLipcXHwuKilcXG4iICsgLy8gSGVhZGVyCiAgICAiIHswLDN9KD86XFx8ICopPyg6Py0rOj8gKig/OlxcfCAqOj8tKzo/ICopKikoPzpcXHwgKik/IiArIC8vIEFsaWduCiAgICAiKD86XFxuKCg/Oig/ISAqXFxufGhyfGhlYWRpbmd8YmxvY2txdW90ZXxjb2RlfGZlbmNlc3xsaXN0fGh0bWwpLiooPzpcXG58JCkpKilcXG4qfCQpIiwgLy8gQ2VsbHMKfSkKCmJsb2NrLmdmbS50YWJsZSA9IGVkaXQoYmxvY2suZ2ZtLnRhYmxlKQogIC5yZXBsYWNlKCJociIsIGJsb2NrLmhyKQogIC5yZXBsYWNlKCJoZWFkaW5nIiwgIiB7MCwzfSN7MSw2fSAiKQogIC5yZXBsYWNlKCJibG9ja3F1b3RlIiwgIiB7MCwzfT4iKQogIC5yZXBsYWNlKCJjb2RlIiwgIiB7NH1bXlxcbl0iKQogIC5yZXBsYWNlKCJmZW5jZXMiLCAiIHswLDN9KD86YHszLH0oPz1bXmBcXG5dKlxcbil8fnszLH0pW15cXG5dKlxcbiIpCiAgLnJlcGxhY2UoImxpc3QiLCAiIHswLDN9KD86WyorLV18MVsuKV0pICIpIC8vIG9ubHkgbGlzdHMgc3RhcnRpbmcgZnJvbSAxIGNhbiBpbnRlcnJ1cHQKICAucmVwbGFjZSgKICAgICJodG1sIiwKICAgICI8Lz8oPzp0YWcpKD86ICt8XFxufC8/Pil8PCg/OnNjcmlwdHxwcmV8c3R5bGV8dGV4dGFyZWF8IS0tKSIKICApCiAgLnJlcGxhY2UoInRhZyIsIGJsb2NrLl90YWcpIC8vIHRhYmxlcyBjYW4gYmUgaW50ZXJydXB0ZWQgYnkgdHlwZSAoNikgaHRtbCBibG9ja3MKICAuZ2V0UmVnZXgoKQoKYmxvY2suZ2ZtLnBhcmFncmFwaCA9IGVkaXQoYmxvY2suX3BhcmFncmFwaCkKICAucmVwbGFjZSgiaHIiLCBibG9jay5ocikKICAucmVwbGFjZSgiaGVhZGluZyIsICIgezAsM30jezEsNn0gIikKICAucmVwbGFjZSgifGxoZWFkaW5nIiwgIiIpIC8vIHNldGV4IGhlYWRpbmdzIGRvbid0IGludGVycnVwdCBjb21tb25tYXJrIHBhcmFncmFwaHMKICAucmVwbGFjZSgidGFibGUiLCBibG9jay5nZm0udGFibGUpIC8vIGludGVycnVwdCBwYXJhZ3JhcGhzIHdpdGggdGFibGUKICAucmVwbGFjZSgiYmxvY2txdW90ZSIsICIgezAsM30+IikKICAucmVwbGFjZSgiZmVuY2VzIiwgIiB7MCwzfSg/OmB7Myx9KD89W15gXFxuXSpcXG4pfH57Myx9KVteXFxuXSpcXG4iKQogIC5yZXBsYWNlKCJsaXN0IiwgIiB7MCwzfSg/OlsqKy1dfDFbLildKSAiKSAvLyBvbmx5IGxpc3RzIHN0YXJ0aW5nIGZyb20gMSBjYW4gaW50ZXJydXB0CiAgLnJlcGxhY2UoCiAgICAiaHRtbCIsCiAgICAiPC8/KD86dGFnKSg/OiArfFxcbnwvPz4pfDwoPzpzY3JpcHR8cHJlfHN0eWxlfHRleHRhcmVhfCEtLSkiCiAgKQogIC5yZXBsYWNlKCJ0YWciLCBibG9jay5fdGFnKSAvLyBwYXJzIGNhbiBiZSBpbnRlcnJ1cHRlZCBieSB0eXBlICg2KSBodG1sIGJsb2NrcwogIC5nZXRSZWdleCgpCi8qKgogKiBQZWRhbnRpYyBncmFtbWFyIChvcmlnaW5hbCBKb2huIEdydWJlcidzIGxvb3NlIG1hcmtkb3duIHNwZWNpZmljYXRpb24pCiAqLwoKYmxvY2sucGVkYW50aWMgPSBtZXJnZSh7fSwgYmxvY2subm9ybWFsLCB7CiAgaHRtbDogZWRpdCgKICAgICJeICooPzpjb21tZW50ICooPzpcXG58XFxzKiQpIiArCiAgICAgICJ8PCh0YWcpW1xcc1xcU10rPzwvXFwxPiAqKD86XFxuezIsfXxcXHMqJCkiICsgLy8gY2xvc2VkIHRhZwogICAgICAifDx0YWcoPzpcIlteXCJdKlwifCdbXiddKid8XFxzW14nXCIvPlxcc10qKSo/Lz8+ICooPzpcXG57Mix9fFxccyokKSkiCiAgKQogICAgLnJlcGxhY2UoImNvbW1lbnQiLCBibG9jay5fY29tbWVudCkKICAgIC5yZXBsYWNlKAogICAgICAvdGFnL2csCiAgICAgICIoPyEoPzoiICsKICAgICAgICAiYXxlbXxzdHJvbmd8c21hbGx8c3xjaXRlfHF8ZGZufGFiYnJ8ZGF0YXx0aW1lfGNvZGV8dmFyfHNhbXB8a2JkfHN1YiIgKwogICAgICAgICJ8c3VwfGl8Ynx1fG1hcmt8cnVieXxydHxycHxiZGl8YmRvfHNwYW58YnJ8d2JyfGluc3xkZWx8aW1nKSIgKwogICAgICAgICJcXGIpXFx3Kyg/ITp8W15cXHdcXHNAXSpAKVxcYiIKICAgICkKICAgIC5nZXRSZWdleCgpLAogIGRlZjogL14gKlxbKFteXF1dKylcXTogKjw/KFteXHM+XSspPj8oPzogKyhbIihdW15cbl0rWyIpXSkpPyAqKD86XG4rfCQpLywKICBoZWFkaW5nOiAvXigjezEsNn0pKC4qKSg/OlxuK3wkKS8sCiAgZmVuY2VzOiBub29wVGVzdCwgLy8gZmVuY2VzIG5vdCBzdXBwb3J0ZWQKICBwYXJhZ3JhcGg6IGVkaXQoYmxvY2subm9ybWFsLl9wYXJhZ3JhcGgpCiAgICAucmVwbGFjZSgiaHIiLCBibG9jay5ocikKICAgIC5yZXBsYWNlKCJoZWFkaW5nIiwgIiAqI3sxLDZ9ICpbXlxuXSIpCiAgICAucmVwbGFjZSgibGhlYWRpbmciLCBibG9jay5saGVhZGluZykKICAgIC5yZXBsYWNlKCJibG9ja3F1b3RlIiwgIiB7MCwzfT4iKQogICAgLnJlcGxhY2UoInxmZW5jZXMiLCAiIikKICAgIC5yZXBsYWNlKCJ8bGlzdCIsICIiKQogICAgLnJlcGxhY2UoInxodG1sIiwgIiIpCiAgICAuZ2V0UmVnZXgoKSwKfSkKCi8qKgogKiBJbmxpbmUtTGV2ZWwgR3JhbW1hcgogKi8KY29uc3QgaW5saW5lID0gewogIGVzY2FwZTogL15cXChbISIjJCUmJygpKissXC0uLzo7PD0+P0BcW1xdXFxeX2B7fH1+XSkvLAogIGF1dG9saW5rOiAvXjwoc2NoZW1lOlteXHNceDAwLVx4MWY8Pl0qfGVtYWlsKT4vLAogIHVybDogbm9vcFRlc3QsCiAgdGFnOgogICAgIl5jb21tZW50IiArCiAgICAifF48L1thLXpBLVpdW1xcdzotXSpcXHMqPiIgKyAvLyBzZWxmLWNsb3NpbmcgdGFnCiAgICAifF48W2EtekEtWl1bXFx3LV0qKD86YXR0cmlidXRlKSo/XFxzKi8/PiIgKyAvLyBvcGVuIHRhZwogICAgInxePFxcP1tcXHNcXFNdKj9cXD8+IiArIC8vIHByb2Nlc3NpbmcgaW5zdHJ1Y3Rpb24sIGUuZy4gPD9waHAgPz4KICAgICJ8XjwhW2EtekEtWl0rXFxzW1xcc1xcU10qPz4iICsgLy8gZGVjbGFyYXRpb24sIGUuZy4gPCFET0NUWVBFIGh0bWw+CiAgICAifF48IVxcW0NEQVRBXFxbW1xcc1xcU10qP1xcXVxcXT4iLCAvLyBDREFUQSBzZWN0aW9uCiAgbGluazogL14hP1xbKGxhYmVsKVxdXChccyooaHJlZikoPzpccysodGl0bGUpKT9ccypcKS8sCiAgcmVmbGluazogL14hP1xbKGxhYmVsKVxdXFsocmVmKVxdLywKICBub2xpbms6IC9eIT9cWyhyZWYpXF0oPzpcW1xdKT8vLAogIHJlZmxpbmtTZWFyY2g6ICJyZWZsaW5rfG5vbGluayg/IVxcKCkiLAogIGVtU3Ryb25nOiB7CiAgICBsRGVsaW06IC9eKD86XCorKD86KFtwdW5jdF9dKXxbXlxzKl0pKXxeXysoPzooW3B1bmN0Kl0pfChbXlxzX10pKS8sCiAgICAvLyAgICAgICAgKDEpIGFuZCAoMikgY2FuIG9ubHkgYmUgYSBSaWdodCBEZWxpbWl0ZXIuICgzKSBhbmQgKDQpIGNhbiBvbmx5IGJlIExlZnQuICAoNSkgYW5kICg2KSBjYW4gYmUgZWl0aGVyIExlZnQgb3IgUmlnaHQuCiAgICAvLyAgICAgICAgICAoKSBTa2lwIG9ycGhhbiBpbnNpZGUgc3Ryb25nICAoKSBDb25zdW1lIHRvIGRlbGltICgxKSAjKioqICAgICAgICAgICAgICAgICgyKSBhKioqIywgYSoqKiAgICAgICAgICAgICAgICAgICAoMykgIyoqKmEsICoqKmEgICAgICAgICAgICAgICAgICg0KSAqKiojICAgICAgICAgICAgICAoNSkgIyoqKiMgICAgICAgICAgICAgICAgICg2KSBhKioqYQogICAgckRlbGltQXN0OgogICAgICAvXlteXypdKj9cX1xfW15fKl0qP1wqW15fKl0qPyg/PVxfXF8pfFteKl0rKD89W14qXSl8W3B1bmN0X10oXCorKSg/PVtcc118JCl8W15wdW5jdCpfXHNdKFwqKykoPz1bcHVuY3RfXHNdfCQpfFtwdW5jdF9cc10oXCorKSg/PVtecHVuY3QqX1xzXSl8W1xzXShcKispKD89W3B1bmN0X10pfFtwdW5jdF9dKFwqKykoPz1bcHVuY3RfXSl8W15wdW5jdCpfXHNdKFwqKykoPz1bXnB1bmN0Kl9cc10pLywKICAgIHJEZWxpbVVuZDoKICAgICAgL15bXl8qXSo/XCpcKlteXypdKj9cX1teXypdKj8oPz1cKlwqKXxbXl9dKyg/PVteX10pfFtwdW5jdCpdKFxfKykoPz1bXHNdfCQpfFtecHVuY3QqX1xzXShcXyspKD89W3B1bmN0KlxzXXwkKXxbcHVuY3QqXHNdKFxfKykoPz1bXnB1bmN0Kl9cc10pfFtcc10oXF8rKSg/PVtwdW5jdCpdKXxbcHVuY3QqXShcXyspKD89W3B1bmN0Kl0pLywgLy8gXi0gTm90IGFsbG93ZWQgZm9yIF8KICB9LAogIGNvZGU6IC9eKGArKShbXmBdfFteYF1bXHNcU10qP1teYF0pXDEoPyFgKS8sCiAgYnI6IC9eKCB7Mix9fFxcKVxuKD8hXHMqJCkvLAogIGRlbDogbm9vcFRlc3QsCiAgdGV4dDogL14oYCt8W15gXSkoPzooPz0gezIsfVxuKXxbXHNcU10qPyg/Oig/PVtcXDwhXFtgKl9dfFxiX3wkKXxbXiBdKD89IHsyLH1cbikpKS8sCiAgcHVuY3R1YXRpb246IC9eKFtcc3B1bmN0dWF0aW9uXSkvLAp9CgovLyBsaXN0IG9mIHB1bmN0dWF0aW9uIG1hcmtzIGZyb20gQ29tbW9uTWFyayBzcGVjCi8vIHdpdGhvdXQgKiBhbmQgXyB0byBoYW5kbGUgdGhlIGRpZmZlcmVudCBlbXBoYXNpcyBtYXJrZXJzICogYW5kIF8KaW5saW5lLl9wdW5jdHVhdGlvbiA9ICIhXCIjJCUmJygpK1xcLS4sLzo7PD0+P0BcXFtcXF1gXnt8fX4iCmlubGluZS5wdW5jdHVhdGlvbiA9IGVkaXQoaW5saW5lLnB1bmN0dWF0aW9uKQogIC5yZXBsYWNlKC9wdW5jdHVhdGlvbi9nLCBpbmxpbmUuX3B1bmN0dWF0aW9uKQogIC5nZXRSZWdleCgpCgovLyBzZXF1ZW5jZXMgZW0gc2hvdWxkIHNraXAgb3ZlciBbdGl0bGVdKGxpbmspLCBgY29kZWAsIDxodG1sPgppbmxpbmUuYmxvY2tTa2lwID0gL1xbW15cXV0qP1xdXChbXlwpXSo/XCl8YFteYF0qP2B8PFtePl0qPz4vZwppbmxpbmUuZXNjYXBlZEVtU3QgPSAvXFxcKnxcXF8vZwoKaW5saW5lLl9jb21tZW50ID0gZWRpdChibG9jay5fY29tbWVudCkucmVwbGFjZSgiKD86LS0+fCQpIiwgIi0tPiIpLmdldFJlZ2V4KCkKCmlubGluZS5lbVN0cm9uZy5sRGVsaW0gPSBlZGl0KGlubGluZS5lbVN0cm9uZy5sRGVsaW0pCiAgLnJlcGxhY2UoL3B1bmN0L2csIGlubGluZS5fcHVuY3R1YXRpb24pCiAgLmdldFJlZ2V4KCkKCmlubGluZS5lbVN0cm9uZy5yRGVsaW1Bc3QgPSBlZGl0KGlubGluZS5lbVN0cm9uZy5yRGVsaW1Bc3QsICJnIikKICAucmVwbGFjZSgvcHVuY3QvZywgaW5saW5lLl9wdW5jdHVhdGlvbikKICAuZ2V0UmVnZXgoKQoKaW5saW5lLmVtU3Ryb25nLnJEZWxpbVVuZCA9IGVkaXQoaW5saW5lLmVtU3Ryb25nLnJEZWxpbVVuZCwgImciKQogIC5yZXBsYWNlKC9wdW5jdC9nLCBpbmxpbmUuX3B1bmN0dWF0aW9uKQogIC5nZXRSZWdleCgpCgppbmxpbmUuX2VzY2FwZXMgPSAvXFwoWyEiIyQlJicoKSorLFwtLi86Ozw9Pj9AXFtcXVxcXl9ge3x9fl0pL2cKCmlubGluZS5fc2NoZW1lID0gL1thLXpBLVpdW2EtekEtWjAtOSsuLV17MSwzMX0vCmlubGluZS5fZW1haWwgPQogIC9bYS16QS1aMC05LiEjJCUmJyorLz0/Xl9ge3x9fi1dKyhAKVthLXpBLVowLTldKD86W2EtekEtWjAtOS1dezAsNjF9W2EtekEtWjAtOV0pPyg/OlwuW2EtekEtWjAtOV0oPzpbYS16QS1aMC05LV17MCw2MX1bYS16QS1aMC05XSk/KSsoPyFbLV9dKS8KaW5saW5lLmF1dG9saW5rID0gZWRpdChpbmxpbmUuYXV0b2xpbmspCiAgLnJlcGxhY2UoInNjaGVtZSIsIGlubGluZS5fc2NoZW1lKQogIC5yZXBsYWNlKCJlbWFpbCIsIGlubGluZS5fZW1haWwpCiAgLmdldFJlZ2V4KCkKCmlubGluZS5fYXR0cmlidXRlID0KICAvXHMrW2EtekEtWjpfXVtcdy46LV0qKD86XHMqPVxzKiJbXiJdKiJ8XHMqPVxzKidbXiddKid8XHMqPVxzKlteXHMiJz08PmBdKyk/LwoKaW5saW5lLnRhZyA9IGVkaXQoaW5saW5lLnRhZykKICAucmVwbGFjZSgiY29tbWVudCIsIGlubGluZS5fY29tbWVudCkKICAucmVwbGFjZSgiYXR0cmlidXRlIiwgaW5saW5lLl9hdHRyaWJ1dGUpCiAgLmdldFJlZ2V4KCkKCmlubGluZS5fbGFiZWwgPSAvKD86XFsoPzpcXC58W15cW1xdXFxdKSpcXXxcXC58YFteYF0qYHxbXlxbXF1cXGBdKSo/LwppbmxpbmUuX2hyZWYgPSAvPCg/OlxcLnxbXlxuPD5cXF0pKz58W15cc1x4MDAtXHgxZl0qLwppbmxpbmUuX3RpdGxlID0gLyIoPzpcXCI/fFteIlxcXSkqInwnKD86XFwnP3xbXidcXF0pKid8XCgoPzpcXFwpP3xbXilcXF0pKlwpLwoKaW5saW5lLmxpbmsgPSBlZGl0KGlubGluZS5saW5rKQogIC5yZXBsYWNlKCJsYWJlbCIsIGlubGluZS5fbGFiZWwpCiAgLnJlcGxhY2UoImhyZWYiLCBpbmxpbmUuX2hyZWYpCiAgLnJlcGxhY2UoInRpdGxlIiwgaW5saW5lLl90aXRsZSkKICAuZ2V0UmVnZXgoKQoKaW5saW5lLnJlZmxpbmsgPSBlZGl0KGlubGluZS5yZWZsaW5rKQogIC5yZXBsYWNlKCJsYWJlbCIsIGlubGluZS5fbGFiZWwpCiAgLnJlcGxhY2UoInJlZiIsIGJsb2NrLl9sYWJlbCkKICAuZ2V0UmVnZXgoKQoKaW5saW5lLm5vbGluayA9IGVkaXQoaW5saW5lLm5vbGluaykucmVwbGFjZSgicmVmIiwgYmxvY2suX2xhYmVsKS5nZXRSZWdleCgpCgppbmxpbmUucmVmbGlua1NlYXJjaCA9IGVkaXQoaW5saW5lLnJlZmxpbmtTZWFyY2gsICJnIikKICAucmVwbGFjZSgicmVmbGluayIsIGlubGluZS5yZWZsaW5rKQogIC5yZXBsYWNlKCJub2xpbmsiLCBpbmxpbmUubm9saW5rKQogIC5nZXRSZWdleCgpCgovKioKICogTm9ybWFsIElubGluZSBHcmFtbWFyCiAqLwoKaW5saW5lLm5vcm1hbCA9IG1lcmdlKHt9LCBpbmxpbmUpCgovKioKICogUGVkYW50aWMgSW5saW5lIEdyYW1tYXIKICovCgppbmxpbmUucGVkYW50aWMgPSBtZXJnZSh7fSwgaW5saW5lLm5vcm1hbCwgewogIHN0cm9uZzogewogICAgc3RhcnQ6IC9eX198XCpcKi8sCiAgICBtaWRkbGU6IC9eX18oPz1cUykoW1xzXFNdKj9cUylfXyg/IV8pfF5cKlwqKD89XFMpKFtcc1xTXSo/XFMpXCpcKig/IVwqKS8sCiAgICBlbmRBc3Q6IC9cKlwqKD8hXCopL2csCiAgICBlbmRVbmQ6IC9fXyg/IV8pL2csCiAgfSwKICBlbTogewogICAgc3RhcnQ6IC9eX3xcKi8sCiAgICBtaWRkbGU6IC9eKClcKig/PVxTKShbXHNcU10qP1xTKVwqKD8hXCopfF5fKD89XFMpKFtcc1xTXSo/XFMpXyg/IV8pLywKICAgIGVuZEFzdDogL1wqKD8hXCopL2csCiAgICBlbmRVbmQ6IC9fKD8hXykvZywKICB9LAogIGxpbms6IGVkaXQoL14hP1xbKGxhYmVsKVxdXCgoLio/KVwpLykKICAgIC5yZXBsYWNlKCJsYWJlbCIsIGlubGluZS5fbGFiZWwpCiAgICAuZ2V0UmVnZXgoKSwKICByZWZsaW5rOiBlZGl0KC9eIT9cWyhsYWJlbClcXVxzKlxbKFteXF1dKilcXS8pCiAgICAucmVwbGFjZSgibGFiZWwiLCBpbmxpbmUuX2xhYmVsKQogICAgLmdldFJlZ2V4KCksCn0pCgovKioKICogR0ZNIElubGluZSBHcmFtbWFyCiAqLwoKaW5saW5lLmdmbSA9IG1lcmdlKHt9LCBpbmxpbmUubm9ybWFsLCB7CiAgZXNjYXBlOiBlZGl0KGlubGluZS5lc2NhcGUpLnJlcGxhY2UoIl0pIiwgIn58XSkiKS5nZXRSZWdleCgpLAogIF9leHRlbmRlZF9lbWFpbDoKICAgIC9bQS1aYS16MC05Ll8rLV0rKEApW2EtekEtWjAtOS1fXSsoPzpcLlthLXpBLVowLTktX10qW2EtekEtWjAtOV0pKyg/IVstX10pLywKICB1cmw6IC9eKCg/OmZ0cHxodHRwcz8pOlwvXC98d3d3XC4pKD86W2EtekEtWjAtOVwtXStcLj8pK1teXHM8XSp8XmVtYWlsLywKICBfYmFja3BlZGFsOgogICAgLyg/OltePyEuLDo7Kl9+KCkmXSt8XChbXildKlwpfCYoPyFbYS16QS1aMC05XSs7JCl8Wz8hLiw6OypffildKyg/ISQpKSsvLAogIGRlbDogL14ofn4/KSg/PVteXHN+XSkoW1xzXFNdKj9bXlxzfl0pXDEoPz1bXn5dfCQpLywKICB0ZXh0OiAvXihbYH5dK3xbXmB+XSkoPzooPz0gezIsfVxuKXwoPz1bYS16QS1aMC05LiEjJCUmJyorXC89P19ge1x8fX4tXStAKXxbXHNcU10qPyg/Oig/PVtcXDwhXFtgKn5fXXxcYl98aHR0cHM/OlwvXC98ZnRwOlwvXC98d3d3XC58JCl8W14gXSg/PSB7Mix9XG4pfFteYS16QS1aMC05LiEjJCUmJyorXC89P19ge1x8fX4tXSg/PVthLXpBLVowLTkuISMkJSYnKitcLz0/X2B7XHx9fi1dK0ApKSkvLAp9KQoKaW5saW5lLmdmbS51cmwgPSBlZGl0KGlubGluZS5nZm0udXJsLCAiaSIpCiAgLnJlcGxhY2UoImVtYWlsIiwgaW5saW5lLmdmbS5fZXh0ZW5kZWRfZW1haWwpCiAgLmdldFJlZ2V4KCkKLyoqCiAqIEdGTSArIExpbmUgQnJlYWtzIElubGluZSBHcmFtbWFyCiAqLwoKaW5saW5lLmJyZWFrcyA9IG1lcmdlKHt9LCBpbmxpbmUuZ2ZtLCB7CiAgYnI6IGVkaXQoaW5saW5lLmJyKS5yZXBsYWNlKCJ7Mix9IiwgIioiKS5nZXRSZWdleCgpLAogIHRleHQ6IGVkaXQoaW5saW5lLmdmbS50ZXh0KQogICAgLnJlcGxhY2UoIlxcYl8iLCAiXFxiX3wgezIsfVxcbiIpCiAgICAucmVwbGFjZSgvXHsyLFx9L2csICIqIikKICAgIC5nZXRSZWdleCgpLAp9KQoKLyoqCiAqIHNtYXJ0eXBhbnRzIHRleHQgcmVwbGFjZW1lbnQKICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICovCmZ1bmN0aW9uIHNtYXJ0eXBhbnRzKHRleHQpIHsKICByZXR1cm4gKAogICAgdGV4dAogICAgICAvLyBlbS1kYXNoZXMKICAgICAgLnJlcGxhY2UoLy0tLS9nLCAiXHUyMDE0IikKICAgICAgLy8gZW4tZGFzaGVzCiAgICAgIC5yZXBsYWNlKC8tLS9nLCAiXHUyMDEzIikKICAgICAgLy8gb3BlbmluZyBzaW5nbGVzCiAgICAgIC5yZXBsYWNlKC8oXnxbLVx1MjAxNC8oXFt7IlxzXSknL2csICIkMVx1MjAxOCIpCiAgICAgIC8vIGNsb3Npbmcgc2luZ2xlcyAmIGFwb3N0cm9waGVzCiAgICAgIC5yZXBsYWNlKC8nL2csICJcdTIwMTkiKQogICAgICAvLyBvcGVuaW5nIGRvdWJsZXMKICAgICAgLnJlcGxhY2UoLyhefFstXHUyMDE0LyhcW3tcdTIwMThcc10pIi9nLCAiJDFcdTIwMWMiKQogICAgICAvLyBjbG9zaW5nIGRvdWJsZXMKICAgICAgLnJlcGxhY2UoLyIvZywgIlx1MjAxZCIpCiAgICAgIC8vIGVsbGlwc2VzCiAgICAgIC5yZXBsYWNlKC9cLnszfS9nLCAiXHUyMDI2IikKICApCn0KCi8qKgogKiBtYW5nbGUgZW1haWwgYWRkcmVzc2VzCiAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAqLwpmdW5jdGlvbiBtYW5nbGUodGV4dCkgewogIGxldCBvdXQgPSAiIiwKICAgIGksCiAgICBjaAoKICBjb25zdCBsID0gdGV4dC5sZW5ndGgKICBmb3IgKGkgPSAwOyBpIDwgbDsgaSsrKSB7CiAgICBjaCA9IHRleHQuY2hhckNvZGVBdChpKQogICAgaWYgKE1hdGgucmFuZG9tKCkgPiAwLjUpIHsKICAgICAgY2ggPSAieCIgKyBjaC50b1N0cmluZygxNikKICAgIH0KICAgIG91dCArPSAiJiMiICsgY2ggKyAiOyIKICB9CgogIHJldHVybiBvdXQKfQoKLyoqCiAqIEJsb2NrIExleGVyCiAqLwpjbGFzcyBMZXhlciB7CiAgY29uc3RydWN0b3Iob3B0aW9ucykgewogICAgdGhpcy50b2tlbnMgPSBbXQogICAgdGhpcy50b2tlbnMubGlua3MgPSBPYmplY3QuY3JlYXRlKG51bGwpCiAgICB0aGlzLm9wdGlvbnMgPSBvcHRpb25zIHx8IGRlZmF1bHRzCiAgICB0aGlzLm9wdGlvbnMudG9rZW5pemVyID0gdGhpcy5vcHRpb25zLnRva2VuaXplciB8fCBuZXcgVG9rZW5pemVyKCkKICAgIHRoaXMudG9rZW5pemVyID0gdGhpcy5vcHRpb25zLnRva2VuaXplcgogICAgdGhpcy50b2tlbml6ZXIub3B0aW9ucyA9IHRoaXMub3B0aW9ucwogICAgdGhpcy50b2tlbml6ZXIubGV4ZXIgPSB0aGlzCiAgICB0aGlzLmlubGluZVF1ZXVlID0gW10KICAgIHRoaXMuc3RhdGUgPSB7CiAgICAgIGluTGluazogZmFsc2UsCiAgICAgIGluUmF3QmxvY2s6IGZhbHNlLAogICAgICB0b3A6IHRydWUsCiAgICB9CgogICAgY29uc3QgcnVsZXMgPSB7CiAgICAgIGJsb2NrOiBibG9jay5ub3JtYWwsCiAgICAgIGlubGluZTogaW5saW5lLm5vcm1hbCwKICAgIH0KCiAgICBpZiAodGhpcy5vcHRpb25zLnBlZGFudGljKSB7CiAgICAgIHJ1bGVzLmJsb2NrID0gYmxvY2sucGVkYW50aWMKICAgICAgcnVsZXMuaW5saW5lID0gaW5saW5lLnBlZGFudGljCiAgICB9IGVsc2UgaWYgKHRoaXMub3B0aW9ucy5nZm0pIHsKICAgICAgcnVsZXMuYmxvY2sgPSBibG9jay5nZm0KICAgICAgaWYgKHRoaXMub3B0aW9ucy5icmVha3MpIHsKICAgICAgICBydWxlcy5pbmxpbmUgPSBpbmxpbmUuYnJlYWtzCiAgICAgIH0gZWxzZSB7CiAgICAgICAgcnVsZXMuaW5saW5lID0gaW5saW5lLmdmbQogICAgICB9CiAgICB9CiAgICB0aGlzLnRva2VuaXplci5ydWxlcyA9IHJ1bGVzCiAgfQoKICAvKioKICAgKiBFeHBvc2UgUnVsZXMKICAgKi8KICBzdGF0aWMgZ2V0IHJ1bGVzKCkgewogICAgcmV0dXJuIHsKICAgICAgYmxvY2ssCiAgICAgIGlubGluZSwKICAgIH0KICB9CgogIC8qKgogICAqIFN0YXRpYyBMZXggTWV0aG9kCiAgICovCiAgc3RhdGljIGxleChzcmMsIG9wdGlvbnMpIHsKICAgIGNvbnN0IGxleGVyID0gbmV3IExleGVyKG9wdGlvbnMpCiAgICByZXR1cm4gbGV4ZXIubGV4KHNyYykKICB9CgogIC8qKgogICAqIFN0YXRpYyBMZXggSW5saW5lIE1ldGhvZAogICAqLwogIHN0YXRpYyBsZXhJbmxpbmUoc3JjLCBvcHRpb25zKSB7CiAgICBjb25zdCBsZXhlciA9IG5ldyBMZXhlcihvcHRpb25zKQogICAgcmV0dXJuIGxleGVyLmlubGluZVRva2VucyhzcmMpCiAgfQoKICAvKioKICAgKiBQcmVwcm9jZXNzaW5nCiAgICovCiAgbGV4KHNyYykgewogICAgc3JjID0gc3JjLnJlcGxhY2UoL1xyXG58XHIvZywgIlxuIikKCiAgICB0aGlzLmJsb2NrVG9rZW5zKHNyYywgdGhpcy50b2tlbnMpCgogICAgbGV0IG5leHQKICAgIHdoaWxlICgobmV4dCA9IHRoaXMuaW5saW5lUXVldWUuc2hpZnQoKSkpIHsKICAgICAgdGhpcy5pbmxpbmVUb2tlbnMobmV4dC5zcmMsIG5leHQudG9rZW5zKQogICAgfQoKICAgIHJldHVybiB0aGlzLnRva2VucwogIH0KCiAgLyoqCiAgICogTGV4aW5nCiAgICovCiAgYmxvY2tUb2tlbnMoc3JjLCB0b2tlbnMgPSBbXSkgewogICAgaWYgKHRoaXMub3B0aW9ucy5wZWRhbnRpYykgewogICAgICBzcmMgPSBzcmMucmVwbGFjZSgvXHQvZywgIiAgICAiKS5yZXBsYWNlKC9eICskL2dtLCAiIikKICAgIH0gZWxzZSB7CiAgICAgIHNyYyA9IHNyYy5yZXBsYWNlKC9eKCAqKShcdCspL2dtLCAoXywgbGVhZGluZywgdGFicykgPT4gewogICAgICAgIHJldHVybiBsZWFkaW5nICsgIiAgICAiLnJlcGVhdCh0YWJzLmxlbmd0aCkKICAgICAgfSkKICAgIH0KCiAgICBsZXQgdG9rZW4sIGxhc3RUb2tlbiwgY3V0U3JjLCBsYXN0UGFyYWdyYXBoQ2xpcHBlZAoKICAgIHdoaWxlIChzcmMpIHsKICAgICAgaWYgKAogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zICYmCiAgICAgICAgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMuYmxvY2sgJiYKICAgICAgICB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucy5ibG9jay5zb21lKGV4dFRva2VuaXplciA9PiB7CiAgICAgICAgICBpZiAoKHRva2VuID0gZXh0VG9rZW5pemVyLmNhbGwoeyBsZXhlcjogdGhpcyB9LCBzcmMsIHRva2VucykpKSB7CiAgICAgICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgICAgIHJldHVybiB0cnVlCiAgICAgICAgICB9CiAgICAgICAgICByZXR1cm4gZmFsc2UKICAgICAgICB9KQogICAgICApIHsKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBuZXdsaW5lCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5zcGFjZShzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICBpZiAodG9rZW4ucmF3Lmxlbmd0aCA9PT0gMSAmJiB0b2tlbnMubGVuZ3RoID4gMCkgewogICAgICAgICAgLy8gaWYgdGhlcmUncyBhIHNpbmdsZSBcbiBhcyBhIHNwYWNlciwgaXQncyB0ZXJtaW5hdGluZyB0aGUgbGFzdCBsaW5lLAogICAgICAgICAgLy8gc28gbW92ZSBpdCB0aGVyZSBzbyB0aGF0IHdlIGRvbid0IGdldCB1bmVjZXNzYXJ5IHBhcmFncmFwaCB0YWdzCiAgICAgICAgICB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdLnJhdyArPSAiXG4iCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIH0KICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBjb2RlCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5jb2RlKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIGxhc3RUb2tlbiA9IHRva2Vuc1t0b2tlbnMubGVuZ3RoIC0gMV0KICAgICAgICAvLyBBbiBpbmRlbnRlZCBjb2RlIGJsb2NrIGNhbm5vdCBpbnRlcnJ1cHQgYSBwYXJhZ3JhcGguCiAgICAgICAgaWYgKAogICAgICAgICAgbGFzdFRva2VuICYmCiAgICAgICAgICAobGFzdFRva2VuLnR5cGUgPT09ICJwYXJhZ3JhcGgiIHx8IGxhc3RUb2tlbi50eXBlID09PSAidGV4dCIpCiAgICAgICAgKSB7CiAgICAgICAgICBsYXN0VG9rZW4ucmF3ICs9ICJcbiIgKyB0b2tlbi5yYXcKICAgICAgICAgIGxhc3RUb2tlbi50ZXh0ICs9ICJcbiIgKyB0b2tlbi50ZXh0CiAgICAgICAgICB0aGlzLmlubGluZVF1ZXVlW3RoaXMuaW5saW5lUXVldWUubGVuZ3RoIC0gMV0uc3JjID0gbGFzdFRva2VuLnRleHQKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgfQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGZlbmNlcwogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIuZmVuY2VzKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGhlYWRpbmcKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmhlYWRpbmcoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gaHIKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmhyKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGJsb2NrcXVvdGUKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmJsb2NrcXVvdGUoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gbGlzdAogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIubGlzdChzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICB0b2tlbnMucHVzaCh0b2tlbikKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBodG1sCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5odG1sKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGRlZgogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIuZGVmKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIGxhc3RUb2tlbiA9IHRva2Vuc1t0b2tlbnMubGVuZ3RoIC0gMV0KICAgICAgICBpZiAoCiAgICAgICAgICBsYXN0VG9rZW4gJiYKICAgICAgICAgIChsYXN0VG9rZW4udHlwZSA9PT0gInBhcmFncmFwaCIgfHwgbGFzdFRva2VuLnR5cGUgPT09ICJ0ZXh0IikKICAgICAgICApIHsKICAgICAgICAgIGxhc3RUb2tlbi5yYXcgKz0gIlxuIiArIHRva2VuLnJhdwogICAgICAgICAgbGFzdFRva2VuLnRleHQgKz0gIlxuIiArIHRva2VuLnJhdwogICAgICAgICAgdGhpcy5pbmxpbmVRdWV1ZVt0aGlzLmlubGluZVF1ZXVlLmxlbmd0aCAtIDFdLnNyYyA9IGxhc3RUb2tlbi50ZXh0CiAgICAgICAgfSBlbHNlIGlmICghdGhpcy50b2tlbnMubGlua3NbdG9rZW4udGFnXSkgewogICAgICAgICAgdGhpcy50b2tlbnMubGlua3NbdG9rZW4udGFnXSA9IHsKICAgICAgICAgICAgaHJlZjogdG9rZW4uaHJlZiwKICAgICAgICAgICAgdGl0bGU6IHRva2VuLnRpdGxlLAogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyB0YWJsZSAoZ2ZtKQogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIudGFibGUoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gbGhlYWRpbmcKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmxoZWFkaW5nKHNyYykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIHRvcC1sZXZlbCBwYXJhZ3JhcGgKICAgICAgLy8gcHJldmVudCBwYXJhZ3JhcGggY29uc3VtaW5nIGV4dGVuc2lvbnMgYnkgY2xpcHBpbmcgJ3NyYycgdG8gZXh0ZW5zaW9uIHN0YXJ0CiAgICAgIGN1dFNyYyA9IHNyYwogICAgICBpZiAodGhpcy5vcHRpb25zLmV4dGVuc2lvbnMgJiYgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMuc3RhcnRCbG9jaykgewogICAgICAgIGxldCBzdGFydEluZGV4ID0gSW5maW5pdHkKICAgICAgICBjb25zdCB0ZW1wU3JjID0gc3JjLnNsaWNlKDEpCiAgICAgICAgbGV0IHRlbXBTdGFydAogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLnN0YXJ0QmxvY2suZm9yRWFjaChmdW5jdGlvbiAoZ2V0U3RhcnRJbmRleCkgewogICAgICAgICAgdGVtcFN0YXJ0ID0gZ2V0U3RhcnRJbmRleC5jYWxsKHsgbGV4ZXI6IHRoaXMgfSwgdGVtcFNyYykKICAgICAgICAgIGlmICh0eXBlb2YgdGVtcFN0YXJ0ID09PSAibnVtYmVyIiAmJiB0ZW1wU3RhcnQgPj0gMCkgewogICAgICAgICAgICBzdGFydEluZGV4ID0gTWF0aC5taW4oc3RhcnRJbmRleCwgdGVtcFN0YXJ0KQogICAgICAgICAgfQogICAgICAgIH0pCiAgICAgICAgaWYgKHN0YXJ0SW5kZXggPCBJbmZpbml0eSAmJiBzdGFydEluZGV4ID49IDApIHsKICAgICAgICAgIGN1dFNyYyA9IHNyYy5zdWJzdHJpbmcoMCwgc3RhcnRJbmRleCArIDEpCiAgICAgICAgfQogICAgICB9CiAgICAgIGlmICh0aGlzLnN0YXRlLnRvcCAmJiAodG9rZW4gPSB0aGlzLnRva2VuaXplci5wYXJhZ3JhcGgoY3V0U3JjKSkpIHsKICAgICAgICBsYXN0VG9rZW4gPSB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdCiAgICAgICAgaWYgKGxhc3RQYXJhZ3JhcGhDbGlwcGVkICYmIGxhc3RUb2tlbi50eXBlID09PSAicGFyYWdyYXBoIikgewogICAgICAgICAgbGFzdFRva2VuLnJhdyArPSAiXG4iICsgdG9rZW4ucmF3CiAgICAgICAgICBsYXN0VG9rZW4udGV4dCArPSAiXG4iICsgdG9rZW4udGV4dAogICAgICAgICAgdGhpcy5pbmxpbmVRdWV1ZS5wb3AoKQogICAgICAgICAgdGhpcy5pbmxpbmVRdWV1ZVt0aGlzLmlubGluZVF1ZXVlLmxlbmd0aCAtIDFdLnNyYyA9IGxhc3RUb2tlbi50ZXh0CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIH0KICAgICAgICBsYXN0UGFyYWdyYXBoQ2xpcHBlZCA9IGN1dFNyYy5sZW5ndGggIT09IHNyYy5sZW5ndGgKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gdGV4dAogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIudGV4dChzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICBsYXN0VG9rZW4gPSB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdCiAgICAgICAgaWYgKGxhc3RUb2tlbiAmJiBsYXN0VG9rZW4udHlwZSA9PT0gInRleHQiKSB7CiAgICAgICAgICBsYXN0VG9rZW4ucmF3ICs9ICJcbiIgKyB0b2tlbi5yYXcKICAgICAgICAgIGxhc3RUb2tlbi50ZXh0ICs9ICJcbiIgKyB0b2tlbi50ZXh0CiAgICAgICAgICB0aGlzLmlubGluZVF1ZXVlLnBvcCgpCiAgICAgICAgICB0aGlzLmlubGluZVF1ZXVlW3RoaXMuaW5saW5lUXVldWUubGVuZ3RoIC0gMV0uc3JjID0gbGFzdFRva2VuLnRleHQKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgfQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIGlmIChzcmMpIHsKICAgICAgICBjb25zdCBlcnJNc2cgPSAiSW5maW5pdGUgbG9vcCBvbiBieXRlOiAiICsgc3JjLmNoYXJDb2RlQXQoMCkKICAgICAgICBpZiAodGhpcy5vcHRpb25zLnNpbGVudCkgewogICAgICAgICAgY29uc29sZS5lcnJvcihlcnJNc2cpCiAgICAgICAgICBicmVhawogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZXJyTXNnKQogICAgICAgIH0KICAgICAgfQogICAgfQoKICAgIHRoaXMuc3RhdGUudG9wID0gdHJ1ZQogICAgcmV0dXJuIHRva2VucwogIH0KCiAgaW5saW5lKHNyYywgdG9rZW5zKSB7CiAgICB0aGlzLmlubGluZVF1ZXVlLnB1c2goeyBzcmMsIHRva2VucyB9KQogIH0KCiAgLyoqCiAgICogTGV4aW5nL0NvbXBpbGluZwogICAqLwogIGlubGluZVRva2VucyhzcmMsIHRva2VucyA9IFtdKSB7CiAgICBsZXQgdG9rZW4sIGxhc3RUb2tlbiwgY3V0U3JjCgogICAgLy8gU3RyaW5nIHdpdGggbGlua3MgbWFza2VkIHRvIGF2b2lkIGludGVyZmVyZW5jZSB3aXRoIGVtIGFuZCBzdHJvbmcKICAgIGxldCBtYXNrZWRTcmMgPSBzcmMKICAgIGxldCBtYXRjaAogICAgbGV0IGtlZXBQcmV2Q2hhciwgcHJldkNoYXIKCiAgICAvLyBNYXNrIG91dCByZWZsaW5rcwogICAgaWYgKHRoaXMudG9rZW5zLmxpbmtzKSB7CiAgICAgIGNvbnN0IGxpbmtzID0gT2JqZWN0LmtleXModGhpcy50b2tlbnMubGlua3MpCiAgICAgIGlmIChsaW5rcy5sZW5ndGggPiAwKSB7CiAgICAgICAgd2hpbGUgKAogICAgICAgICAgKG1hdGNoID0gdGhpcy50b2tlbml6ZXIucnVsZXMuaW5saW5lLnJlZmxpbmtTZWFyY2guZXhlYyhtYXNrZWRTcmMpKSAhPQogICAgICAgICAgbnVsbAogICAgICAgICkgewogICAgICAgICAgaWYgKAogICAgICAgICAgICBsaW5rcy5pbmNsdWRlcyhtYXRjaFswXS5zbGljZShtYXRjaFswXS5sYXN0SW5kZXhPZigiWyIpICsgMSwgLTEpKQogICAgICAgICAgKSB7CiAgICAgICAgICAgIG1hc2tlZFNyYyA9CiAgICAgICAgICAgICAgbWFza2VkU3JjLnNsaWNlKDAsIG1hdGNoLmluZGV4KSArCiAgICAgICAgICAgICAgIlsiICsKICAgICAgICAgICAgICByZXBlYXRTdHJpbmcoImEiLCBtYXRjaFswXS5sZW5ndGggLSAyKSArCiAgICAgICAgICAgICAgIl0iICsKICAgICAgICAgICAgICBtYXNrZWRTcmMuc2xpY2UoCiAgICAgICAgICAgICAgICB0aGlzLnRva2VuaXplci5ydWxlcy5pbmxpbmUucmVmbGlua1NlYXJjaC5sYXN0SW5kZXgKICAgICAgICAgICAgICApCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9CiAgICAvLyBNYXNrIG91dCBvdGhlciBibG9ja3MKICAgIHdoaWxlICgKICAgICAgKG1hdGNoID0gdGhpcy50b2tlbml6ZXIucnVsZXMuaW5saW5lLmJsb2NrU2tpcC5leGVjKG1hc2tlZFNyYykpICE9IG51bGwKICAgICkgewogICAgICBtYXNrZWRTcmMgPQogICAgICAgIG1hc2tlZFNyYy5zbGljZSgwLCBtYXRjaC5pbmRleCkgKwogICAgICAgICJbIiArCiAgICAgICAgcmVwZWF0U3RyaW5nKCJhIiwgbWF0Y2hbMF0ubGVuZ3RoIC0gMikgKwogICAgICAgICJdIiArCiAgICAgICAgbWFza2VkU3JjLnNsaWNlKHRoaXMudG9rZW5pemVyLnJ1bGVzLmlubGluZS5ibG9ja1NraXAubGFzdEluZGV4KQogICAgfQoKICAgIC8vIE1hc2sgb3V0IGVzY2FwZWQgZW0gJiBzdHJvbmcgZGVsaW1pdGVycwogICAgd2hpbGUgKAogICAgICAobWF0Y2ggPSB0aGlzLnRva2VuaXplci5ydWxlcy5pbmxpbmUuZXNjYXBlZEVtU3QuZXhlYyhtYXNrZWRTcmMpKSAhPSBudWxsCiAgICApIHsKICAgICAgbWFza2VkU3JjID0KICAgICAgICBtYXNrZWRTcmMuc2xpY2UoMCwgbWF0Y2guaW5kZXgpICsKICAgICAgICAiKysiICsKICAgICAgICBtYXNrZWRTcmMuc2xpY2UodGhpcy50b2tlbml6ZXIucnVsZXMuaW5saW5lLmVzY2FwZWRFbVN0Lmxhc3RJbmRleCkKICAgIH0KCiAgICB3aGlsZSAoc3JjKSB7CiAgICAgIGlmICgha2VlcFByZXZDaGFyKSB7CiAgICAgICAgcHJldkNoYXIgPSAiIgogICAgICB9CiAgICAgIGtlZXBQcmV2Q2hhciA9IGZhbHNlCgogICAgICAvLyBleHRlbnNpb25zCiAgICAgIGlmICgKICAgICAgICB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucyAmJgogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLmlubGluZSAmJgogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLmlubGluZS5zb21lKGV4dFRva2VuaXplciA9PiB7CiAgICAgICAgICBpZiAoKHRva2VuID0gZXh0VG9rZW5pemVyLmNhbGwoeyBsZXhlcjogdGhpcyB9LCBzcmMsIHRva2VucykpKSB7CiAgICAgICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgICAgIHJldHVybiB0cnVlCiAgICAgICAgICB9CiAgICAgICAgICByZXR1cm4gZmFsc2UKICAgICAgICB9KQogICAgICApIHsKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBlc2NhcGUKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmVzY2FwZShzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICB0b2tlbnMucHVzaCh0b2tlbikKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyB0YWcKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLnRhZyhzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICBsYXN0VG9rZW4gPSB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdCiAgICAgICAgaWYgKGxhc3RUb2tlbiAmJiB0b2tlbi50eXBlID09PSAidGV4dCIgJiYgbGFzdFRva2VuLnR5cGUgPT09ICJ0ZXh0IikgewogICAgICAgICAgbGFzdFRva2VuLnJhdyArPSB0b2tlbi5yYXcKICAgICAgICAgIGxhc3RUb2tlbi50ZXh0ICs9IHRva2VuLnRleHQKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgfQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGxpbmsKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmxpbmsoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gcmVmbGluaywgbm9saW5rCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5yZWZsaW5rKHNyYywgdGhpcy50b2tlbnMubGlua3MpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICBsYXN0VG9rZW4gPSB0b2tlbnNbdG9rZW5zLmxlbmd0aCAtIDFdCiAgICAgICAgaWYgKGxhc3RUb2tlbiAmJiB0b2tlbi50eXBlID09PSAidGV4dCIgJiYgbGFzdFRva2VuLnR5cGUgPT09ICJ0ZXh0IikgewogICAgICAgICAgbGFzdFRva2VuLnJhdyArPSB0b2tlbi5yYXcKICAgICAgICAgIGxhc3RUb2tlbi50ZXh0ICs9IHRva2VuLnRleHQKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgfQogICAgICAgIGNvbnRpbnVlCiAgICAgIH0KCiAgICAgIC8vIGVtICYgc3Ryb25nCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5lbVN0cm9uZyhzcmMsIG1hc2tlZFNyYywgcHJldkNoYXIpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICB0b2tlbnMucHVzaCh0b2tlbikKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBjb2RlCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5jb2Rlc3BhbihzcmMpKSkgewogICAgICAgIHNyYyA9IHNyYy5zdWJzdHJpbmcodG9rZW4ucmF3Lmxlbmd0aCkKICAgICAgICB0b2tlbnMucHVzaCh0b2tlbikKICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICAvLyBicgogICAgICBpZiAoKHRva2VuID0gdGhpcy50b2tlbml6ZXIuYnIoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gZGVsIChnZm0pCiAgICAgIGlmICgodG9rZW4gPSB0aGlzLnRva2VuaXplci5kZWwoc3JjKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gYXV0b2xpbmsKICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmF1dG9saW5rKHNyYywgbWFuZ2xlKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gdXJsIChnZm0pCiAgICAgIGlmICghdGhpcy5zdGF0ZS5pbkxpbmsgJiYgKHRva2VuID0gdGhpcy50b2tlbml6ZXIudXJsKHNyYywgbWFuZ2xlKSkpIHsKICAgICAgICBzcmMgPSBzcmMuc3Vic3RyaW5nKHRva2VuLnJhdy5sZW5ndGgpCiAgICAgICAgdG9rZW5zLnB1c2godG9rZW4pCiAgICAgICAgY29udGludWUKICAgICAgfQoKICAgICAgLy8gdGV4dAogICAgICAvLyBwcmV2ZW50IGlubGluZVRleHQgY29uc3VtaW5nIGV4dGVuc2lvbnMgYnkgY2xpcHBpbmcgJ3NyYycgdG8gZXh0ZW5zaW9uIHN0YXJ0CiAgICAgIGN1dFNyYyA9IHNyYwogICAgICBpZiAodGhpcy5vcHRpb25zLmV4dGVuc2lvbnMgJiYgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMuc3RhcnRJbmxpbmUpIHsKICAgICAgICBsZXQgc3RhcnRJbmRleCA9IEluZmluaXR5CiAgICAgICAgY29uc3QgdGVtcFNyYyA9IHNyYy5zbGljZSgxKQogICAgICAgIGxldCB0ZW1wU3RhcnQKICAgICAgICB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucy5zdGFydElubGluZS5mb3JFYWNoKGZ1bmN0aW9uIChnZXRTdGFydEluZGV4KSB7CiAgICAgICAgICB0ZW1wU3RhcnQgPSBnZXRTdGFydEluZGV4LmNhbGwoeyBsZXhlcjogdGhpcyB9LCB0ZW1wU3JjKQogICAgICAgICAgaWYgKHR5cGVvZiB0ZW1wU3RhcnQgPT09ICJudW1iZXIiICYmIHRlbXBTdGFydCA+PSAwKSB7CiAgICAgICAgICAgIHN0YXJ0SW5kZXggPSBNYXRoLm1pbihzdGFydEluZGV4LCB0ZW1wU3RhcnQpCiAgICAgICAgICB9CiAgICAgICAgfSkKICAgICAgICBpZiAoc3RhcnRJbmRleCA8IEluZmluaXR5ICYmIHN0YXJ0SW5kZXggPj0gMCkgewogICAgICAgICAgY3V0U3JjID0gc3JjLnN1YnN0cmluZygwLCBzdGFydEluZGV4ICsgMSkKICAgICAgICB9CiAgICAgIH0KICAgICAgaWYgKCh0b2tlbiA9IHRoaXMudG9rZW5pemVyLmlubGluZVRleHQoY3V0U3JjLCBzbWFydHlwYW50cykpKSB7CiAgICAgICAgc3JjID0gc3JjLnN1YnN0cmluZyh0b2tlbi5yYXcubGVuZ3RoKQogICAgICAgIGlmICh0b2tlbi5yYXcuc2xpY2UoLTEpICE9PSAiXyIpIHsKICAgICAgICAgIC8vIFRyYWNrIHByZXZDaGFyIGJlZm9yZSBzdHJpbmcgb2YgX19fXyBzdGFydGVkCiAgICAgICAgICBwcmV2Q2hhciA9IHRva2VuLnJhdy5zbGljZSgtMSkKICAgICAgICB9CiAgICAgICAga2VlcFByZXZDaGFyID0gdHJ1ZQogICAgICAgIGxhc3RUb2tlbiA9IHRva2Vuc1t0b2tlbnMubGVuZ3RoIC0gMV0KICAgICAgICBpZiAobGFzdFRva2VuICYmIGxhc3RUb2tlbi50eXBlID09PSAidGV4dCIpIHsKICAgICAgICAgIGxhc3RUb2tlbi5yYXcgKz0gdG9rZW4ucmF3CiAgICAgICAgICBsYXN0VG9rZW4udGV4dCArPSB0b2tlbi50ZXh0CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIHRva2Vucy5wdXNoKHRva2VuKQogICAgICAgIH0KICAgICAgICBjb250aW51ZQogICAgICB9CgogICAgICBpZiAoc3JjKSB7CiAgICAgICAgY29uc3QgZXJyTXNnID0gIkluZmluaXRlIGxvb3Agb24gYnl0ZTogIiArIHNyYy5jaGFyQ29kZUF0KDApCiAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5zaWxlbnQpIHsKICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyTXNnKQogICAgICAgICAgYnJlYWsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGVyck1zZykKICAgICAgICB9CiAgICAgIH0KICAgIH0KCiAgICByZXR1cm4gdG9rZW5zCiAgfQp9CgovKioKICogUmVuZGVyZXIKICovCmNsYXNzIFJlbmRlcmVyIHsKICBjb25zdHJ1Y3RvcihvcHRpb25zKSB7CiAgICB0aGlzLm9wdGlvbnMgPSBvcHRpb25zIHx8IGRlZmF1bHRzCiAgfQoKICBjb2RlKGNvZGUsIGluZm9zdHJpbmcsIGVzY2FwZWQpIHsKICAgIGNvbnN0IGxhbmcgPSAoaW5mb3N0cmluZyB8fCAiIikubWF0Y2goL1xTKi8pWzBdCiAgICBpZiAodGhpcy5vcHRpb25zLmhpZ2hsaWdodCkgewogICAgICBjb25zdCBvdXQgPSB0aGlzLm9wdGlvbnMuaGlnaGxpZ2h0KGNvZGUsIGxhbmcpCiAgICAgIGlmIChvdXQgIT0gbnVsbCAmJiBvdXQgIT09IGNvZGUpIHsKICAgICAgICBlc2NhcGVkID0gdHJ1ZQogICAgICAgIGNvZGUgPSBvdXQKICAgICAgfQogICAgfQoKICAgIGNvZGUgPSBjb2RlLnJlcGxhY2UoL1xuJC8sICIiKSArICJcbiIKCiAgICBpZiAoIWxhbmcpIHsKICAgICAgcmV0dXJuICgKICAgICAgICAiPHByZT48Y29kZT4iICsKICAgICAgICAoZXNjYXBlZCA/IGNvZGUgOiBlc2NhcGUoY29kZSwgdHJ1ZSkpICsKICAgICAgICAiPC9jb2RlPjwvcHJlPlxuIgogICAgICApCiAgICB9CgogICAgcmV0dXJuICgKICAgICAgJzxwcmU+PGNvZGUgY2xhc3M9IicgKwogICAgICB0aGlzLm9wdGlvbnMubGFuZ1ByZWZpeCArCiAgICAgIGVzY2FwZShsYW5nLCB0cnVlKSArCiAgICAgICciPicgKwogICAgICAoZXNjYXBlZCA/IGNvZGUgOiBlc2NhcGUoY29kZSwgdHJ1ZSkpICsKICAgICAgIjwvY29kZT48L3ByZT5cbiIKICAgICkKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSBxdW90ZQogICAqLwogIGJsb2NrcXVvdGUocXVvdGUpIHsKICAgIHJldHVybiBgPGJsb2NrcXVvdGU+XG4ke3F1b3RlfTwvYmxvY2txdW90ZT5cbmAKICB9CgogIGh0bWwoaHRtbCkgewogICAgcmV0dXJuIGh0bWwKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAgICogQHBhcmFtIHtzdHJpbmd9IGxldmVsCiAgICogQHBhcmFtIHtzdHJpbmd9IHJhdwogICAqIEBwYXJhbSB7YW55fSBzbHVnZ2VyCiAgICovCiAgaGVhZGluZyh0ZXh0LCBsZXZlbCwgcmF3LCBzbHVnZ2VyKSB7CiAgICBpZiAodGhpcy5vcHRpb25zLmhlYWRlcklkcykgewogICAgICBjb25zdCBpZCA9IHRoaXMub3B0aW9ucy5oZWFkZXJQcmVmaXggKyBzbHVnZ2VyLnNsdWcocmF3KQogICAgICByZXR1cm4gYDxoJHtsZXZlbH0gaWQ9IiR7aWR9Ij4ke3RleHR9PC9oJHtsZXZlbH0+XG5gCiAgICB9CgogICAgLy8gaWdub3JlIElEcwogICAgcmV0dXJuIGA8aCR7bGV2ZWx9PiR7dGV4dH08L2gke2xldmVsfT5cbmAKICB9CgogIGhyKCkgewogICAgcmV0dXJuIHRoaXMub3B0aW9ucy54aHRtbCA/ICI8aHIvPlxuIiA6ICI8aHI+XG4iCiAgfQoKICBsaXN0KGJvZHksIG9yZGVyZWQsIHN0YXJ0KSB7CiAgICBjb25zdCB0eXBlID0gb3JkZXJlZCA/ICJvbCIgOiAidWwiLAogICAgICBzdGFydGF0dCA9IG9yZGVyZWQgJiYgc3RhcnQgIT09IDEgPyAnIHN0YXJ0PSInICsgc3RhcnQgKyAnIicgOiAiIgogICAgcmV0dXJuICI8IiArIHR5cGUgKyBzdGFydGF0dCArICI+XG4iICsgYm9keSArICI8LyIgKyB0eXBlICsgIj5cbiIKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAgICovCiAgbGlzdGl0ZW0odGV4dCkgewogICAgcmV0dXJuIGA8bGk+JHt0ZXh0fTwvbGk+XG5gCiAgfQoKICBjaGVja2JveChjaGVja2VkKSB7CiAgICByZXR1cm4gKAogICAgICAiPGlucHV0ICIgKwogICAgICAoY2hlY2tlZCA/ICdjaGVja2VkPSIiICcgOiAiIikgKwogICAgICAnZGlzYWJsZWQ9IiIgdHlwZT0iY2hlY2tib3giJyArCiAgICAgICh0aGlzLm9wdGlvbnMueGh0bWwgPyAiIC8iIDogIiIpICsKICAgICAgIj4gIgogICAgKQogIH0KCiAgLyoqCiAgICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICAgKi8KICBwYXJhZ3JhcGgodGV4dCkgewogICAgcmV0dXJuIGA8cD4ke3RleHR9PC9wPlxuYAogIH0KCiAgLyoqCiAgICogQHBhcmFtIHtzdHJpbmd9IGhlYWRlcgogICAqIEBwYXJhbSB7c3RyaW5nfSBib2R5CiAgICovCiAgdGFibGUoaGVhZGVyLCBib2R5KSB7CiAgICBpZiAoYm9keSkgYm9keSA9IGA8dGJvZHk+JHtib2R5fTwvdGJvZHk+YAoKICAgIHJldHVybiAoCiAgICAgICI8dGFibGU+XG4iICsgIjx0aGVhZD5cbiIgKyBoZWFkZXIgKyAiPC90aGVhZD5cbiIgKyBib2R5ICsgIjwvdGFibGU+XG4iCiAgICApCiAgfQoKICAvKioKICAgKiBAcGFyYW0ge3N0cmluZ30gY29udGVudAogICAqLwogIHRhYmxlcm93KGNvbnRlbnQpIHsKICAgIHJldHVybiBgPHRyPlxuJHtjb250ZW50fTwvdHI+XG5gCiAgfQoKICB0YWJsZWNlbGwoY29udGVudCwgZmxhZ3MpIHsKICAgIGNvbnN0IHR5cGUgPSBmbGFncy5oZWFkZXIgPyAidGgiIDogInRkIgogICAgY29uc3QgdGFnID0gZmxhZ3MuYWxpZ24gPyBgPCR7dHlwZX0gYWxpZ249IiR7ZmxhZ3MuYWxpZ259Ij5gIDogYDwke3R5cGV9PmAKICAgIHJldHVybiB0YWcgKyBjb250ZW50ICsgYDwvJHt0eXBlfT5cbmAKICB9CgogIC8qKgogICAqIHNwYW4gbGV2ZWwgcmVuZGVyZXIKICAgKiBAcGFyYW0ge3N0cmluZ30gdGV4dAogICAqLwogIHN0cm9uZyh0ZXh0KSB7CiAgICByZXR1cm4gYDxzdHJvbmc+JHt0ZXh0fTwvc3Ryb25nPmAKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAgICovCiAgZW0odGV4dCkgewogICAgcmV0dXJuIGA8ZW0+JHt0ZXh0fTwvZW0+YAogIH0KCiAgLyoqCiAgICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICAgKi8KICBjb2Rlc3Bhbih0ZXh0KSB7CiAgICByZXR1cm4gYDxjb2RlPiR7dGV4dH08L2NvZGU+YAogIH0KCiAgYnIoKSB7CiAgICByZXR1cm4gdGhpcy5vcHRpb25zLnhodG1sID8gIjxici8+IiA6ICI8YnI+IgogIH0KCiAgLyoqCiAgICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICAgKi8KICBkZWwodGV4dCkgewogICAgcmV0dXJuIGA8ZGVsPiR7dGV4dH08L2RlbD5gCiAgfQoKICAvKioKICAgKiBAcGFyYW0ge3N0cmluZ30gaHJlZgogICAqIEBwYXJhbSB7c3RyaW5nfSB0aXRsZQogICAqIEBwYXJhbSB7c3RyaW5nfSB0ZXh0CiAgICovCiAgbGluayhocmVmLCB0aXRsZSwgdGV4dCkgewogICAgaHJlZiA9IGNsZWFuVXJsKHRoaXMub3B0aW9ucy5zYW5pdGl6ZSwgdGhpcy5vcHRpb25zLmJhc2VVcmwsIGhyZWYpCiAgICBpZiAoaHJlZiA9PT0gbnVsbCkgewogICAgICByZXR1cm4gdGV4dAogICAgfQogICAgbGV0IG91dCA9ICc8YSBocmVmPSInICsgZXNjYXBlKGhyZWYpICsgJyInCiAgICBpZiAodGl0bGUpIHsKICAgICAgb3V0ICs9ICcgdGl0bGU9IicgKyB0aXRsZSArICciJwogICAgfQogICAgb3V0ICs9ICI+IiArIHRleHQgKyAiPC9hPiIKICAgIHJldHVybiBvdXQKICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSBocmVmCiAgICogQHBhcmFtIHtzdHJpbmd9IHRpdGxlCiAgICogQHBhcmFtIHtzdHJpbmd9IHRleHQKICAgKi8KICBpbWFnZShocmVmLCB0aXRsZSwgdGV4dCkgewogICAgaHJlZiA9IGNsZWFuVXJsKHRoaXMub3B0aW9ucy5zYW5pdGl6ZSwgdGhpcy5vcHRpb25zLmJhc2VVcmwsIGhyZWYpCiAgICBpZiAoaHJlZiA9PT0gbnVsbCkgewogICAgICByZXR1cm4gdGV4dAogICAgfQoKICAgIGxldCBvdXQgPSBgPGltZyBzcmM9IiR7aHJlZn0iIGFsdD0iJHt0ZXh0fSJgCiAgICBpZiAodGl0bGUpIHsKICAgICAgb3V0ICs9IGAgdGl0bGU9IiR7dGl0bGV9ImAKICAgIH0KICAgIG91dCArPSB0aGlzLm9wdGlvbnMueGh0bWwgPyAiLz4iIDogIj4iCiAgICByZXR1cm4gb3V0CiAgfQoKICB0ZXh0KHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQp9CgovKioKICogVGV4dFJlbmRlcmVyCiAqIHJldHVybnMgb25seSB0aGUgdGV4dHVhbCBwYXJ0IG9mIHRoZSB0b2tlbgogKi8KY2xhc3MgVGV4dFJlbmRlcmVyIHsKICAvLyBubyBuZWVkIGZvciBibG9jayBsZXZlbCByZW5kZXJlcnMKICBzdHJvbmcodGV4dCkgewogICAgcmV0dXJuIHRleHQKICB9CgogIGVtKHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICBjb2Rlc3Bhbih0ZXh0KSB7CiAgICByZXR1cm4gdGV4dAogIH0KCiAgZGVsKHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICBodG1sKHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICB0ZXh0KHRleHQpIHsKICAgIHJldHVybiB0ZXh0CiAgfQoKICBsaW5rKGhyZWYsIHRpdGxlLCB0ZXh0KSB7CiAgICByZXR1cm4gIiIgKyB0ZXh0CiAgfQoKICBpbWFnZShocmVmLCB0aXRsZSwgdGV4dCkgewogICAgcmV0dXJuICIiICsgdGV4dAogIH0KCiAgYnIoKSB7CiAgICByZXR1cm4gIiIKICB9Cn0KCi8qKgogKiBTbHVnZ2VyIGdlbmVyYXRlcyBoZWFkZXIgaWQKICovCmNsYXNzIFNsdWdnZXIgewogIGNvbnN0cnVjdG9yKCkgewogICAgdGhpcy5zZWVuID0ge30KICB9CgogIC8qKgogICAqIEBwYXJhbSB7c3RyaW5nfSB2YWx1ZQogICAqLwogIHNlcmlhbGl6ZSh2YWx1ZSkgewogICAgcmV0dXJuICgKICAgICAgdmFsdWUKICAgICAgICAudG9Mb3dlckNhc2UoKQogICAgICAgIC50cmltKCkKICAgICAgICAvLyByZW1vdmUgaHRtbCB0YWdzCiAgICAgICAgLnJlcGxhY2UoLzxbIVwvYS16XS4qPz4vZ2ksICIiKQogICAgICAgIC8vIHJlbW92ZSB1bndhbnRlZCBjaGFycwogICAgICAgIC5yZXBsYWNlKAogICAgICAgICAgL1tcdTIwMDAtXHUyMDZGXHUyRTAwLVx1MkU3RlxcJyEiIyQlJigpKissLi86Ozw9Pj9AW1xdXmB7fH1+XS9nLAogICAgICAgICAgIiIKICAgICAgICApCiAgICAgICAgLnJlcGxhY2UoL1xzL2csICItIikKICAgICkKICB9CgogIC8qKgogICAqIEZpbmRzIHRoZSBuZXh0IHNhZmUgKHVuaXF1ZSkgc2x1ZyB0byB1c2UKICAgKiBAcGFyYW0ge3N0cmluZ30gb3JpZ2luYWxTbHVnCiAgICogQHBhcmFtIHtib29sZWFufSBpc0RyeVJ1bgogICAqLwogIGdldE5leHRTYWZlU2x1ZyhvcmlnaW5hbFNsdWcsIGlzRHJ5UnVuKSB7CiAgICBsZXQgc2x1ZyA9IG9yaWdpbmFsU2x1ZwogICAgbGV0IG9jY3VyZW5jZUFjY3VtdWxhdG9yID0gMAogICAgaWYgKHRoaXMuc2Vlbi5oYXNPd25Qcm9wZXJ0eShzbHVnKSkgewogICAgICBvY2N1cmVuY2VBY2N1bXVsYXRvciA9IHRoaXMuc2VlbltvcmlnaW5hbFNsdWddCiAgICAgIGRvIHsKICAgICAgICBvY2N1cmVuY2VBY2N1bXVsYXRvcisrCiAgICAgICAgc2x1ZyA9IG9yaWdpbmFsU2x1ZyArICItIiArIG9jY3VyZW5jZUFjY3VtdWxhdG9yCiAgICAgIH0gd2hpbGUgKHRoaXMuc2Vlbi5oYXNPd25Qcm9wZXJ0eShzbHVnKSkKICAgIH0KICAgIGlmICghaXNEcnlSdW4pIHsKICAgICAgdGhpcy5zZWVuW29yaWdpbmFsU2x1Z10gPSBvY2N1cmVuY2VBY2N1bXVsYXRvcgogICAgICB0aGlzLnNlZW5bc2x1Z10gPSAwCiAgICB9CiAgICByZXR1cm4gc2x1ZwogIH0KCiAgLyoqCiAgICogQ29udmVydCBzdHJpbmcgdG8gdW5pcXVlIGlkCiAgICogQHBhcmFtIHtvYmplY3R9IFtvcHRpb25zXQogICAqIEBwYXJhbSB7Ym9vbGVhbn0gW29wdGlvbnMuZHJ5cnVuXSBHZW5lcmF0ZXMgdGhlIG5leHQgdW5pcXVlIHNsdWcgd2l0aG91dAogICAqIHVwZGF0aW5nIHRoZSBpbnRlcm5hbCBhY2N1bXVsYXRvci4KICAgKi8KICBzbHVnKHZhbHVlLCBvcHRpb25zID0ge30pIHsKICAgIGNvbnN0IHNsdWcgPSB0aGlzLnNlcmlhbGl6ZSh2YWx1ZSkKICAgIHJldHVybiB0aGlzLmdldE5leHRTYWZlU2x1ZyhzbHVnLCBvcHRpb25zLmRyeXJ1bikKICB9Cn0KCi8qKgogKiBQYXJzaW5nICYgQ29tcGlsaW5nCiAqLwpjbGFzcyBQYXJzZXIgewogIGNvbnN0cnVjdG9yKG9wdGlvbnMpIHsKICAgIHRoaXMub3B0aW9ucyA9IG9wdGlvbnMgfHwgZGVmYXVsdHMKICAgIHRoaXMub3B0aW9ucy5yZW5kZXJlciA9IHRoaXMub3B0aW9ucy5yZW5kZXJlciB8fCBuZXcgUmVuZGVyZXIoKQogICAgdGhpcy5yZW5kZXJlciA9IHRoaXMub3B0aW9ucy5yZW5kZXJlcgogICAgdGhpcy5yZW5kZXJlci5vcHRpb25zID0gdGhpcy5vcHRpb25zCiAgICB0aGlzLnRleHRSZW5kZXJlciA9IG5ldyBUZXh0UmVuZGVyZXIoKQogICAgdGhpcy5zbHVnZ2VyID0gbmV3IFNsdWdnZXIoKQogIH0KCiAgLyoqCiAgICogU3RhdGljIFBhcnNlIE1ldGhvZAogICAqLwogIHN0YXRpYyBwYXJzZSh0b2tlbnMsIG9wdGlvbnMpIHsKICAgIGNvbnN0IHBhcnNlciA9IG5ldyBQYXJzZXIob3B0aW9ucykKICAgIHJldHVybiBwYXJzZXIucGFyc2UodG9rZW5zKQogIH0KCiAgLyoqCiAgICogU3RhdGljIFBhcnNlIElubGluZSBNZXRob2QKICAgKi8KICBzdGF0aWMgcGFyc2VJbmxpbmUodG9rZW5zLCBvcHRpb25zKSB7CiAgICBjb25zdCBwYXJzZXIgPSBuZXcgUGFyc2VyKG9wdGlvbnMpCiAgICByZXR1cm4gcGFyc2VyLnBhcnNlSW5saW5lKHRva2VucykKICB9CgogIC8qKgogICAqIFBhcnNlIExvb3AKICAgKi8KICBwYXJzZSh0b2tlbnMsIHRvcCA9IHRydWUpIHsKICAgIGxldCBvdXQgPSAiIiwKICAgICAgaSwKICAgICAgaiwKICAgICAgaywKICAgICAgbDIsCiAgICAgIGwzLAogICAgICByb3csCiAgICAgIGNlbGwsCiAgICAgIGhlYWRlciwKICAgICAgYm9keSwKICAgICAgdG9rZW4sCiAgICAgIG9yZGVyZWQsCiAgICAgIHN0YXJ0LAogICAgICBsb29zZSwKICAgICAgaXRlbUJvZHksCiAgICAgIGl0ZW0sCiAgICAgIGNoZWNrZWQsCiAgICAgIHRhc2ssCiAgICAgIGNoZWNrYm94LAogICAgICByZXQKCiAgICBjb25zdCBsID0gdG9rZW5zLmxlbmd0aAogICAgZm9yIChpID0gMDsgaSA8IGw7IGkrKykgewogICAgICB0b2tlbiA9IHRva2Vuc1tpXQoKICAgICAgLy8gUnVuIGFueSByZW5kZXJlciBleHRlbnNpb25zCiAgICAgIGlmICgKICAgICAgICB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucyAmJgogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLnJlbmRlcmVycyAmJgogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zLnJlbmRlcmVyc1t0b2tlbi50eXBlXQogICAgICApIHsKICAgICAgICByZXQgPSB0aGlzLm9wdGlvbnMuZXh0ZW5zaW9ucy5yZW5kZXJlcnNbdG9rZW4udHlwZV0uY2FsbCgKICAgICAgICAgIHsgcGFyc2VyOiB0aGlzIH0sCiAgICAgICAgICB0b2tlbgogICAgICAgICkKICAgICAgICBpZiAoCiAgICAgICAgICByZXQgIT09IGZhbHNlIHx8CiAgICAgICAgICAhWwogICAgICAgICAgICAic3BhY2UiLAogICAgICAgICAgICAiaHIiLAogICAgICAgICAgICAiaGVhZGluZyIsCiAgICAgICAgICAgICJjb2RlIiwKICAgICAgICAgICAgInRhYmxlIiwKICAgICAgICAgICAgImJsb2NrcXVvdGUiLAogICAgICAgICAgICAibGlzdCIsCiAgICAgICAgICAgICJodG1sIiwKICAgICAgICAgICAgInBhcmFncmFwaCIsCiAgICAgICAgICAgICJ0ZXh0IiwKICAgICAgICAgIF0uaW5jbHVkZXModG9rZW4udHlwZSkKICAgICAgICApIHsKICAgICAgICAgIG91dCArPSByZXQgfHwgIiIKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICB9CgogICAgICBzd2l0Y2ggKHRva2VuLnR5cGUpIHsKICAgICAgICBjYXNlICJzcGFjZSI6IHsKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICAgIGNhc2UgImhyIjogewogICAgICAgICAgb3V0ICs9IHRoaXMucmVuZGVyZXIuaHIoKQogICAgICAgICAgY29udGludWUKICAgICAgICB9CiAgICAgICAgY2FzZSAiaGVhZGluZyI6IHsKICAgICAgICAgIG91dCArPSB0aGlzLnJlbmRlcmVyLmhlYWRpbmcoCiAgICAgICAgICAgIHRoaXMucGFyc2VJbmxpbmUodG9rZW4udG9rZW5zKSwKICAgICAgICAgICAgdG9rZW4uZGVwdGgsCiAgICAgICAgICAgIHVuZXNjYXBlKHRoaXMucGFyc2VJbmxpbmUodG9rZW4udG9rZW5zLCB0aGlzLnRleHRSZW5kZXJlcikpLAogICAgICAgICAgICB0aGlzLnNsdWdnZXIKICAgICAgICAgICkKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICAgIGNhc2UgImNvZGUiOiB7CiAgICAgICAgICBvdXQgKz0gdGhpcy5yZW5kZXJlci5jb2RlKHRva2VuLnRleHQsIHRva2VuLmxhbmcsIHRva2VuLmVzY2FwZWQpCiAgICAgICAgICBjb250aW51ZQogICAgICAgIH0KICAgICAgICBjYXNlICJ0YWJsZSI6IHsKICAgICAgICAgIGhlYWRlciA9ICIiCgogICAgICAgICAgLy8gaGVhZGVyCiAgICAgICAgICBjZWxsID0gIiIKICAgICAgICAgIGwyID0gdG9rZW4uaGVhZGVyLmxlbmd0aAogICAgICAgICAgZm9yIChqID0gMDsgaiA8IGwyOyBqKyspIHsKICAgICAgICAgICAgY2VsbCArPSB0aGlzLnJlbmRlcmVyLnRhYmxlY2VsbCgKICAgICAgICAgICAgICB0aGlzLnBhcnNlSW5saW5lKHRva2VuLmhlYWRlcltqXS50b2tlbnMpLAogICAgICAgICAgICAgIHsgaGVhZGVyOiB0cnVlLCBhbGlnbjogdG9rZW4uYWxpZ25bal0gfQogICAgICAgICAgICApCiAgICAgICAgICB9CiAgICAgICAgICBoZWFkZXIgKz0gdGhpcy5yZW5kZXJlci50YWJsZXJvdyhjZWxsKQoKICAgICAgICAgIGJvZHkgPSAiIgogICAgICAgICAgbDIgPSB0b2tlbi5yb3dzLmxlbmd0aAogICAgICAgICAgZm9yIChqID0gMDsgaiA8IGwyOyBqKyspIHsKICAgICAgICAgICAgcm93ID0gdG9rZW4ucm93c1tqXQoKICAgICAgICAgICAgY2VsbCA9ICIiCiAgICAgICAgICAgIGwzID0gcm93Lmxlbmd0aAogICAgICAgICAgICBmb3IgKGsgPSAwOyBrIDwgbDM7IGsrKykgewogICAgICAgICAgICAgIGNlbGwgKz0gdGhpcy5yZW5kZXJlci50YWJsZWNlbGwodGhpcy5wYXJzZUlubGluZShyb3dba10udG9rZW5zKSwgewogICAgICAgICAgICAgICAgaGVhZGVyOiBmYWxzZSwKICAgICAgICAgICAgICAgIGFsaWduOiB0b2tlbi5hbGlnbltrXSwKICAgICAgICAgICAgICB9KQogICAgICAgICAgICB9CgogICAgICAgICAgICBib2R5ICs9IHRoaXMucmVuZGVyZXIudGFibGVyb3coY2VsbCkKICAgICAgICAgIH0KICAgICAgICAgIG91dCArPSB0aGlzLnJlbmRlcmVyLnRhYmxlKGhlYWRlciwgYm9keSkKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICAgIGNhc2UgImJsb2NrcXVvdGUiOiB7CiAgICAgICAgICBib2R5ID0gdGhpcy5wYXJzZSh0b2tlbi50b2tlbnMpCiAgICAgICAgICBvdXQgKz0gdGhpcy5yZW5kZXJlci5ibG9ja3F1b3RlKGJvZHkpCiAgICAgICAgICBjb250aW51ZQogICAgICAgIH0KICAgICAgICBjYXNlICJsaXN0IjogewogICAgICAgICAgb3JkZXJlZCA9IHRva2VuLm9yZGVyZWQKICAgICAgICAgIHN0YXJ0ID0gdG9rZW4uc3RhcnQKICAgICAgICAgIGxvb3NlID0gdG9rZW4ubG9vc2UKICAgICAgICAgIGwyID0gdG9rZW4uaXRlbXMubGVuZ3RoCgogICAgICAgICAgYm9keSA9ICIiCiAgICAgICAgICBmb3IgKGogPSAwOyBqIDwgbDI7IGorKykgewogICAgICAgICAgICBpdGVtID0gdG9rZW4uaXRlbXNbal0KICAgICAgICAgICAgY2hlY2tlZCA9IGl0ZW0uY2hlY2tlZAogICAgICAgICAgICB0YXNrID0gaXRlbS50YXNrCgogICAgICAgICAgICBpdGVtQm9keSA9ICIiCiAgICAgICAgICAgIGlmIChpdGVtLnRhc2spIHsKICAgICAgICAgICAgICBjaGVja2JveCA9IHRoaXMucmVuZGVyZXIuY2hlY2tib3goY2hlY2tlZCkKICAgICAgICAgICAgICBpZiAobG9vc2UpIHsKICAgICAgICAgICAgICAgIGlmICgKICAgICAgICAgICAgICAgICAgaXRlbS50b2tlbnMubGVuZ3RoID4gMCAmJgogICAgICAgICAgICAgICAgICBpdGVtLnRva2Vuc1swXS50eXBlID09PSAicGFyYWdyYXBoIgogICAgICAgICAgICAgICAgKSB7CiAgICAgICAgICAgICAgICAgIGl0ZW0udG9rZW5zWzBdLnRleHQgPSBjaGVja2JveCArICIgIiArIGl0ZW0udG9rZW5zWzBdLnRleHQKICAgICAgICAgICAgICAgICAgaWYgKAogICAgICAgICAgICAgICAgICAgIGl0ZW0udG9rZW5zWzBdLnRva2VucyAmJgogICAgICAgICAgICAgICAgICAgIGl0ZW0udG9rZW5zWzBdLnRva2Vucy5sZW5ndGggPiAwICYmCiAgICAgICAgICAgICAgICAgICAgaXRlbS50b2tlbnNbMF0udG9rZW5zWzBdLnR5cGUgPT09ICJ0ZXh0IgogICAgICAgICAgICAgICAgICApIHsKICAgICAgICAgICAgICAgICAgICBpdGVtLnRva2Vuc1swXS50b2tlbnNbMF0udGV4dCA9CiAgICAgICAgICAgICAgICAgICAgICBjaGVja2JveCArICIgIiArIGl0ZW0udG9rZW5zWzBdLnRva2Vuc1swXS50ZXh0CiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgIGl0ZW0udG9rZW5zLnVuc2hpZnQoewogICAgICAgICAgICAgICAgICAgIHR5cGU6ICJ0ZXh0IiwKICAgICAgICAgICAgICAgICAgICB0ZXh0OiBjaGVja2JveCwKICAgICAgICAgICAgICAgICAgfSkKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgaXRlbUJvZHkgKz0gY2hlY2tib3gKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGl0ZW1Cb2R5ICs9IHRoaXMucGFyc2UoaXRlbS50b2tlbnMsIGxvb3NlKQogICAgICAgICAgICBib2R5ICs9IHRoaXMucmVuZGVyZXIubGlzdGl0ZW0oaXRlbUJvZHksIHRhc2ssIGNoZWNrZWQpCiAgICAgICAgICB9CgogICAgICAgICAgb3V0ICs9IHRoaXMucmVuZGVyZXIubGlzdChib2R5LCBvcmRlcmVkLCBzdGFydCkKICAgICAgICAgIGNvbnRpbnVlCiAgICAgICAgfQogICAgICAgIGNhc2UgImh0bWwiOiB7CiAgICAgICAgICAvLyBUT0RPIHBhcnNlIGlubGluZSBjb250ZW50IGlmIHBhcmFtZXRlciBtYXJrZG93bj0xCiAgICAgICAgICBvdXQgKz0gdGhpcy5yZW5kZXJlci5odG1sKHRva2VuLnRleHQpCiAgICAgICAgICBjb250aW51ZQogICAgICAgIH0KICAgICAgICBjYXNlICJwYXJhZ3JhcGgiOiB7CiAgICAgICAgICBvdXQgKz0gdGhpcy5yZW5kZXJlci5wYXJhZ3JhcGgodGhpcy5wYXJzZUlubGluZSh0b2tlbi50b2tlbnMpKQogICAgICAgICAgY29udGludWUKICAgICAgICB9CiAgICAgICAgY2FzZSAidGV4dCI6IHsKICAgICAgICAgIGJvZHkgPSB0b2tlbi50b2tlbnMgPyB0aGlzLnBhcnNlSW5saW5lKHRva2VuLnRva2VucykgOiB0b2tlbi50ZXh0CiAgICAgICAgICB3aGlsZSAoaSArIDEgPCBsICYmIHRva2Vuc1tpICsgMV0udHlwZSA9PT0gInRleHQiKSB7CiAgICAgICAgICAgIHRva2VuID0gdG9rZW5zWysraV0KICAgICAgICAgICAgYm9keSArPQogICAgICAgICAgICAgICJcbiIgKwogICAgICAgICAgICAgICh0b2tlbi50b2tlbnMgPyB0aGlzLnBhcnNlSW5saW5lKHRva2VuLnRva2VucykgOiB0b2tlbi50ZXh0KQogICAgICAgICAgfQogICAgICAgICAgb3V0ICs9IHRvcCA/IHRoaXMucmVuZGVyZXIucGFyYWdyYXBoKGJvZHkpIDogYm9keQogICAgICAgICAgY29udGludWUKICAgICAgICB9CgogICAgICAgIGRlZmF1bHQ6IHsKICAgICAgICAgIGNvbnN0IGVyck1zZyA9ICdUb2tlbiB3aXRoICInICsgdG9rZW4udHlwZSArICciIHR5cGUgd2FzIG5vdCBmb3VuZC4nCiAgICAgICAgICBpZiAodGhpcy5vcHRpb25zLnNpbGVudCkgewogICAgICAgICAgICBjb25zb2xlLmVycm9yKGVyck1zZykKICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZXJyTXNnKQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfQoKICAgIHJldHVybiBvdXQKICB9CgogIC8qKgogICAqIFBhcnNlIElubGluZSBUb2tlbnMKICAgKi8KICBwYXJzZUlubGluZSh0b2tlbnMsIHJlbmRlcmVyKSB7CiAgICByZW5kZXJlciA9IHJlbmRlcmVyIHx8IHRoaXMucmVuZGVyZXIKICAgIGxldCBvdXQgPSAiIiwKICAgICAgaSwKICAgICAgdG9rZW4sCiAgICAgIHJldAoKICAgIGNvbnN0IGwgPSB0b2tlbnMubGVuZ3RoCiAgICBmb3IgKGkgPSAwOyBpIDwgbDsgaSsrKSB7CiAgICAgIHRva2VuID0gdG9rZW5zW2ldCgogICAgICAvLyBSdW4gYW55IHJlbmRlcmVyIGV4dGVuc2lvbnMKICAgICAgaWYgKAogICAgICAgIHRoaXMub3B0aW9ucy5leHRlbnNpb25zICYmCiAgICAgICAgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMucmVuZGVyZXJzICYmCiAgICAgICAgdGhpcy5vcHRpb25zLmV4dGVuc2lvbnMucmVuZGVyZXJzW3Rva2VuLnR5cGVdCiAgICAgICkgewogICAgICAgIHJldCA9IHRoaXMub3B0aW9ucy5leHRlbnNpb25zLnJlbmRlcmVyc1t0b2tlbi50eXBlXS5jYWxsKAogICAgICAgICAgeyBwYXJzZXI6IHRoaXMgfSwKICAgICAgICAgIHRva2VuCiAgICAgICAgKQogICAgICAgIGlmICgKICAgICAgICAgIHJldCAhPT0gZmFsc2UgfHwKICAgICAgICAgICFbCiAgICAgICAgICAgICJlc2NhcGUiLAogICAgICAgICAgICAiaHRtbCIsCiAgICAgICAgICAgICJsaW5rIiwKICAgICAgICAgICAgImltYWdlIiwKICAgICAgICAgICAgInN0cm9uZyIsCiAgICAgICAgICAgICJlbSIsCiAgICAgICAgICAgICJjb2Rlc3BhbiIsCiAgICAgICAgICAgICJiciIsCiAgICAgICAgICAgICJkZWwiLAogICAgICAgICAgICAidGV4dCIsCiAgICAgICAgICBdLmluY2x1ZGVzKHRva2VuLnR5cGUpCiAgICAgICAgKSB7CiAgICAgICAgICBvdXQgKz0gcmV0IHx8ICIiCiAgICAgICAgICBjb250aW51ZQogICAgICAgIH0KICAgICAgfQoKICAgICAgc3dpdGNoICh0b2tlbi50eXBlKSB7CiAgICAgICAgY2FzZSAiZXNjYXBlIjogewogICAgICAgICAgb3V0ICs9IHJlbmRlcmVyLnRleHQodG9rZW4udGV4dCkKICAgICAgICAgIGJyZWFrCiAgICAgICAgfQogICAgICAgIGNhc2UgImh0bWwiOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIuaHRtbCh0b2tlbi50ZXh0KQogICAgICAgICAgYnJlYWsKICAgICAgICB9CiAgICAgICAgY2FzZSAibGluayI6IHsKICAgICAgICAgIG91dCArPSByZW5kZXJlci5saW5rKAogICAgICAgICAgICB0b2tlbi5ocmVmLAogICAgICAgICAgICB0b2tlbi50aXRsZSwKICAgICAgICAgICAgdGhpcy5wYXJzZUlubGluZSh0b2tlbi50b2tlbnMsIHJlbmRlcmVyKQogICAgICAgICAgKQogICAgICAgICAgYnJlYWsKICAgICAgICB9CiAgICAgICAgY2FzZSAiaW1hZ2UiOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIuaW1hZ2UodG9rZW4uaHJlZiwgdG9rZW4udGl0bGUsIHRva2VuLnRleHQpCiAgICAgICAgICBicmVhawogICAgICAgIH0KICAgICAgICBjYXNlICJzdHJvbmciOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIuc3Ryb25nKHRoaXMucGFyc2VJbmxpbmUodG9rZW4udG9rZW5zLCByZW5kZXJlcikpCiAgICAgICAgICBicmVhawogICAgICAgIH0KICAgICAgICBjYXNlICJlbSI6IHsKICAgICAgICAgIG91dCArPSByZW5kZXJlci5lbSh0aGlzLnBhcnNlSW5saW5lKHRva2VuLnRva2VucywgcmVuZGVyZXIpKQogICAgICAgICAgYnJlYWsKICAgICAgICB9CiAgICAgICAgY2FzZSAiY29kZXNwYW4iOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIuY29kZXNwYW4odG9rZW4udGV4dCkKICAgICAgICAgIGJyZWFrCiAgICAgICAgfQogICAgICAgIGNhc2UgImJyIjogewogICAgICAgICAgb3V0ICs9IHJlbmRlcmVyLmJyKCkKICAgICAgICAgIGJyZWFrCiAgICAgICAgfQogICAgICAgIGNhc2UgImRlbCI6IHsKICAgICAgICAgIG91dCArPSByZW5kZXJlci5kZWwodGhpcy5wYXJzZUlubGluZSh0b2tlbi50b2tlbnMsIHJlbmRlcmVyKSkKICAgICAgICAgIGJyZWFrCiAgICAgICAgfQogICAgICAgIGNhc2UgInRleHQiOiB7CiAgICAgICAgICBvdXQgKz0gcmVuZGVyZXIudGV4dCh0b2tlbi50ZXh0KQogICAgICAgICAgYnJlYWsKICAgICAgICB9CiAgICAgICAgZGVmYXVsdDogewogICAgICAgICAgY29uc3QgZXJyTXNnID0gJ1Rva2VuIHdpdGggIicgKyB0b2tlbi50eXBlICsgJyIgdHlwZSB3YXMgbm90IGZvdW5kLicKICAgICAgICAgIGlmICh0aGlzLm9wdGlvbnMuc2lsZW50KSB7CiAgICAgICAgICAgIGNvbnNvbGUuZXJyb3IoZXJyTXNnKQogICAgICAgICAgICByZXR1cm4KICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihlcnJNc2cpCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICB9CiAgICB9CiAgICByZXR1cm4gb3V0CiAgfQp9CgovKioKICogTWFya2VkCiAqLwpmdW5jdGlvbiBtYXJrZWQoc3JjLCBvcHQsIGNhbGxiYWNrKSB7CiAgLy8gdGhyb3cgZXJyb3IgaW4gY2FzZSBvZiBub24gc3RyaW5nIGlucHV0CiAgaWYgKHR5cGVvZiBzcmMgPT09ICJ1bmRlZmluZWQiIHx8IHNyYyA9PT0gbnVsbCkgewogICAgdGhyb3cgbmV3IEVycm9yKCJtYXJrZWQoKTogaW5wdXQgcGFyYW1ldGVyIGlzIHVuZGVmaW5lZCBvciBudWxsIikKICB9CiAgaWYgKHR5cGVvZiBzcmMgIT09ICJzdHJpbmciKSB7CiAgICB0aHJvdyBuZXcgRXJyb3IoCiAgICAgICJtYXJrZWQoKTogaW5wdXQgcGFyYW1ldGVyIGlzIG9mIHR5cGUgIiArCiAgICAgICAgT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKHNyYykgKwogICAgICAgICIsIHN0cmluZyBleHBlY3RlZCIKICAgICkKICB9CgogIGlmICh0eXBlb2Ygb3B0ID09PSAiZnVuY3Rpb24iKSB7CiAgICBjYWxsYmFjayA9IG9wdAogICAgb3B0ID0gbnVsbAogIH0KCiAgb3B0ID0gbWVyZ2Uoe30sIG1hcmtlZC5kZWZhdWx0cywgb3B0IHx8IHt9KQogIGNoZWNrU2FuaXRpemVEZXByZWNhdGlvbihvcHQpCgogIGlmIChjYWxsYmFjaykgewogICAgY29uc3QgaGlnaGxpZ2h0ID0gb3B0LmhpZ2hsaWdodAogICAgbGV0IHRva2VucwoKICAgIHRyeSB7CiAgICAgIHRva2VucyA9IExleGVyLmxleChzcmMsIG9wdCkKICAgIH0gY2F0Y2ggKGUpIHsKICAgICAgcmV0dXJuIGNhbGxiYWNrKGUpCiAgICB9CgogICAgY29uc3QgZG9uZSA9IGZ1bmN0aW9uIChlcnIpIHsKICAgICAgbGV0IG91dAoKICAgICAgaWYgKCFlcnIpIHsKICAgICAgICB0cnkgewogICAgICAgICAgaWYgKG9wdC53YWxrVG9rZW5zKSB7CiAgICAgICAgICAgIG1hcmtlZC53YWxrVG9rZW5zKHRva2Vucywgb3B0LndhbGtUb2tlbnMpCiAgICAgICAgICB9CiAgICAgICAgICBvdXQgPSBQYXJzZXIucGFyc2UodG9rZW5zLCBvcHQpCiAgICAgICAgfSBjYXRjaCAoZSkgewogICAgICAgICAgZXJyID0gZQogICAgICAgIH0KICAgICAgfQoKICAgICAgb3B0LmhpZ2hsaWdodCA9IGhpZ2hsaWdodAoKICAgICAgcmV0dXJuIGVyciA/IGNhbGxiYWNrKGVycikgOiBjYWxsYmFjayhudWxsLCBvdXQpCiAgICB9CgogICAgaWYgKCFoaWdobGlnaHQgfHwgaGlnaGxpZ2h0Lmxlbmd0aCA8IDMpIHsKICAgICAgcmV0dXJuIGRvbmUoKQogICAgfQoKICAgIGRlbGV0ZSBvcHQuaGlnaGxpZ2h0CgogICAgaWYgKCF0b2tlbnMubGVuZ3RoKSByZXR1cm4gZG9uZSgpCgogICAgbGV0IHBlbmRpbmcgPSAwCiAgICBtYXJrZWQud2Fsa1Rva2Vucyh0b2tlbnMsIGZ1bmN0aW9uICh0b2tlbikgewogICAgICBpZiAodG9rZW4udHlwZSA9PT0gImNvZGUiKSB7CiAgICAgICAgcGVuZGluZysrCiAgICAgICAgc2V0VGltZW91dCgoKSA9PiB7CiAgICAgICAgICBoaWdobGlnaHQodG9rZW4udGV4dCwgdG9rZW4ubGFuZywgZnVuY3Rpb24gKGVyciwgY29kZSkgewogICAgICAgICAgICBpZiAoZXJyKSB7CiAgICAgICAgICAgICAgcmV0dXJuIGRvbmUoZXJyKQogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmIChjb2RlICE9IG51bGwgJiYgY29kZSAhPT0gdG9rZW4udGV4dCkgewogICAgICAgICAgICAgIHRva2VuLnRleHQgPSBjb2RlCiAgICAgICAgICAgICAgdG9rZW4uZXNjYXBlZCA9IHRydWUKICAgICAgICAgICAgfQoKICAgICAgICAgICAgcGVuZGluZy0tCiAgICAgICAgICAgIGlmIChwZW5kaW5nID09PSAwKSB7CiAgICAgICAgICAgICAgZG9uZSgpCiAgICAgICAgICAgIH0KICAgICAgICAgIH0pCiAgICAgICAgfSwgMCkKICAgICAgfQogICAgfSkKCiAgICBpZiAocGVuZGluZyA9PT0gMCkgewogICAgICBkb25lKCkKICAgIH0KCiAgICByZXR1cm4KICB9CgogIHRyeSB7CiAgICBjb25zdCB0b2tlbnMgPSBMZXhlci5sZXgoc3JjLCBvcHQpCiAgICBpZiAob3B0LndhbGtUb2tlbnMpIHsKICAgICAgbWFya2VkLndhbGtUb2tlbnModG9rZW5zLCBvcHQud2Fsa1Rva2VucykKICAgIH0KICAgIHJldHVybiBQYXJzZXIucGFyc2UodG9rZW5zLCBvcHQpCiAgfSBjYXRjaCAoZSkgewogICAgZS5tZXNzYWdlICs9ICJcblBsZWFzZSByZXBvcnQgdGhpcyB0byBodHRwczovL2dpdGh1Yi5jb20vbWFya2VkanMvbWFya2VkLiIKICAgIGlmIChvcHQuc2lsZW50KSB7CiAgICAgIHJldHVybiAoCiAgICAgICAgIjxwPkFuIGVycm9yIG9jY3VycmVkOjwvcD48cHJlPiIgKwogICAgICAgIGVzY2FwZShlLm1lc3NhZ2UgKyAiIiwgdHJ1ZSkgKwogICAgICAgICI8L3ByZT4iCiAgICAgICkKICAgIH0KICAgIHRocm93IGUKICB9Cn0KCi8qKgogKiBPcHRpb25zCiAqLwoKbWFya2VkLm9wdGlvbnMgPSBtYXJrZWQuc2V0T3B0aW9ucyA9IGZ1bmN0aW9uIChvcHQpIHsKICBtZXJnZShtYXJrZWQuZGVmYXVsdHMsIG9wdCkKICBjaGFuZ2VEZWZhdWx0cyhtYXJrZWQuZGVmYXVsdHMpCiAgcmV0dXJuIG1hcmtlZAp9CgptYXJrZWQuZ2V0RGVmYXVsdHMgPSBnZXREZWZhdWx0cwoKbWFya2VkLmRlZmF1bHRzID0gZGVmYXVsdHMKCi8qKgogKiBVc2UgRXh0ZW5zaW9uCiAqLwoKbWFya2VkLnVzZSA9IGZ1bmN0aW9uICguLi5hcmdzKSB7CiAgY29uc3Qgb3B0cyA9IG1lcmdlKHt9LCAuLi5hcmdzKQogIGNvbnN0IGV4dGVuc2lvbnMgPSBtYXJrZWQuZGVmYXVsdHMuZXh0ZW5zaW9ucyB8fCB7CiAgICByZW5kZXJlcnM6IHt9LAogICAgY2hpbGRUb2tlbnM6IHt9LAogIH0KICBsZXQgaGFzRXh0ZW5zaW9ucwoKICBhcmdzLmZvckVhY2gocGFjayA9PiB7CiAgICAvLyA9PS0tIFBhcnNlICJhZGRvbiIgZXh0ZW5zaW9ucyAtLT09IC8vCiAgICBpZiAocGFjay5leHRlbnNpb25zKSB7CiAgICAgIGhhc0V4dGVuc2lvbnMgPSB0cnVlCiAgICAgIHBhY2suZXh0ZW5zaW9ucy5mb3JFYWNoKGV4dCA9PiB7CiAgICAgICAgaWYgKCFleHQubmFtZSkgewogICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCJleHRlbnNpb24gbmFtZSByZXF1aXJlZCIpCiAgICAgICAgfQogICAgICAgIGlmIChleHQucmVuZGVyZXIpIHsKICAgICAgICAgIC8vIFJlbmRlcmVyIGV4dGVuc2lvbnMKICAgICAgICAgIGNvbnN0IHByZXZSZW5kZXJlciA9IGV4dGVuc2lvbnMucmVuZGVyZXJzCiAgICAgICAgICAgID8gZXh0ZW5zaW9ucy5yZW5kZXJlcnNbZXh0Lm5hbWVdCiAgICAgICAgICAgIDogbnVsbAogICAgICAgICAgaWYgKHByZXZSZW5kZXJlcikgewogICAgICAgICAgICAvLyBSZXBsYWNlIGV4dGVuc2lvbiB3aXRoIGZ1bmMgdG8gcnVuIG5ldyBleHRlbnNpb24gYnV0IGZhbGwgYmFjayBpZiBmYWxzZQogICAgICAgICAgICBleHRlbnNpb25zLnJlbmRlcmVyc1tleHQubmFtZV0gPSBmdW5jdGlvbiAoLi4uYXJncykgewogICAgICAgICAgICAgIGxldCByZXQgPSBleHQucmVuZGVyZXIuYXBwbHkodGhpcywgYXJncykKICAgICAgICAgICAgICBpZiAocmV0ID09PSBmYWxzZSkgewogICAgICAgICAgICAgICAgcmV0ID0gcHJldlJlbmRlcmVyLmFwcGx5KHRoaXMsIGFyZ3MpCiAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIHJldHVybiByZXQKICAgICAgICAgICAgfQogICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgZXh0ZW5zaW9ucy5yZW5kZXJlcnNbZXh0Lm5hbWVdID0gZXh0LnJlbmRlcmVyCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIGlmIChleHQudG9rZW5pemVyKSB7CiAgICAgICAgICAvLyBUb2tlbml6ZXIgRXh0ZW5zaW9ucwogICAgICAgICAgaWYgKCFleHQubGV2ZWwgfHwgKGV4dC5sZXZlbCAhPT0gImJsb2NrIiAmJiBleHQubGV2ZWwgIT09ICJpbmxpbmUiKSkgewogICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoImV4dGVuc2lvbiBsZXZlbCBtdXN0IGJlICdibG9jaycgb3IgJ2lubGluZSciKQogICAgICAgICAgfQogICAgICAgICAgaWYgKGV4dGVuc2lvbnNbZXh0LmxldmVsXSkgewogICAgICAgICAgICBleHRlbnNpb25zW2V4dC5sZXZlbF0udW5zaGlmdChleHQudG9rZW5pemVyKQogICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgZXh0ZW5zaW9uc1tleHQubGV2ZWxdID0gW2V4dC50b2tlbml6ZXJdCiAgICAgICAgICB9CiAgICAgICAgICBpZiAoZXh0LnN0YXJ0KSB7CiAgICAgICAgICAgIC8vIEZ1bmN0aW9uIHRvIGNoZWNrIGZvciBzdGFydCBvZiB0b2tlbgogICAgICAgICAgICBpZiAoZXh0LmxldmVsID09PSAiYmxvY2siKSB7CiAgICAgICAgICAgICAgaWYgKGV4dGVuc2lvbnMuc3RhcnRCbG9jaykgewogICAgICAgICAgICAgICAgZXh0ZW5zaW9ucy5zdGFydEJsb2NrLnB1c2goZXh0LnN0YXJ0KQogICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICBleHRlbnNpb25zLnN0YXJ0QmxvY2sgPSBbZXh0LnN0YXJ0XQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSBlbHNlIGlmIChleHQubGV2ZWwgPT09ICJpbmxpbmUiKSB7CiAgICAgICAgICAgICAgaWYgKGV4dGVuc2lvbnMuc3RhcnRJbmxpbmUpIHsKICAgICAgICAgICAgICAgIGV4dGVuc2lvbnMuc3RhcnRJbmxpbmUucHVzaChleHQuc3RhcnQpCiAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIGV4dGVuc2lvbnMuc3RhcnRJbmxpbmUgPSBbZXh0LnN0YXJ0XQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBpZiAoZXh0LmNoaWxkVG9rZW5zKSB7CiAgICAgICAgICAvLyBDaGlsZCB0b2tlbnMgdG8gYmUgdmlzaXRlZCBieSB3YWxrVG9rZW5zCiAgICAgICAgICBleHRlbnNpb25zLmNoaWxkVG9rZW5zW2V4dC5uYW1lXSA9IGV4dC5jaGlsZFRva2VucwogICAgICAgIH0KICAgICAgfSkKICAgIH0KCiAgICAvLyA9PS0tIFBhcnNlICJvdmVyd3JpdGUiIGV4dGVuc2lvbnMgLS09PSAvLwogICAgaWYgKHBhY2sucmVuZGVyZXIpIHsKICAgICAgY29uc3QgcmVuZGVyZXIgPSBtYXJrZWQuZGVmYXVsdHMucmVuZGVyZXIgfHwgbmV3IFJlbmRlcmVyKCkKICAgICAgZm9yIChjb25zdCBwcm9wIGluIHBhY2sucmVuZGVyZXIpIHsKICAgICAgICBjb25zdCBwcmV2UmVuZGVyZXIgPSByZW5kZXJlcltwcm9wXQogICAgICAgIC8vIFJlcGxhY2UgcmVuZGVyZXIgd2l0aCBmdW5jIHRvIHJ1biBleHRlbnNpb24sIGJ1dCBmYWxsIGJhY2sgaWYgZmFsc2UKICAgICAgICByZW5kZXJlcltwcm9wXSA9ICguLi5hcmdzKSA9PiB7CiAgICAgICAgICBsZXQgcmV0ID0gcGFjay5yZW5kZXJlcltwcm9wXS5hcHBseShyZW5kZXJlciwgYXJncykKICAgICAgICAgIGlmIChyZXQgPT09IGZhbHNlKSB7CiAgICAgICAgICAgIHJldCA9IHByZXZSZW5kZXJlci5hcHBseShyZW5kZXJlciwgYXJncykKICAgICAgICAgIH0KICAgICAgICAgIHJldHVybiByZXQKICAgICAgICB9CiAgICAgIH0KICAgICAgb3B0cy5yZW5kZXJlciA9IHJlbmRlcmVyCiAgICB9CiAgICBpZiAocGFjay50b2tlbml6ZXIpIHsKICAgICAgY29uc3QgdG9rZW5pemVyID0gbWFya2VkLmRlZmF1bHRzLnRva2VuaXplciB8fCBuZXcgVG9rZW5pemVyKCkKICAgICAgZm9yIChjb25zdCBwcm9wIGluIHBhY2sudG9rZW5pemVyKSB7CiAgICAgICAgY29uc3QgcHJldlRva2VuaXplciA9IHRva2VuaXplcltwcm9wXQogICAgICAgIC8vIFJlcGxhY2UgdG9rZW5pemVyIHdpdGggZnVuYyB0byBydW4gZXh0ZW5zaW9uLCBidXQgZmFsbCBiYWNrIGlmIGZhbHNlCiAgICAgICAgdG9rZW5pemVyW3Byb3BdID0gKC4uLmFyZ3MpID0+IHsKICAgICAgICAgIGxldCByZXQgPSBwYWNrLnRva2VuaXplcltwcm9wXS5hcHBseSh0b2tlbml6ZXIsIGFyZ3MpCiAgICAgICAgICBpZiAocmV0ID09PSBmYWxzZSkgewogICAgICAgICAgICByZXQgPSBwcmV2VG9rZW5pemVyLmFwcGx5KHRva2VuaXplciwgYXJncykKICAgICAgICAgIH0KICAgICAgICAgIHJldHVybiByZXQKICAgICAgICB9CiAgICAgIH0KICAgICAgb3B0cy50b2tlbml6ZXIgPSB0b2tlbml6ZXIKICAgIH0KCiAgICAvLyA9PS0tIFBhcnNlIFdhbGtUb2tlbnMgZXh0ZW5zaW9ucyAtLT09IC8vCiAgICBpZiAocGFjay53YWxrVG9rZW5zKSB7CiAgICAgIGNvbnN0IHdhbGtUb2tlbnMgPSBtYXJrZWQuZGVmYXVsdHMud2Fsa1Rva2VucwogICAgICBvcHRzLndhbGtUb2tlbnMgPSBmdW5jdGlvbiAodG9rZW4pIHsKICAgICAgICBwYWNrLndhbGtUb2tlbnMuY2FsbCh0aGlzLCB0b2tlbikKICAgICAgICBpZiAod2Fsa1Rva2VucykgewogICAgICAgICAgd2Fsa1Rva2Vucy5jYWxsKHRoaXMsIHRva2VuKQogICAgICAgIH0KICAgICAgfQogICAgfQoKICAgIGlmIChoYXNFeHRlbnNpb25zKSB7CiAgICAgIG9wdHMuZXh0ZW5zaW9ucyA9IGV4dGVuc2lvbnMKICAgIH0KCiAgICBtYXJrZWQuc2V0T3B0aW9ucyhvcHRzKQogIH0pCn0KCi8qKgogKiBSdW4gY2FsbGJhY2sgZm9yIGV2ZXJ5IHRva2VuCiAqLwoKbWFya2VkLndhbGtUb2tlbnMgPSBmdW5jdGlvbiAodG9rZW5zLCBjYWxsYmFjaykgewogIGZvciAoY29uc3QgdG9rZW4gb2YgdG9rZW5zKSB7CiAgICBjYWxsYmFjay5jYWxsKG1hcmtlZCwgdG9rZW4pCiAgICBzd2l0Y2ggKHRva2VuLnR5cGUpIHsKICAgICAgY2FzZSAidGFibGUiOiB7CiAgICAgICAgZm9yIChjb25zdCBjZWxsIG9mIHRva2VuLmhlYWRlcikgewogICAgICAgICAgbWFya2VkLndhbGtUb2tlbnMoY2VsbC50b2tlbnMsIGNhbGxiYWNrKQogICAgICAgIH0KICAgICAgICBmb3IgKGNvbnN0IHJvdyBvZiB0b2tlbi5yb3dzKSB7CiAgICAgICAgICBmb3IgKGNvbnN0IGNlbGwgb2Ygcm93KSB7CiAgICAgICAgICAgIG1hcmtlZC53YWxrVG9rZW5zKGNlbGwudG9rZW5zLCBjYWxsYmFjaykKICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgYnJlYWsKICAgICAgfQogICAgICBjYXNlICJsaXN0IjogewogICAgICAgIG1hcmtlZC53YWxrVG9rZW5zKHRva2VuLml0ZW1zLCBjYWxsYmFjaykKICAgICAgICBicmVhawogICAgICB9CiAgICAgIGRlZmF1bHQ6IHsKICAgICAgICBpZiAoCiAgICAgICAgICBtYXJrZWQuZGVmYXVsdHMuZXh0ZW5zaW9ucyAmJgogICAgICAgICAgbWFya2VkLmRlZmF1bHRzLmV4dGVuc2lvbnMuY2hpbGRUb2tlbnMgJiYKICAgICAgICAgIG1hcmtlZC5kZWZhdWx0cy5leHRlbnNpb25zLmNoaWxkVG9rZW5zW3Rva2VuLnR5cGVdCiAgICAgICAgKSB7CiAgICAgICAgICAvLyBXYWxrIGFueSBleHRlbnNpb25zCiAgICAgICAgICBtYXJrZWQuZGVmYXVsdHMuZXh0ZW5zaW9ucy5jaGlsZFRva2Vuc1t0b2tlbi50eXBlXS5mb3JFYWNoKGZ1bmN0aW9uICgKICAgICAgICAgICAgY2hpbGRUb2tlbnMKICAgICAgICAgICkgewogICAgICAgICAgICBtYXJrZWQud2Fsa1Rva2Vucyh0b2tlbltjaGlsZFRva2Vuc10sIGNhbGxiYWNrKQogICAgICAgICAgfSkKICAgICAgICB9IGVsc2UgaWYgKHRva2VuLnRva2VucykgewogICAgICAgICAgbWFya2VkLndhbGtUb2tlbnModG9rZW4udG9rZW5zLCBjYWxsYmFjaykKICAgICAgICB9CiAgICAgIH0KICAgIH0KICB9Cn0KCi8qKgogKiBQYXJzZSBJbmxpbmUKICogQHBhcmFtIHtzdHJpbmd9IHNyYwogKi8KbWFya2VkLnBhcnNlSW5saW5lID0gZnVuY3Rpb24gKHNyYywgb3B0KSB7CiAgLy8gdGhyb3cgZXJyb3IgaW4gY2FzZSBvZiBub24gc3RyaW5nIGlucHV0CiAgaWYgKHR5cGVvZiBzcmMgPT09ICJ1bmRlZmluZWQiIHx8IHNyYyA9PT0gbnVsbCkgewogICAgdGhyb3cgbmV3IEVycm9yKAogICAgICAibWFya2VkLnBhcnNlSW5saW5lKCk6IGlucHV0IHBhcmFtZXRlciBpcyB1bmRlZmluZWQgb3IgbnVsbCIKICAgICkKICB9CiAgaWYgKHR5cGVvZiBzcmMgIT09ICJzdHJpbmciKSB7CiAgICB0aHJvdyBuZXcgRXJyb3IoCiAgICAgICJtYXJrZWQucGFyc2VJbmxpbmUoKTogaW5wdXQgcGFyYW1ldGVyIGlzIG9mIHR5cGUgIiArCiAgICAgICAgT2JqZWN0LnByb3RvdHlwZS50b1N0cmluZy5jYWxsKHNyYykgKwogICAgICAgICIsIHN0cmluZyBleHBlY3RlZCIKICAgICkKICB9CgogIG9wdCA9IG1lcmdlKHt9LCBtYXJrZWQuZGVmYXVsdHMsIG9wdCB8fCB7fSkKICBjaGVja1Nhbml0aXplRGVwcmVjYXRpb24ob3B0KQoKICB0cnkgewogICAgY29uc3QgdG9rZW5zID0gTGV4ZXIubGV4SW5saW5lKHNyYywgb3B0KQogICAgaWYgKG9wdC53YWxrVG9rZW5zKSB7CiAgICAgIG1hcmtlZC53YWxrVG9rZW5zKHRva2Vucywgb3B0LndhbGtUb2tlbnMpCiAgICB9CiAgICByZXR1cm4gUGFyc2VyLnBhcnNlSW5saW5lKHRva2Vucywgb3B0KQogIH0gY2F0Y2ggKGUpIHsKICAgIGUubWVzc2FnZSArPSAiXG5QbGVhc2UgcmVwb3J0IHRoaXMgdG8gaHR0cHM6Ly9naXRodWIuY29tL21hcmtlZGpzL21hcmtlZC4iCiAgICBpZiAob3B0LnNpbGVudCkgewogICAgICByZXR1cm4gKAogICAgICAgICI8cD5BbiBlcnJvciBvY2N1cnJlZDo8L3A+PHByZT4iICsKICAgICAgICBlc2NhcGUoZS5tZXNzYWdlICsgIiIsIHRydWUpICsKICAgICAgICAiPC9wcmU+IgogICAgICApCiAgICB9CiAgICB0aHJvdyBlCiAgfQp9CgovKioKICogRXhwb3NlCiAqLwptYXJrZWQuUGFyc2VyID0gUGFyc2VyCm1hcmtlZC5wYXJzZXIgPSBQYXJzZXIucGFyc2UKbWFya2VkLlJlbmRlcmVyID0gUmVuZGVyZXIKbWFya2VkLlRleHRSZW5kZXJlciA9IFRleHRSZW5kZXJlcgptYXJrZWQuTGV4ZXIgPSBMZXhlcgptYXJrZWQubGV4ZXIgPSBMZXhlci5sZXgKbWFya2VkLlRva2VuaXplciA9IFRva2VuaXplcgptYXJrZWQuU2x1Z2dlciA9IFNsdWdnZXIKbWFya2VkLnBhcnNlID0gbWFya2VkCgpjb25zdCBvcHRpb25zID0gbWFya2VkLm9wdGlvbnMKY29uc3Qgc2V0T3B0aW9ucyA9IG1hcmtlZC5zZXRPcHRpb25zCmNvbnN0IHVzZSA9IG1hcmtlZC51c2UKY29uc3Qgd2Fsa1Rva2VucyA9IG1hcmtlZC53YWxrVG9rZW5zCmNvbnN0IHBhcnNlSW5saW5lID0gbWFya2VkLnBhcnNlSW5saW5lCmNvbnN0IHBhcnNlID0gbWFya2VkCmNvbnN0IHBhcnNlciA9IFBhcnNlci5wYXJzZQpjb25zdCBsZXhlciA9IExleGVyLmxleAoKY29uc3QgZW1haWwgPSB0cmlnZ2VyLnJvdwpyZXR1cm4gbWFya2VkKGVtYWlsLk1lc3NhZ2Up` +module.exports.CrazyLongSnippet = + '/**\n * marked - a markdown parser\n * Copyright (c) 2011-2022, Christopher Jeffrey. (MIT Licensed)\n * https://github.com/markedjs/marked\n */\n\n/**\n * DO NOT EDIT THIS FILE\n * The code in this file is generated from files in ./src/\n */\n\nfunction getDefaults() {\n return {\n baseUrl: null,\n breaks: false,\n extensions: null,\n gfm: true,\n headerIds: true,\n headerPrefix: "",\n highlight: null,\n langPrefix: "language-",\n mangle: true,\n pedantic: false,\n renderer: null,\n sanitize: false,\n sanitizer: null,\n silent: false,\n smartLists: false,\n smartypants: false,\n tokenizer: null,\n walkTokens: null,\n xhtml: false,\n }\n}\n\nlet defaults = getDefaults()\n\nfunction changeDefaults(newDefaults) {\n defaults = newDefaults\n}\n\n/**\n * Helpers\n */\nconst escapeTest = /[&<>"\']/\nconst escapeReplace = /[&<>"\']/g\nconst escapeTestNoEncode = /[<>"\']|&(?!#?\\w+;)/\nconst escapeReplaceNoEncode = /[<>"\']|&(?!#?\\w+;)/g\nconst escapeReplacements = {\n "&": "&",\n "<": "<",\n ">": ">",\n \'"\': """,\n "\'": "'",\n}\nconst getEscapeReplacement = ch => escapeReplacements[ch]\nfunction escape(html, encode) {\n if (encode) {\n if (escapeTest.test(html)) {\n return html.replace(escapeReplace, getEscapeReplacement)\n }\n } else {\n if (escapeTestNoEncode.test(html)) {\n return html.replace(escapeReplaceNoEncode, getEscapeReplacement)\n }\n }\n\n return html\n}\n\nconst unescapeTest = /&(#(?:\\d+)|(?:#x[0-9A-Fa-f]+)|(?:\\w+));?/gi\n\n/**\n * @param {string} html\n */\nfunction unescape(html) {\n // explicitly match decimal, hex, and named HTML entities\n return html.replace(unescapeTest, (_, n) => {\n n = n.toLowerCase()\n if (n === "colon") return ":"\n if (n.charAt(0) === "#") {\n return n.charAt(1) === "x"\n ? String.fromCharCode(parseInt(n.substring(2), 16))\n : String.fromCharCode(+n.substring(1))\n }\n return ""\n })\n}\n\nconst caret = /(^|[^\\[])\\^/g\n\n/**\n * @param {string | RegExp} regex\n * @param {string} opt\n */\nfunction edit(regex, opt) {\n regex = typeof regex === "string" ? regex : regex.source\n opt = opt || ""\n const obj = {\n replace: (name, val) => {\n val = val.source || val\n val = val.replace(caret, "$1")\n regex = regex.replace(name, val)\n return obj\n },\n getRegex: () => {\n return new RegExp(regex, opt)\n },\n }\n return obj\n}\n\nconst nonWordAndColonTest = /[^\\w:]/g\nconst originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i\n\n/**\n * @param {boolean} sanitize\n * @param {string} base\n * @param {string} href\n */\nfunction cleanUrl(sanitize, base, href) {\n if (sanitize) {\n let prot\n try {\n prot = decodeURIComponent(unescape(href))\n .replace(nonWordAndColonTest, "")\n .toLowerCase()\n } catch (e) {\n return null\n }\n if (\n prot.indexOf("javascript:") === 0 ||\n prot.indexOf("vbscript:") === 0 ||\n prot.indexOf("data:") === 0\n ) {\n return null\n }\n }\n if (base && !originIndependentUrl.test(href)) {\n href = resolveUrl(base, href)\n }\n try {\n href = encodeURI(href).replace(/%25/g, "%")\n } catch (e) {\n return null\n }\n return href\n}\n\nconst baseUrls = {}\nconst justDomain = /^[^:]+:\\/*[^/]*$/\nconst protocol = /^([^:]+:)[\\s\\S]*$/\nconst domain = /^([^:]+:\\/*[^/]*)[\\s\\S]*$/\n\n/**\n * @param {string} base\n * @param {string} href\n */\nfunction resolveUrl(base, href) {\n if (!baseUrls[" " + base]) {\n // we can ignore everything in base after the last slash of its path component,\n // but we might need to add _that_\n // https://tools.ietf.org/html/rfc3986#section-3\n if (justDomain.test(base)) {\n baseUrls[" " + base] = base + "/"\n } else {\n baseUrls[" " + base] = rtrim(base, "/", true)\n }\n }\n base = baseUrls[" " + base]\n const relativeBase = base.indexOf(":") === -1\n\n if (href.substring(0, 2) === "//") {\n if (relativeBase) {\n return href\n }\n return base.replace(protocol, "$1") + href\n } else if (href.charAt(0) === "/") {\n if (relativeBase) {\n return href\n }\n return base.replace(domain, "$1") + href\n } else {\n return base + href\n }\n}\n\nconst noopTest = { exec: function noopTest() {} }\n\nfunction merge(obj) {\n let i = 1,\n target,\n key\n\n for (; i < arguments.length; i++) {\n target = arguments[i]\n for (key in target) {\n if (Object.prototype.hasOwnProperty.call(target, key)) {\n obj[key] = target[key]\n }\n }\n }\n\n return obj\n}\n\nfunction splitCells(tableRow, count) {\n // ensure that every cell-delimiting pipe has a space\n // before it to distinguish it from an escaped pipe\n const row = tableRow.replace(/\\|/g, (match, offset, str) => {\n let escaped = false,\n curr = offset\n while (--curr >= 0 && str[curr] === "\\\\") escaped = !escaped\n if (escaped) {\n // odd number of slashes means | is escaped\n // so we leave it alone\n return "|"\n } else {\n // add space before unescaped |\n return " |"\n }\n }),\n cells = row.split(/ \\|/)\n let i = 0\n\n // First/last cell in a row cannot be empty if it has no leading/trailing pipe\n if (!cells[0].trim()) {\n cells.shift()\n }\n if (cells.length > 0 && !cells[cells.length - 1].trim()) {\n cells.pop()\n }\n\n if (cells.length > count) {\n cells.splice(count)\n } else {\n while (cells.length < count) cells.push("")\n }\n\n for (; i < cells.length; i++) {\n // leading or trailing whitespace is ignored per the gfm spec\n cells[i] = cells[i].trim().replace(/\\\\\\|/g, "|")\n }\n return cells\n}\n\n/**\n * Remove trailing \'c\'s. Equivalent to str.replace(/c*$/, \'\').\n * /c*$/ is vulnerable to REDOS.\n *\n * @param {string} str\n * @param {string} c\n * @param {boolean} invert Remove suffix of non-c chars instead. Default falsey.\n */\nfunction rtrim(str, c, invert) {\n const l = str.length\n if (l === 0) {\n return ""\n }\n\n // Length of suffix matching the invert condition.\n let suffLen = 0\n\n // Step left until we fail to match the invert condition.\n while (suffLen < l) {\n const currChar = str.charAt(l - suffLen - 1)\n if (currChar === c && !invert) {\n suffLen++\n } else if (currChar !== c && invert) {\n suffLen++\n } else {\n break\n }\n }\n\n return str.slice(0, l - suffLen)\n}\n\nfunction findClosingBracket(str, b) {\n if (str.indexOf(b[1]) === -1) {\n return -1\n }\n const l = str.length\n let level = 0,\n i = 0\n for (; i < l; i++) {\n if (str[i] === "\\\\") {\n i++\n } else if (str[i] === b[0]) {\n level++\n } else if (str[i] === b[1]) {\n level--\n if (level < 0) {\n return i\n }\n }\n }\n return -1\n}\n\nfunction checkSanitizeDeprecation(opt) {\n if (opt && opt.sanitize && !opt.silent) {\n console.warn(\n "marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options"\n )\n }\n}\n\n// copied from https://stackoverflow.com/a/5450113/806777\n/**\n * @param {string} pattern\n * @param {number} count\n */\nfunction repeatString(pattern, count) {\n if (count < 1) {\n return ""\n }\n let result = ""\n while (count > 1) {\n if (count & 1) {\n result += pattern\n }\n count >>= 1\n pattern += pattern\n }\n return result + pattern\n}\n\nfunction outputLink(cap, link, raw, lexer) {\n const href = link.href\n const title = link.title ? escape(link.title) : null\n const text = cap[1].replace(/\\\\([\\[\\]])/g, "$1")\n\n if (cap[0].charAt(0) !== "!") {\n lexer.state.inLink = true\n const token = {\n type: "link",\n raw,\n href,\n title,\n text,\n tokens: lexer.inlineTokens(text, []),\n }\n lexer.state.inLink = false\n return token\n }\n return {\n type: "image",\n raw,\n href,\n title,\n text: escape(text),\n }\n}\n\nfunction indentCodeCompensation(raw, text) {\n const matchIndentToCode = raw.match(/^(\\s+)(?:```)/)\n\n if (matchIndentToCode === null) {\n return text\n }\n\n const indentToCode = matchIndentToCode[1]\n\n return text\n .split("\\n")\n .map(node => {\n const matchIndentInNode = node.match(/^\\s+/)\n if (matchIndentInNode === null) {\n return node\n }\n\n const [indentInNode] = matchIndentInNode\n\n if (indentInNode.length >= indentToCode.length) {\n return node.slice(indentToCode.length)\n }\n\n return node\n })\n .join("\\n")\n}\n\n/**\n * Tokenizer\n */\nclass Tokenizer {\n constructor(options) {\n this.options = options || defaults\n }\n\n space(src) {\n const cap = this.rules.block.newline.exec(src)\n if (cap && cap[0].length > 0) {\n return {\n type: "space",\n raw: cap[0],\n }\n }\n }\n\n code(src) {\n const cap = this.rules.block.code.exec(src)\n if (cap) {\n const text = cap[0].replace(/^ {1,4}/gm, "")\n return {\n type: "code",\n raw: cap[0],\n codeBlockStyle: "indented",\n text: !this.options.pedantic ? rtrim(text, "\\n") : text,\n }\n }\n }\n\n fences(src) {\n const cap = this.rules.block.fences.exec(src)\n if (cap) {\n const raw = cap[0]\n const text = indentCodeCompensation(raw, cap[3] || "")\n\n return {\n type: "code",\n raw,\n lang: cap[2] ? cap[2].trim() : cap[2],\n text,\n }\n }\n }\n\n heading(src) {\n const cap = this.rules.block.heading.exec(src)\n if (cap) {\n let text = cap[2].trim()\n\n // remove trailing #s\n if (/#$/.test(text)) {\n const trimmed = rtrim(text, "#")\n if (this.options.pedantic) {\n text = trimmed.trim()\n } else if (!trimmed || / $/.test(trimmed)) {\n // CommonMark requires space before trailing #s\n text = trimmed.trim()\n }\n }\n\n const token = {\n type: "heading",\n raw: cap[0],\n depth: cap[1].length,\n text,\n tokens: [],\n }\n this.lexer.inline(token.text, token.tokens)\n return token\n }\n }\n\n hr(src) {\n const cap = this.rules.block.hr.exec(src)\n if (cap) {\n return {\n type: "hr",\n raw: cap[0],\n }\n }\n }\n\n blockquote(src) {\n const cap = this.rules.block.blockquote.exec(src)\n if (cap) {\n const text = cap[0].replace(/^ *>[ \\t]?/gm, "")\n\n return {\n type: "blockquote",\n raw: cap[0],\n tokens: this.lexer.blockTokens(text, []),\n text,\n }\n }\n }\n\n list(src) {\n let cap = this.rules.block.list.exec(src)\n if (cap) {\n let raw,\n istask,\n ischecked,\n indent,\n i,\n blankLine,\n endsWithBlankLine,\n line,\n nextLine,\n rawLine,\n itemContents,\n endEarly\n\n let bull = cap[1].trim()\n const isordered = bull.length > 1\n\n const list = {\n type: "list",\n raw: "",\n ordered: isordered,\n start: isordered ? +bull.slice(0, -1) : "",\n loose: false,\n items: [],\n }\n\n bull = isordered ? `\\\\d{1,9}\\\\${bull.slice(-1)}` : `\\\\${bull}`\n\n if (this.options.pedantic) {\n bull = isordered ? bull : "[*+-]"\n }\n\n // Get next list item\n const itemRegex = new RegExp(\n `^( {0,3}${bull})((?:[\\t ][^\\\\n]*)?(?:\\\\n|$))`\n )\n\n // Check if current bullet point can start a new List Item\n while (src) {\n endEarly = false\n if (!(cap = itemRegex.exec(src))) {\n break\n }\n\n if (this.rules.block.hr.test(src)) {\n // End list if bullet was actually HR (possibly move into itemRegex?)\n break\n }\n\n raw = cap[0]\n src = src.substring(raw.length)\n\n line = cap[2].split("\\n", 1)[0]\n nextLine = src.split("\\n", 1)[0]\n\n if (this.options.pedantic) {\n indent = 2\n itemContents = line.trimLeft()\n } else {\n indent = cap[2].search(/[^ ]/) // Find first non-space char\n indent = indent > 4 ? 1 : indent // Treat indented code blocks (> 4 spaces) as having only 1 indent\n itemContents = line.slice(indent)\n indent += cap[1].length\n }\n\n blankLine = false\n\n if (!line && /^ *$/.test(nextLine)) {\n // Items begin with at most one blank line\n raw += nextLine + "\\n"\n src = src.substring(nextLine.length + 1)\n endEarly = true\n }\n\n if (!endEarly) {\n const nextBulletRegex = new RegExp(\n `^ {0,${Math.min(\n 3,\n indent - 1\n )}}(?:[*+-]|\\\\d{1,9}[.)])((?: [^\\\\n]*)?(?:\\\\n|$))`\n )\n const hrRegex = new RegExp(\n `^ {0,${Math.min(\n 3,\n indent - 1\n )}}((?:- *){3,}|(?:_ *){3,}|(?:\\\\* *){3,})(?:\\\\n+|$)`\n )\n\n // Check if following lines should be included in List Item\n while (src) {\n rawLine = src.split("\\n", 1)[0]\n line = rawLine\n\n // Re-align to follow commonmark nesting rules\n if (this.options.pedantic) {\n line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, " ")\n }\n\n // End list item if found start of new bullet\n if (nextBulletRegex.test(line)) {\n break\n }\n\n // Horizontal rule found\n if (hrRegex.test(src)) {\n break\n }\n\n if (line.search(/[^ ]/) >= indent || !line.trim()) {\n // Dedent if possible\n itemContents += "\\n" + line.slice(indent)\n } else if (!blankLine) {\n // Until blank line, item doesn\'t need indentation\n itemContents += "\\n" + line\n } else {\n // Otherwise, improper indentation ends this item\n break\n }\n\n if (!blankLine && !line.trim()) {\n // Check if current line is blank\n blankLine = true\n }\n\n raw += rawLine + "\\n"\n src = src.substring(rawLine.length + 1)\n }\n }\n\n if (!list.loose) {\n // If the previous item ended with a blank line, the list is loose\n if (endsWithBlankLine) {\n list.loose = true\n } else if (/\\n *\\n *$/.test(raw)) {\n endsWithBlankLine = true\n }\n }\n\n // Check for task list items\n if (this.options.gfm) {\n istask = /^\\[[ xX]\\] /.exec(itemContents)\n if (istask) {\n ischecked = istask[0] !== "[ ] "\n itemContents = itemContents.replace(/^\\[[ xX]\\] +/, "")\n }\n }\n\n list.items.push({\n type: "list_item",\n raw,\n task: !!istask,\n checked: ischecked,\n loose: false,\n text: itemContents,\n })\n\n list.raw += raw\n }\n\n // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic\n list.items[list.items.length - 1].raw = raw.trimRight()\n list.items[list.items.length - 1].text = itemContents.trimRight()\n list.raw = list.raw.trimRight()\n\n const l = list.items.length\n\n // Item child tokens handled here at end because we needed to have the final item to trim it first\n for (i = 0; i < l; i++) {\n this.lexer.state.top = false\n list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, [])\n const spacers = list.items[i].tokens.filter(t => t.type === "space")\n const hasMultipleLineBreaks = spacers.every(t => {\n const chars = t.raw.split("")\n let lineBreaks = 0\n for (const char of chars) {\n if (char === "\\n") {\n lineBreaks += 1\n }\n if (lineBreaks > 1) {\n return true\n }\n }\n\n return false\n })\n\n if (!list.loose && spacers.length && hasMultipleLineBreaks) {\n // Having a single line break doesn\'t mean a list is loose. A single line break is terminating the last list item\n list.loose = true\n list.items[i].loose = true\n }\n }\n\n return list\n }\n }\n\n html(src) {\n const cap = this.rules.block.html.exec(src)\n if (cap) {\n const token = {\n type: "html",\n raw: cap[0],\n pre:\n !this.options.sanitizer &&\n (cap[1] === "pre" || cap[1] === "script" || cap[1] === "style"),\n text: cap[0],\n }\n if (this.options.sanitize) {\n token.type = "paragraph"\n token.text = this.options.sanitizer\n ? this.options.sanitizer(cap[0])\n : escape(cap[0])\n token.tokens = []\n this.lexer.inline(token.text, token.tokens)\n }\n return token\n }\n }\n\n def(src) {\n const cap = this.rules.block.def.exec(src)\n if (cap) {\n if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1)\n const tag = cap[1].toLowerCase().replace(/\\s+/g, " ")\n return {\n type: "def",\n tag,\n raw: cap[0],\n href: cap[2],\n title: cap[3],\n }\n }\n }\n\n table(src) {\n const cap = this.rules.block.table.exec(src)\n if (cap) {\n const item = {\n type: "table",\n header: splitCells(cap[1]).map(c => {\n return { text: c }\n }),\n align: cap[2].replace(/^ *|\\| *$/g, "").split(/ *\\| */),\n rows:\n cap[3] && cap[3].trim()\n ? cap[3].replace(/\\n[ \\t]*$/, "").split("\\n")\n : [],\n }\n\n if (item.header.length === item.align.length) {\n item.raw = cap[0]\n\n let l = item.align.length\n let i, j, k, row\n for (i = 0; i < l; i++) {\n if (/^ *-+: *$/.test(item.align[i])) {\n item.align[i] = "right"\n } else if (/^ *:-+: *$/.test(item.align[i])) {\n item.align[i] = "center"\n } else if (/^ *:-+ *$/.test(item.align[i])) {\n item.align[i] = "left"\n } else {\n item.align[i] = null\n }\n }\n\n l = item.rows.length\n for (i = 0; i < l; i++) {\n item.rows[i] = splitCells(item.rows[i], item.header.length).map(c => {\n return { text: c }\n })\n }\n\n // parse child tokens inside headers and cells\n\n // header child tokens\n l = item.header.length\n for (j = 0; j < l; j++) {\n item.header[j].tokens = []\n this.lexer.inline(item.header[j].text, item.header[j].tokens)\n }\n\n // cell child tokens\n l = item.rows.length\n for (j = 0; j < l; j++) {\n row = item.rows[j]\n for (k = 0; k < row.length; k++) {\n row[k].tokens = []\n this.lexer.inline(row[k].text, row[k].tokens)\n }\n }\n\n return item\n }\n }\n }\n\n lheading(src) {\n const cap = this.rules.block.lheading.exec(src)\n if (cap) {\n const token = {\n type: "heading",\n raw: cap[0],\n depth: cap[2].charAt(0) === "=" ? 1 : 2,\n text: cap[1],\n tokens: [],\n }\n this.lexer.inline(token.text, token.tokens)\n return token\n }\n }\n\n paragraph(src) {\n const cap = this.rules.block.paragraph.exec(src)\n if (cap) {\n const token = {\n type: "paragraph",\n raw: cap[0],\n text:\n cap[1].charAt(cap[1].length - 1) === "\\n"\n ? cap[1].slice(0, -1)\n : cap[1],\n tokens: [],\n }\n this.lexer.inline(token.text, token.tokens)\n return token\n }\n }\n\n text(src) {\n const cap = this.rules.block.text.exec(src)\n if (cap) {\n const token = {\n type: "text",\n raw: cap[0],\n text: cap[0],\n tokens: [],\n }\n this.lexer.inline(token.text, token.tokens)\n return token\n }\n }\n\n escape(src) {\n const cap = this.rules.inline.escape.exec(src)\n if (cap) {\n return {\n type: "escape",\n raw: cap[0],\n text: escape(cap[1]),\n }\n }\n }\n\n tag(src) {\n const cap = this.rules.inline.tag.exec(src)\n if (cap) {\n if (!this.lexer.state.inLink && /^/i.test(cap[0])) {\n this.lexer.state.inLink = false\n }\n if (\n !this.lexer.state.inRawBlock &&\n /^<(pre|code|kbd|script)(\\s|>)/i.test(cap[0])\n ) {\n this.lexer.state.inRawBlock = true\n } else if (\n this.lexer.state.inRawBlock &&\n /^<\\/(pre|code|kbd|script)(\\s|>)/i.test(cap[0])\n ) {\n this.lexer.state.inRawBlock = false\n }\n\n return {\n type: this.options.sanitize ? "text" : "html",\n raw: cap[0],\n inLink: this.lexer.state.inLink,\n inRawBlock: this.lexer.state.inRawBlock,\n text: this.options.sanitize\n ? this.options.sanitizer\n ? this.options.sanitizer(cap[0])\n : escape(cap[0])\n : cap[0],\n }\n }\n }\n\n link(src) {\n const cap = this.rules.inline.link.exec(src)\n if (cap) {\n const trimmedUrl = cap[2].trim()\n if (!this.options.pedantic && /^$/.test(trimmedUrl)) {\n return\n }\n\n // ending angle bracket cannot be escaped\n const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), "\\\\")\n if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {\n return\n }\n } else {\n // find closing parenthesis\n const lastParenIndex = findClosingBracket(cap[2], "()")\n if (lastParenIndex > -1) {\n const start = cap[0].indexOf("!") === 0 ? 5 : 4\n const linkLen = start + cap[1].length + lastParenIndex\n cap[2] = cap[2].substring(0, lastParenIndex)\n cap[0] = cap[0].substring(0, linkLen).trim()\n cap[3] = ""\n }\n }\n let href = cap[2]\n let title = ""\n if (this.options.pedantic) {\n // split pedantic href and title\n const link = /^([^\'"]*[^\\s])\\s+([\'"])(.*)\\2/.exec(href)\n\n if (link) {\n href = link[1]\n title = link[3]\n }\n } else {\n title = cap[3] ? cap[3].slice(1, -1) : ""\n }\n\n href = href.trim()\n if (/^$/.test(trimmedUrl)) {\n // pedantic allows starting angle bracket without ending angle bracket\n href = href.slice(1)\n } else {\n href = href.slice(1, -1)\n }\n }\n return outputLink(\n cap,\n {\n href: href ? href.replace(this.rules.inline._escapes, "$1") : href,\n title: title\n ? title.replace(this.rules.inline._escapes, "$1")\n : title,\n },\n cap[0],\n this.lexer\n )\n }\n }\n\n reflink(src, links) {\n let cap\n if (\n (cap = this.rules.inline.reflink.exec(src)) ||\n (cap = this.rules.inline.nolink.exec(src))\n ) {\n let link = (cap[2] || cap[1]).replace(/\\s+/g, " ")\n link = links[link.toLowerCase()]\n if (!link || !link.href) {\n const text = cap[0].charAt(0)\n return {\n type: "text",\n raw: text,\n text,\n }\n }\n return outputLink(cap, link, cap[0], this.lexer)\n }\n }\n\n emStrong(src, maskedSrc, prevChar = "") {\n let match = this.rules.inline.emStrong.lDelim.exec(src)\n if (!match) return\n\n // _ can\'t be between two alphanumerics. \\p{L}\\p{N} includes non-english alphabet/numbers as well\n if (match[3] && prevChar.match(/[\\p{L}\\p{N}]/u)) return\n\n const nextChar = match[1] || match[2] || ""\n\n if (\n !nextChar ||\n (nextChar &&\n (prevChar === "" || this.rules.inline.punctuation.exec(prevChar)))\n ) {\n const lLength = match[0].length - 1\n let rDelim,\n rLength,\n delimTotal = lLength,\n midDelimTotal = 0\n\n const endReg =\n match[0][0] === "*"\n ? this.rules.inline.emStrong.rDelimAst\n : this.rules.inline.emStrong.rDelimUnd\n endReg.lastIndex = 0\n\n // Clip maskedSrc to same section of string as src (move to lexer?)\n maskedSrc = maskedSrc.slice(-1 * src.length + lLength)\n\n while ((match = endReg.exec(maskedSrc)) != null) {\n rDelim =\n match[1] || match[2] || match[3] || match[4] || match[5] || match[6]\n\n if (!rDelim) continue // skip single * in __abc*abc__\n\n rLength = rDelim.length\n\n if (match[3] || match[4]) {\n // found another Left Delim\n delimTotal += rLength\n continue\n } else if (match[5] || match[6]) {\n // either Left or Right Delim\n if (lLength % 3 && !((lLength + rLength) % 3)) {\n midDelimTotal += rLength\n continue // CommonMark Emphasis Rules 9-10\n }\n }\n\n delimTotal -= rLength\n\n if (delimTotal > 0) continue // Haven\'t found enough closing delimiters\n\n // Remove extra characters. *a*** -> *a*\n rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal)\n\n // Create `em` if smallest delimiter has odd char count. *a***\n if (Math.min(lLength, rLength) % 2) {\n const text = src.slice(1, lLength + match.index + rLength)\n return {\n type: "em",\n raw: src.slice(0, lLength + match.index + rLength + 1),\n text,\n tokens: this.lexer.inlineTokens(text, []),\n }\n }\n\n // Create \'strong\' if smallest delimiter has even char count. **a***\n const text = src.slice(2, lLength + match.index + rLength - 1)\n return {\n type: "strong",\n raw: src.slice(0, lLength + match.index + rLength + 1),\n text,\n tokens: this.lexer.inlineTokens(text, []),\n }\n }\n }\n }\n\n codespan(src) {\n const cap = this.rules.inline.code.exec(src)\n if (cap) {\n let text = cap[2].replace(/\\n/g, " ")\n const hasNonSpaceChars = /[^ ]/.test(text)\n const hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text)\n if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {\n text = text.substring(1, text.length - 1)\n }\n text = escape(text, true)\n return {\n type: "codespan",\n raw: cap[0],\n text,\n }\n }\n }\n\n br(src) {\n const cap = this.rules.inline.br.exec(src)\n if (cap) {\n return {\n type: "br",\n raw: cap[0],\n }\n }\n }\n\n del(src) {\n const cap = this.rules.inline.del.exec(src)\n if (cap) {\n return {\n type: "del",\n raw: cap[0],\n text: cap[2],\n tokens: this.lexer.inlineTokens(cap[2], []),\n }\n }\n }\n\n autolink(src, mangle) {\n const cap = this.rules.inline.autolink.exec(src)\n if (cap) {\n let text, href\n if (cap[2] === "@") {\n text = escape(this.options.mangle ? mangle(cap[1]) : cap[1])\n href = "mailto:" + text\n } else {\n text = escape(cap[1])\n href = text\n }\n\n return {\n type: "link",\n raw: cap[0],\n text,\n href,\n tokens: [\n {\n type: "text",\n raw: text,\n text,\n },\n ],\n }\n }\n }\n\n url(src, mangle) {\n let cap\n if ((cap = this.rules.inline.url.exec(src))) {\n let text, href\n if (cap[2] === "@") {\n text = escape(this.options.mangle ? mangle(cap[0]) : cap[0])\n href = "mailto:" + text\n } else {\n // do extended autolink path validation\n let prevCapZero\n do {\n prevCapZero = cap[0]\n cap[0] = this.rules.inline._backpedal.exec(cap[0])[0]\n } while (prevCapZero !== cap[0])\n text = escape(cap[0])\n if (cap[1] === "www.") {\n href = "http://" + text\n } else {\n href = text\n }\n }\n return {\n type: "link",\n raw: cap[0],\n text,\n href,\n tokens: [\n {\n type: "text",\n raw: text,\n text,\n },\n ],\n }\n }\n }\n\n inlineText(src, smartypants) {\n const cap = this.rules.inline.text.exec(src)\n if (cap) {\n let text\n if (this.lexer.state.inRawBlock) {\n text = this.options.sanitize\n ? this.options.sanitizer\n ? this.options.sanitizer(cap[0])\n : escape(cap[0])\n : cap[0]\n } else {\n text = escape(this.options.smartypants ? smartypants(cap[0]) : cap[0])\n }\n return {\n type: "text",\n raw: cap[0],\n text,\n }\n }\n }\n}\n\n/**\n * Block-Level Grammar\n */\nconst block = {\n newline: /^(?: *(?:\\n|$))+/,\n code: /^( {4}[^\\n]+(?:\\n(?: *(?:\\n|$))*)?)+/,\n fences:\n /^ {0,3}(`{3,}(?=[^`\\n]*\\n)|~{3,})([^\\n]*)\\n(?:|([\\s\\S]*?)\\n)(?: {0,3}\\1[~`]* *(?=\\n|$)|$)/,\n hr: /^ {0,3}((?:-[\\t ]*){3,}|(?:_[ \\t]*){3,}|(?:\\*[ \\t]*){3,})(?:\\n+|$)/,\n heading: /^ {0,3}(#{1,6})(?=\\s|$)(.*)(?:\\n+|$)/,\n blockquote: /^( {0,3}> ?(paragraph|[^\\n]*)(?:\\n|$))+/,\n list: /^( {0,3}bull)([ \\t][^\\n]+?)?(?:\\n|$)/,\n html:\n "^ {0,3}(?:" + // optional indentation\n "<(script|pre|style|textarea)[\\\\s>][\\\\s\\\\S]*?(?:[^\\\\n]*\\\\n+|$)" + // (1)\n "|comment[^\\\\n]*(\\\\n+|$)" + // (2)\n "|<\\\\?[\\\\s\\\\S]*?(?:\\\\?>\\\\n*|$)" + // (3)\n "|\\\\n*|$)" + // (4)\n "|\\\\n*|$)" + // (5)\n "|)[\\\\s\\\\S]*?(?:(?:\\\\n *)+\\\\n|$)" + // (6)\n "|<(?!script|pre|style|textarea)([a-z][\\\\w-]*)(?:attribute)*? */?>(?=[ \\\\t]*(?:\\\\n|$))[\\\\s\\\\S]*?(?:(?:\\\\n *)+\\\\n|$)" + // (7) open tag\n "|(?=[ \\\\t]*(?:\\\\n|$))[\\\\s\\\\S]*?(?:(?:\\\\n *)+\\\\n|$)" + // (7) closing tag\n ")",\n def: /^ {0,3}\\[(label)\\]: *(?:\\n *)?]+)>?(?:(?: +(?:\\n *)?| *\\n *)(title))? *(?:\\n+|$)/,\n table: noopTest,\n lheading: /^([^\\n]+)\\n {0,3}(=+|-+) *(?:\\n+|$)/,\n // regex template, placeholders will be replaced according to different paragraph\n // interruption rules of commonmark and the original markdown spec:\n _paragraph:\n /^([^\\n]+(?:\\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\\n)[^\\n]+)*)/,\n text: /^[^\\n]+/,\n}\n\nblock._label = /(?!\\s*\\])(?:\\\\.|[^\\[\\]\\\\])+/\nblock._title = /(?:"(?:\\\\"?|[^"\\\\])*"|\'[^\'\\n]*(?:\\n[^\'\\n]+)*\\n?\'|\\([^()]*\\))/\nblock.def = edit(block.def)\n .replace("label", block._label)\n .replace("title", block._title)\n .getRegex()\n\nblock.bullet = /(?:[*+-]|\\d{1,9}[.)])/\nblock.listItemStart = edit(/^( *)(bull) */)\n .replace("bull", block.bullet)\n .getRegex()\n\nblock.list = edit(block.list)\n .replace(/bull/g, block.bullet)\n .replace(\n "hr",\n "\\\\n+(?=\\\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\\\* *){3,})(?:\\\\n+|$))"\n )\n .replace("def", "\\\\n+(?=" + block.def.source + ")")\n .getRegex()\n\nblock._tag =\n "address|article|aside|base|basefont|blockquote|body|caption" +\n "|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption" +\n "|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe" +\n "|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option" +\n "|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr" +\n "|track|ul"\nblock._comment = /\x3C!--(?!-?>)[\\s\\S]*?(?:-->|$)/\nblock.html = edit(block.html, "i")\n .replace("comment", block._comment)\n .replace("tag", block._tag)\n .replace(\n "attribute",\n / +[a-zA-Z:_][\\w.:-]*(?: *= *"[^"\\n]*"| *= *\'[^\'\\n]*\'| *= *[^\\s"\'=<>`]+)?/\n )\n .getRegex()\n\nblock.paragraph = edit(block._paragraph)\n .replace("hr", block.hr)\n .replace("heading", " {0,3}#{1,6} ")\n .replace("|lheading", "") // setex headings don\'t interrupt commonmark paragraphs\n .replace("|table", "")\n .replace("blockquote", " {0,3}>")\n .replace("fences", " {0,3}(?:`{3,}(?=[^`\\\\n]*\\\\n)|~{3,})[^\\\\n]*\\\\n")\n .replace("list", " {0,3}(?:[*+-]|1[.)]) ") // only lists starting from 1 can interrupt\n .replace(\n "html",\n ")|<(?:script|pre|style|textarea|!--)"\n )\n .replace("tag", block._tag) // pars can be interrupted by type (6) html blocks\n .getRegex()\n\nblock.blockquote = edit(block.blockquote)\n .replace("paragraph", block.paragraph)\n .getRegex()\n\n/**\n * Normal Block Grammar\n */\n\nblock.normal = merge({}, block)\n\n/**\n * GFM Block Grammar\n */\n\nblock.gfm = merge({}, block.normal, {\n table:\n "^ *([^\\\\n ].*\\\\|.*)\\\\n" + // Header\n " {0,3}(?:\\\\| *)?(:?-+:? *(?:\\\\| *:?-+:? *)*)(?:\\\\| *)?" + // Align\n "(?:\\\\n((?:(?! *\\\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\\\n|$))*)\\\\n*|$)", // Cells\n})\n\nblock.gfm.table = edit(block.gfm.table)\n .replace("hr", block.hr)\n .replace("heading", " {0,3}#{1,6} ")\n .replace("blockquote", " {0,3}>")\n .replace("code", " {4}[^\\\\n]")\n .replace("fences", " {0,3}(?:`{3,}(?=[^`\\\\n]*\\\\n)|~{3,})[^\\\\n]*\\\\n")\n .replace("list", " {0,3}(?:[*+-]|1[.)]) ") // only lists starting from 1 can interrupt\n .replace(\n "html",\n ")|<(?:script|pre|style|textarea|!--)"\n )\n .replace("tag", block._tag) // tables can be interrupted by type (6) html blocks\n .getRegex()\n\nblock.gfm.paragraph = edit(block._paragraph)\n .replace("hr", block.hr)\n .replace("heading", " {0,3}#{1,6} ")\n .replace("|lheading", "") // setex headings don\'t interrupt commonmark paragraphs\n .replace("table", block.gfm.table) // interrupt paragraphs with table\n .replace("blockquote", " {0,3}>")\n .replace("fences", " {0,3}(?:`{3,}(?=[^`\\\\n]*\\\\n)|~{3,})[^\\\\n]*\\\\n")\n .replace("list", " {0,3}(?:[*+-]|1[.)]) ") // only lists starting from 1 can interrupt\n .replace(\n "html",\n ")|<(?:script|pre|style|textarea|!--)"\n )\n .replace("tag", block._tag) // pars can be interrupted by type (6) html blocks\n .getRegex()\n/**\n * Pedantic grammar (original John Gruber\'s loose markdown specification)\n */\n\nblock.pedantic = merge({}, block.normal, {\n html: edit(\n "^ *(?:comment *(?:\\\\n|\\\\s*$)" +\n "|<(tag)[\\\\s\\\\S]+? *(?:\\\\n{2,}|\\\\s*$)" + // closed tag\n "|\\\\s]*)*?/?> *(?:\\\\n{2,}|\\\\s*$))"\n )\n .replace("comment", block._comment)\n .replace(\n /tag/g,\n "(?!(?:" +\n "a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub" +\n "|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)" +\n "\\\\b)\\\\w+(?!:|[^\\\\w\\\\s@]*@)\\\\b"\n )\n .getRegex(),\n def: /^ *\\[([^\\]]+)\\]: *]+)>?(?: +(["(][^\\n]+[")]))? *(?:\\n+|$)/,\n heading: /^(#{1,6})(.*)(?:\\n+|$)/,\n fences: noopTest, // fences not supported\n paragraph: edit(block.normal._paragraph)\n .replace("hr", block.hr)\n .replace("heading", " *#{1,6} *[^\\n]")\n .replace("lheading", block.lheading)\n .replace("blockquote", " {0,3}>")\n .replace("|fences", "")\n .replace("|list", "")\n .replace("|html", "")\n .getRegex(),\n})\n\n/**\n * Inline-Level Grammar\n */\nconst inline = {\n escape: /^\\\\([!"#$%&\'()*+,\\-./:;<=>?@\\[\\]\\\\^_`{|}~])/,\n autolink: /^<(scheme:[^\\s\\x00-\\x1f<>]*|email)>/,\n url: noopTest,\n tag:\n "^comment" +\n "|^" + // self-closing tag\n "|^<[a-zA-Z][\\\\w-]*(?:attribute)*?\\\\s*/?>" + // open tag\n "|^<\\\\?[\\\\s\\\\S]*?\\\\?>" + // processing instruction, e.g. \n "|^" + // declaration, e.g. \n "|^", // CDATA section\n link: /^!?\\[(label)\\]\\(\\s*(href)(?:\\s+(title))?\\s*\\)/,\n reflink: /^!?\\[(label)\\]\\[(ref)\\]/,\n nolink: /^!?\\[(ref)\\](?:\\[\\])?/,\n reflinkSearch: "reflink|nolink(?!\\\\()",\n emStrong: {\n lDelim: /^(?:\\*+(?:([punct_])|[^\\s*]))|^_+(?:([punct*])|([^\\s_]))/,\n // (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right.\n // () Skip orphan inside strong () Consume to delim (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a\n rDelimAst:\n /^[^_*]*?\\_\\_[^_*]*?\\*[^_*]*?(?=\\_\\_)|[^*]+(?=[^*])|[punct_](\\*+)(?=[\\s]|$)|[^punct*_\\s](\\*+)(?=[punct_\\s]|$)|[punct_\\s](\\*+)(?=[^punct*_\\s])|[\\s](\\*+)(?=[punct_])|[punct_](\\*+)(?=[punct_])|[^punct*_\\s](\\*+)(?=[^punct*_\\s])/,\n rDelimUnd:\n /^[^_*]*?\\*\\*[^_*]*?\\_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|[punct*](\\_+)(?=[\\s]|$)|[^punct*_\\s](\\_+)(?=[punct*\\s]|$)|[punct*\\s](\\_+)(?=[^punct*_\\s])|[\\s](\\_+)(?=[punct*])|[punct*](\\_+)(?=[punct*])/, // ^- Not allowed for _\n },\n code: /^(`+)([^`]|[^`][\\s\\S]*?[^`])\\1(?!`)/,\n br: /^( {2,}|\\\\)\\n(?!\\s*$)/,\n del: noopTest,\n text: /^(`+|[^`])(?:(?= {2,}\\n)|[\\s\\S]*?(?:(?=[\\\\?@\\\\[\\\\]`^{|}~"\ninline.punctuation = edit(inline.punctuation)\n .replace(/punctuation/g, inline._punctuation)\n .getRegex()\n\n// sequences em should skip over [title](link), `code`, \ninline.blockSkip = /\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>/g\ninline.escapedEmSt = /\\\\\\*|\\\\_/g\n\ninline._comment = edit(block._comment).replace("(?:-->|$)", "-->").getRegex()\n\ninline.emStrong.lDelim = edit(inline.emStrong.lDelim)\n .replace(/punct/g, inline._punctuation)\n .getRegex()\n\ninline.emStrong.rDelimAst = edit(inline.emStrong.rDelimAst, "g")\n .replace(/punct/g, inline._punctuation)\n .getRegex()\n\ninline.emStrong.rDelimUnd = edit(inline.emStrong.rDelimUnd, "g")\n .replace(/punct/g, inline._punctuation)\n .getRegex()\n\ninline._escapes = /\\\\([!"#$%&\'()*+,\\-./:;<=>?@\\[\\]\\\\^_`{|}~])/g\n\ninline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/\ninline._email =\n /[a-zA-Z0-9.!#$%&\'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/\ninline.autolink = edit(inline.autolink)\n .replace("scheme", inline._scheme)\n .replace("email", inline._email)\n .getRegex()\n\ninline._attribute =\n /\\s+[a-zA-Z:_][\\w.:-]*(?:\\s*=\\s*"[^"]*"|\\s*=\\s*\'[^\']*\'|\\s*=\\s*[^\\s"\'=<>`]+)?/\n\ninline.tag = edit(inline.tag)\n .replace("comment", inline._comment)\n .replace("attribute", inline._attribute)\n .getRegex()\n\ninline._label = /(?:\\[(?:\\\\.|[^\\[\\]\\\\])*\\]|\\\\.|`[^`]*`|[^\\[\\]\\\\`])*?/\ninline._href = /<(?:\\\\.|[^\\n<>\\\\])+>|[^\\s\\x00-\\x1f]*/\ninline._title = /"(?:\\\\"?|[^"\\\\])*"|\'(?:\\\\\'?|[^\'\\\\])*\'|\\((?:\\\\\\)?|[^)\\\\])*\\)/\n\ninline.link = edit(inline.link)\n .replace("label", inline._label)\n .replace("href", inline._href)\n .replace("title", inline._title)\n .getRegex()\n\ninline.reflink = edit(inline.reflink)\n .replace("label", inline._label)\n .replace("ref", block._label)\n .getRegex()\n\ninline.nolink = edit(inline.nolink).replace("ref", block._label).getRegex()\n\ninline.reflinkSearch = edit(inline.reflinkSearch, "g")\n .replace("reflink", inline.reflink)\n .replace("nolink", inline.nolink)\n .getRegex()\n\n/**\n * Normal Inline Grammar\n */\n\ninline.normal = merge({}, inline)\n\n/**\n * Pedantic Inline Grammar\n */\n\ninline.pedantic = merge({}, inline.normal, {\n strong: {\n start: /^__|\\*\\*/,\n middle: /^__(?=\\S)([\\s\\S]*?\\S)__(?!_)|^\\*\\*(?=\\S)([\\s\\S]*?\\S)\\*\\*(?!\\*)/,\n endAst: /\\*\\*(?!\\*)/g,\n endUnd: /__(?!_)/g,\n },\n em: {\n start: /^_|\\*/,\n middle: /^()\\*(?=\\S)([\\s\\S]*?\\S)\\*(?!\\*)|^_(?=\\S)([\\s\\S]*?\\S)_(?!_)/,\n endAst: /\\*(?!\\*)/g,\n endUnd: /_(?!_)/g,\n },\n link: edit(/^!?\\[(label)\\]\\((.*?)\\)/)\n .replace("label", inline._label)\n .getRegex(),\n reflink: edit(/^!?\\[(label)\\]\\s*\\[([^\\]]*)\\]/)\n .replace("label", inline._label)\n .getRegex(),\n})\n\n/**\n * GFM Inline Grammar\n */\n\ninline.gfm = merge({}, inline.normal, {\n escape: edit(inline.escape).replace("])", "~|])").getRegex(),\n _extended_email:\n /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,\n url: /^((?:ftp|https?):\\/\\/|www\\.)(?:[a-zA-Z0-9\\-]+\\.?)+[^\\s<]*|^email/,\n _backpedal:\n /(?:[^?!.,:;*_~()&]+|\\([^)]*\\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,\n del: /^(~~?)(?=[^\\s~])([\\s\\S]*?[^\\s~])\\1(?=[^~]|$)/,\n text: /^([`~]+|[^`~])(?:(?= {2,}\\n)|(?=[a-zA-Z0-9.!#$%&\'*+\\/=?_`{\\|}~-]+@)|[\\s\\S]*?(?:(?=[\\\\ 0.5) {\n ch = "x" + ch.toString(16)\n }\n out += "&#" + ch + ";"\n }\n\n return out\n}\n\n/**\n * Block Lexer\n */\nclass Lexer {\n constructor(options) {\n this.tokens = []\n this.tokens.links = Object.create(null)\n this.options = options || defaults\n this.options.tokenizer = this.options.tokenizer || new Tokenizer()\n this.tokenizer = this.options.tokenizer\n this.tokenizer.options = this.options\n this.tokenizer.lexer = this\n this.inlineQueue = []\n this.state = {\n inLink: false,\n inRawBlock: false,\n top: true,\n }\n\n const rules = {\n block: block.normal,\n inline: inline.normal,\n }\n\n if (this.options.pedantic) {\n rules.block = block.pedantic\n rules.inline = inline.pedantic\n } else if (this.options.gfm) {\n rules.block = block.gfm\n if (this.options.breaks) {\n rules.inline = inline.breaks\n } else {\n rules.inline = inline.gfm\n }\n }\n this.tokenizer.rules = rules\n }\n\n /**\n * Expose Rules\n */\n static get rules() {\n return {\n block,\n inline,\n }\n }\n\n /**\n * Static Lex Method\n */\n static lex(src, options) {\n const lexer = new Lexer(options)\n return lexer.lex(src)\n }\n\n /**\n * Static Lex Inline Method\n */\n static lexInline(src, options) {\n const lexer = new Lexer(options)\n return lexer.inlineTokens(src)\n }\n\n /**\n * Preprocessing\n */\n lex(src) {\n src = src.replace(/\\r\\n|\\r/g, "\\n")\n\n this.blockTokens(src, this.tokens)\n\n let next\n while ((next = this.inlineQueue.shift())) {\n this.inlineTokens(next.src, next.tokens)\n }\n\n return this.tokens\n }\n\n /**\n * Lexing\n */\n blockTokens(src, tokens = []) {\n if (this.options.pedantic) {\n src = src.replace(/\\t/g, " ").replace(/^ +$/gm, "")\n } else {\n src = src.replace(/^( *)(\\t+)/gm, (_, leading, tabs) => {\n return leading + " ".repeat(tabs.length)\n })\n }\n\n let token, lastToken, cutSrc, lastParagraphClipped\n\n while (src) {\n if (\n this.options.extensions &&\n this.options.extensions.block &&\n this.options.extensions.block.some(extTokenizer => {\n if ((token = extTokenizer.call({ lexer: this }, src, tokens))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n return true\n }\n return false\n })\n ) {\n continue\n }\n\n // newline\n if ((token = this.tokenizer.space(src))) {\n src = src.substring(token.raw.length)\n if (token.raw.length === 1 && tokens.length > 0) {\n // if there\'s a single \\n as a spacer, it\'s terminating the last line,\n // so move it there so that we don\'t get unecessary paragraph tags\n tokens[tokens.length - 1].raw += "\\n"\n } else {\n tokens.push(token)\n }\n continue\n }\n\n // code\n if ((token = this.tokenizer.code(src))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n // An indented code block cannot interrupt a paragraph.\n if (\n lastToken &&\n (lastToken.type === "paragraph" || lastToken.type === "text")\n ) {\n lastToken.raw += "\\n" + token.raw\n lastToken.text += "\\n" + token.text\n this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n // fences\n if ((token = this.tokenizer.fences(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // heading\n if ((token = this.tokenizer.heading(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // hr\n if ((token = this.tokenizer.hr(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // blockquote\n if ((token = this.tokenizer.blockquote(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // list\n if ((token = this.tokenizer.list(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // html\n if ((token = this.tokenizer.html(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // def\n if ((token = this.tokenizer.def(src))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n if (\n lastToken &&\n (lastToken.type === "paragraph" || lastToken.type === "text")\n ) {\n lastToken.raw += "\\n" + token.raw\n lastToken.text += "\\n" + token.raw\n this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text\n } else if (!this.tokens.links[token.tag]) {\n this.tokens.links[token.tag] = {\n href: token.href,\n title: token.title,\n }\n }\n continue\n }\n\n // table (gfm)\n if ((token = this.tokenizer.table(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // lheading\n if ((token = this.tokenizer.lheading(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // top-level paragraph\n // prevent paragraph consuming extensions by clipping \'src\' to extension start\n cutSrc = src\n if (this.options.extensions && this.options.extensions.startBlock) {\n let startIndex = Infinity\n const tempSrc = src.slice(1)\n let tempStart\n this.options.extensions.startBlock.forEach(function (getStartIndex) {\n tempStart = getStartIndex.call({ lexer: this }, tempSrc)\n if (typeof tempStart === "number" && tempStart >= 0) {\n startIndex = Math.min(startIndex, tempStart)\n }\n })\n if (startIndex < Infinity && startIndex >= 0) {\n cutSrc = src.substring(0, startIndex + 1)\n }\n }\n if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {\n lastToken = tokens[tokens.length - 1]\n if (lastParagraphClipped && lastToken.type === "paragraph") {\n lastToken.raw += "\\n" + token.raw\n lastToken.text += "\\n" + token.text\n this.inlineQueue.pop()\n this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text\n } else {\n tokens.push(token)\n }\n lastParagraphClipped = cutSrc.length !== src.length\n src = src.substring(token.raw.length)\n continue\n }\n\n // text\n if ((token = this.tokenizer.text(src))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n if (lastToken && lastToken.type === "text") {\n lastToken.raw += "\\n" + token.raw\n lastToken.text += "\\n" + token.text\n this.inlineQueue.pop()\n this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n if (src) {\n const errMsg = "Infinite loop on byte: " + src.charCodeAt(0)\n if (this.options.silent) {\n console.error(errMsg)\n break\n } else {\n throw new Error(errMsg)\n }\n }\n }\n\n this.state.top = true\n return tokens\n }\n\n inline(src, tokens) {\n this.inlineQueue.push({ src, tokens })\n }\n\n /**\n * Lexing/Compiling\n */\n inlineTokens(src, tokens = []) {\n let token, lastToken, cutSrc\n\n // String with links masked to avoid interference with em and strong\n let maskedSrc = src\n let match\n let keepPrevChar, prevChar\n\n // Mask out reflinks\n if (this.tokens.links) {\n const links = Object.keys(this.tokens.links)\n if (links.length > 0) {\n while (\n (match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) !=\n null\n ) {\n if (\n links.includes(match[0].slice(match[0].lastIndexOf("[") + 1, -1))\n ) {\n maskedSrc =\n maskedSrc.slice(0, match.index) +\n "[" +\n repeatString("a", match[0].length - 2) +\n "]" +\n maskedSrc.slice(\n this.tokenizer.rules.inline.reflinkSearch.lastIndex\n )\n }\n }\n }\n }\n // Mask out other blocks\n while (\n (match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null\n ) {\n maskedSrc =\n maskedSrc.slice(0, match.index) +\n "[" +\n repeatString("a", match[0].length - 2) +\n "]" +\n maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex)\n }\n\n // Mask out escaped em & strong delimiters\n while (\n (match = this.tokenizer.rules.inline.escapedEmSt.exec(maskedSrc)) != null\n ) {\n maskedSrc =\n maskedSrc.slice(0, match.index) +\n "++" +\n maskedSrc.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex)\n }\n\n while (src) {\n if (!keepPrevChar) {\n prevChar = ""\n }\n keepPrevChar = false\n\n // extensions\n if (\n this.options.extensions &&\n this.options.extensions.inline &&\n this.options.extensions.inline.some(extTokenizer => {\n if ((token = extTokenizer.call({ lexer: this }, src, tokens))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n return true\n }\n return false\n })\n ) {\n continue\n }\n\n // escape\n if ((token = this.tokenizer.escape(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // tag\n if ((token = this.tokenizer.tag(src))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n if (lastToken && token.type === "text" && lastToken.type === "text") {\n lastToken.raw += token.raw\n lastToken.text += token.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n // link\n if ((token = this.tokenizer.link(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // reflink, nolink\n if ((token = this.tokenizer.reflink(src, this.tokens.links))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n if (lastToken && token.type === "text" && lastToken.type === "text") {\n lastToken.raw += token.raw\n lastToken.text += token.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n // em & strong\n if ((token = this.tokenizer.emStrong(src, maskedSrc, prevChar))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // code\n if ((token = this.tokenizer.codespan(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // br\n if ((token = this.tokenizer.br(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // del (gfm)\n if ((token = this.tokenizer.del(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // autolink\n if ((token = this.tokenizer.autolink(src, mangle))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // url (gfm)\n if (!this.state.inLink && (token = this.tokenizer.url(src, mangle))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // text\n // prevent inlineText consuming extensions by clipping \'src\' to extension start\n cutSrc = src\n if (this.options.extensions && this.options.extensions.startInline) {\n let startIndex = Infinity\n const tempSrc = src.slice(1)\n let tempStart\n this.options.extensions.startInline.forEach(function (getStartIndex) {\n tempStart = getStartIndex.call({ lexer: this }, tempSrc)\n if (typeof tempStart === "number" && tempStart >= 0) {\n startIndex = Math.min(startIndex, tempStart)\n }\n })\n if (startIndex < Infinity && startIndex >= 0) {\n cutSrc = src.substring(0, startIndex + 1)\n }\n }\n if ((token = this.tokenizer.inlineText(cutSrc, smartypants))) {\n src = src.substring(token.raw.length)\n if (token.raw.slice(-1) !== "_") {\n // Track prevChar before string of ____ started\n prevChar = token.raw.slice(-1)\n }\n keepPrevChar = true\n lastToken = tokens[tokens.length - 1]\n if (lastToken && lastToken.type === "text") {\n lastToken.raw += token.raw\n lastToken.text += token.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n if (src) {\n const errMsg = "Infinite loop on byte: " + src.charCodeAt(0)\n if (this.options.silent) {\n console.error(errMsg)\n break\n } else {\n throw new Error(errMsg)\n }\n }\n }\n\n return tokens\n }\n}\n\n/**\n * Renderer\n */\nclass Renderer {\n constructor(options) {\n this.options = options || defaults\n }\n\n code(code, infostring, escaped) {\n const lang = (infostring || "").match(/\\S*/)[0]\n if (this.options.highlight) {\n const out = this.options.highlight(code, lang)\n if (out != null && out !== code) {\n escaped = true\n code = out\n }\n }\n\n code = code.replace(/\\n$/, "") + "\\n"\n\n if (!lang) {\n return (\n "
" +\n        (escaped ? code : escape(code, true)) +\n        "
\\n"\n )\n }\n\n return (\n \'
\' +\n      (escaped ? code : escape(code, true)) +\n      "
\\n"\n )\n }\n\n /**\n * @param {string} quote\n */\n blockquote(quote) {\n return `
\\n${quote}
\\n`\n }\n\n html(html) {\n return html\n }\n\n /**\n * @param {string} text\n * @param {string} level\n * @param {string} raw\n * @param {any} slugger\n */\n heading(text, level, raw, slugger) {\n if (this.options.headerIds) {\n const id = this.options.headerPrefix + slugger.slug(raw)\n return `${text}\\n`\n }\n\n // ignore IDs\n return `${text}\\n`\n }\n\n hr() {\n return this.options.xhtml ? "
\\n" : "
\\n"\n }\n\n list(body, ordered, start) {\n const type = ordered ? "ol" : "ul",\n startatt = ordered && start !== 1 ? \' start="\' + start + \'"\' : ""\n return "<" + type + startatt + ">\\n" + body + "\\n"\n }\n\n /**\n * @param {string} text\n */\n listitem(text) {\n return `
  • ${text}
  • \\n`\n }\n\n checkbox(checked) {\n return (\n " "\n )\n }\n\n /**\n * @param {string} text\n */\n paragraph(text) {\n return `

    ${text}

    \\n`\n }\n\n /**\n * @param {string} header\n * @param {string} body\n */\n table(header, body) {\n if (body) body = `${body}`\n\n return (\n "\\n" + "\\n" + header + "\\n" + body + "
    \\n"\n )\n }\n\n /**\n * @param {string} content\n */\n tablerow(content) {\n return `\\n${content}\\n`\n }\n\n tablecell(content, flags) {\n const type = flags.header ? "th" : "td"\n const tag = flags.align ? `<${type} align="${flags.align}">` : `<${type}>`\n return tag + content + `\\n`\n }\n\n /**\n * span level renderer\n * @param {string} text\n */\n strong(text) {\n return `${text}`\n }\n\n /**\n * @param {string} text\n */\n em(text) {\n return `${text}`\n }\n\n /**\n * @param {string} text\n */\n codespan(text) {\n return `${text}`\n }\n\n br() {\n return this.options.xhtml ? "
    " : "
    "\n }\n\n /**\n * @param {string} text\n */\n del(text) {\n return `${text}`\n }\n\n /**\n * @param {string} href\n * @param {string} title\n * @param {string} text\n */\n link(href, title, text) {\n href = cleanUrl(this.options.sanitize, this.options.baseUrl, href)\n if (href === null) {\n return text\n }\n let out = \'
    "\n return out\n }\n\n /**\n * @param {string} href\n * @param {string} title\n * @param {string} text\n */\n image(href, title, text) {\n href = cleanUrl(this.options.sanitize, this.options.baseUrl, href)\n if (href === null) {\n return text\n }\n\n let out = `${text}" : ">"\n return out\n }\n\n text(text) {\n return text\n }\n}\n\n/**\n * TextRenderer\n * returns only the textual part of the token\n */\nclass TextRenderer {\n // no need for block level renderers\n strong(text) {\n return text\n }\n\n em(text) {\n return text\n }\n\n codespan(text) {\n return text\n }\n\n del(text) {\n return text\n }\n\n html(text) {\n return text\n }\n\n text(text) {\n return text\n }\n\n link(href, title, text) {\n return "" + text\n }\n\n image(href, title, text) {\n return "" + text\n }\n\n br() {\n return ""\n }\n}\n\n/**\n * Slugger generates header id\n */\nclass Slugger {\n constructor() {\n this.seen = {}\n }\n\n /**\n * @param {string} value\n */\n serialize(value) {\n return (\n value\n .toLowerCase()\n .trim()\n // remove html tags\n .replace(/<[!\\/a-z].*?>/gi, "")\n // remove unwanted chars\n .replace(\n /[\\u2000-\\u206F\\u2E00-\\u2E7F\\\\\'!"#$%&()*+,./:;<=>?@[\\]^`{|}~]/g,\n ""\n )\n .replace(/\\s/g, "-")\n )\n }\n\n /**\n * Finds the next safe (unique) slug to use\n * @param {string} originalSlug\n * @param {boolean} isDryRun\n */\n getNextSafeSlug(originalSlug, isDryRun) {\n let slug = originalSlug\n let occurenceAccumulator = 0\n if (this.seen.hasOwnProperty(slug)) {\n occurenceAccumulator = this.seen[originalSlug]\n do {\n occurenceAccumulator++\n slug = originalSlug + "-" + occurenceAccumulator\n } while (this.seen.hasOwnProperty(slug))\n }\n if (!isDryRun) {\n this.seen[originalSlug] = occurenceAccumulator\n this.seen[slug] = 0\n }\n return slug\n }\n\n /**\n * Convert string to unique id\n * @param {object} [options]\n * @param {boolean} [options.dryrun] Generates the next unique slug without\n * updating the internal accumulator.\n */\n slug(value, options = {}) {\n const slug = this.serialize(value)\n return this.getNextSafeSlug(slug, options.dryrun)\n }\n}\n\n/**\n * Parsing & Compiling\n */\nclass Parser {\n constructor(options) {\n this.options = options || defaults\n this.options.renderer = this.options.renderer || new Renderer()\n this.renderer = this.options.renderer\n this.renderer.options = this.options\n this.textRenderer = new TextRenderer()\n this.slugger = new Slugger()\n }\n\n /**\n * Static Parse Method\n */\n static parse(tokens, options) {\n const parser = new Parser(options)\n return parser.parse(tokens)\n }\n\n /**\n * Static Parse Inline Method\n */\n static parseInline(tokens, options) {\n const parser = new Parser(options)\n return parser.parseInline(tokens)\n }\n\n /**\n * Parse Loop\n */\n parse(tokens, top = true) {\n let out = "",\n i,\n j,\n k,\n l2,\n l3,\n row,\n cell,\n header,\n body,\n token,\n ordered,\n start,\n loose,\n itemBody,\n item,\n checked,\n task,\n checkbox,\n ret\n\n const l = tokens.length\n for (i = 0; i < l; i++) {\n token = tokens[i]\n\n // Run any renderer extensions\n if (\n this.options.extensions &&\n this.options.extensions.renderers &&\n this.options.extensions.renderers[token.type]\n ) {\n ret = this.options.extensions.renderers[token.type].call(\n { parser: this },\n token\n )\n if (\n ret !== false ||\n ![\n "space",\n "hr",\n "heading",\n "code",\n "table",\n "blockquote",\n "list",\n "html",\n "paragraph",\n "text",\n ].includes(token.type)\n ) {\n out += ret || ""\n continue\n }\n }\n\n switch (token.type) {\n case "space": {\n continue\n }\n case "hr": {\n out += this.renderer.hr()\n continue\n }\n case "heading": {\n out += this.renderer.heading(\n this.parseInline(token.tokens),\n token.depth,\n unescape(this.parseInline(token.tokens, this.textRenderer)),\n this.slugger\n )\n continue\n }\n case "code": {\n out += this.renderer.code(token.text, token.lang, token.escaped)\n continue\n }\n case "table": {\n header = ""\n\n // header\n cell = ""\n l2 = token.header.length\n for (j = 0; j < l2; j++) {\n cell += this.renderer.tablecell(\n this.parseInline(token.header[j].tokens),\n { header: true, align: token.align[j] }\n )\n }\n header += this.renderer.tablerow(cell)\n\n body = ""\n l2 = token.rows.length\n for (j = 0; j < l2; j++) {\n row = token.rows[j]\n\n cell = ""\n l3 = row.length\n for (k = 0; k < l3; k++) {\n cell += this.renderer.tablecell(this.parseInline(row[k].tokens), {\n header: false,\n align: token.align[k],\n })\n }\n\n body += this.renderer.tablerow(cell)\n }\n out += this.renderer.table(header, body)\n continue\n }\n case "blockquote": {\n body = this.parse(token.tokens)\n out += this.renderer.blockquote(body)\n continue\n }\n case "list": {\n ordered = token.ordered\n start = token.start\n loose = token.loose\n l2 = token.items.length\n\n body = ""\n for (j = 0; j < l2; j++) {\n item = token.items[j]\n checked = item.checked\n task = item.task\n\n itemBody = ""\n if (item.task) {\n checkbox = this.renderer.checkbox(checked)\n if (loose) {\n if (\n item.tokens.length > 0 &&\n item.tokens[0].type === "paragraph"\n ) {\n item.tokens[0].text = checkbox + " " + item.tokens[0].text\n if (\n item.tokens[0].tokens &&\n item.tokens[0].tokens.length > 0 &&\n item.tokens[0].tokens[0].type === "text"\n ) {\n item.tokens[0].tokens[0].text =\n checkbox + " " + item.tokens[0].tokens[0].text\n }\n } else {\n item.tokens.unshift({\n type: "text",\n text: checkbox,\n })\n }\n } else {\n itemBody += checkbox\n }\n }\n\n itemBody += this.parse(item.tokens, loose)\n body += this.renderer.listitem(itemBody, task, checked)\n }\n\n out += this.renderer.list(body, ordered, start)\n continue\n }\n case "html": {\n // TODO parse inline content if parameter markdown=1\n out += this.renderer.html(token.text)\n continue\n }\n case "paragraph": {\n out += this.renderer.paragraph(this.parseInline(token.tokens))\n continue\n }\n case "text": {\n body = token.tokens ? this.parseInline(token.tokens) : token.text\n while (i + 1 < l && tokens[i + 1].type === "text") {\n token = tokens[++i]\n body +=\n "\\n" +\n (token.tokens ? this.parseInline(token.tokens) : token.text)\n }\n out += top ? this.renderer.paragraph(body) : body\n continue\n }\n\n default: {\n const errMsg = \'Token with "\' + token.type + \'" type was not found.\'\n if (this.options.silent) {\n console.error(errMsg)\n return\n } else {\n throw new Error(errMsg)\n }\n }\n }\n }\n\n return out\n }\n\n /**\n * Parse Inline Tokens\n */\n parseInline(tokens, renderer) {\n renderer = renderer || this.renderer\n let out = "",\n i,\n token,\n ret\n\n const l = tokens.length\n for (i = 0; i < l; i++) {\n token = tokens[i]\n\n // Run any renderer extensions\n if (\n this.options.extensions &&\n this.options.extensions.renderers &&\n this.options.extensions.renderers[token.type]\n ) {\n ret = this.options.extensions.renderers[token.type].call(\n { parser: this },\n token\n )\n if (\n ret !== false ||\n ![\n "escape",\n "html",\n "link",\n "image",\n "strong",\n "em",\n "codespan",\n "br",\n "del",\n "text",\n ].includes(token.type)\n ) {\n out += ret || ""\n continue\n }\n }\n\n switch (token.type) {\n case "escape": {\n out += renderer.text(token.text)\n break\n }\n case "html": {\n out += renderer.html(token.text)\n break\n }\n case "link": {\n out += renderer.link(\n token.href,\n token.title,\n this.parseInline(token.tokens, renderer)\n )\n break\n }\n case "image": {\n out += renderer.image(token.href, token.title, token.text)\n break\n }\n case "strong": {\n out += renderer.strong(this.parseInline(token.tokens, renderer))\n break\n }\n case "em": {\n out += renderer.em(this.parseInline(token.tokens, renderer))\n break\n }\n case "codespan": {\n out += renderer.codespan(token.text)\n break\n }\n case "br": {\n out += renderer.br()\n break\n }\n case "del": {\n out += renderer.del(this.parseInline(token.tokens, renderer))\n break\n }\n case "text": {\n out += renderer.text(token.text)\n break\n }\n default: {\n const errMsg = \'Token with "\' + token.type + \'" type was not found.\'\n if (this.options.silent) {\n console.error(errMsg)\n return\n } else {\n throw new Error(errMsg)\n }\n }\n }\n }\n return out\n }\n}\n\n/**\n * Marked\n */\nfunction marked(src, opt, callback) {\n // throw error in case of non string input\n if (typeof src === "undefined" || src === null) {\n throw new Error("marked(): input parameter is undefined or null")\n }\n if (typeof src !== "string") {\n throw new Error(\n "marked(): input parameter is of type " +\n Object.prototype.toString.call(src) +\n ", string expected"\n )\n }\n\n if (typeof opt === "function") {\n callback = opt\n opt = null\n }\n\n opt = merge({}, marked.defaults, opt || {})\n checkSanitizeDeprecation(opt)\n\n if (callback) {\n const highlight = opt.highlight\n let tokens\n\n try {\n tokens = Lexer.lex(src, opt)\n } catch (e) {\n return callback(e)\n }\n\n const done = function (err) {\n let out\n\n if (!err) {\n try {\n if (opt.walkTokens) {\n marked.walkTokens(tokens, opt.walkTokens)\n }\n out = Parser.parse(tokens, opt)\n } catch (e) {\n err = e\n }\n }\n\n opt.highlight = highlight\n\n return err ? callback(err) : callback(null, out)\n }\n\n if (!highlight || highlight.length < 3) {\n return done()\n }\n\n delete opt.highlight\n\n if (!tokens.length) return done()\n\n let pending = 0\n marked.walkTokens(tokens, function (token) {\n if (token.type === "code") {\n pending++\n setTimeout(() => {\n highlight(token.text, token.lang, function (err, code) {\n if (err) {\n return done(err)\n }\n if (code != null && code !== token.text) {\n token.text = code\n token.escaped = true\n }\n\n pending--\n if (pending === 0) {\n done()\n }\n })\n }, 0)\n }\n })\n\n if (pending === 0) {\n done()\n }\n\n return\n }\n\n try {\n const tokens = Lexer.lex(src, opt)\n if (opt.walkTokens) {\n marked.walkTokens(tokens, opt.walkTokens)\n }\n return Parser.parse(tokens, opt)\n } catch (e) {\n e.message += "\\nPlease report this to https://github.com/markedjs/marked."\n if (opt.silent) {\n return (\n "

    An error occurred:

    " +\n        escape(e.message + "", true) +\n        "
    "\n )\n }\n throw e\n }\n}\n\n/**\n * Options\n */\n\nmarked.options = marked.setOptions = function (opt) {\n merge(marked.defaults, opt)\n changeDefaults(marked.defaults)\n return marked\n}\n\nmarked.getDefaults = getDefaults\n\nmarked.defaults = defaults\n\n/**\n * Use Extension\n */\n\nmarked.use = function (...args) {\n const opts = merge({}, ...args)\n const extensions = marked.defaults.extensions || {\n renderers: {},\n childTokens: {},\n }\n let hasExtensions\n\n args.forEach(pack => {\n // ==-- Parse "addon" extensions --== //\n if (pack.extensions) {\n hasExtensions = true\n pack.extensions.forEach(ext => {\n if (!ext.name) {\n throw new Error("extension name required")\n }\n if (ext.renderer) {\n // Renderer extensions\n const prevRenderer = extensions.renderers\n ? extensions.renderers[ext.name]\n : null\n if (prevRenderer) {\n // Replace extension with func to run new extension but fall back if false\n extensions.renderers[ext.name] = function (...args) {\n let ret = ext.renderer.apply(this, args)\n if (ret === false) {\n ret = prevRenderer.apply(this, args)\n }\n return ret\n }\n } else {\n extensions.renderers[ext.name] = ext.renderer\n }\n }\n if (ext.tokenizer) {\n // Tokenizer Extensions\n if (!ext.level || (ext.level !== "block" && ext.level !== "inline")) {\n throw new Error("extension level must be \'block\' or \'inline\'")\n }\n if (extensions[ext.level]) {\n extensions[ext.level].unshift(ext.tokenizer)\n } else {\n extensions[ext.level] = [ext.tokenizer]\n }\n if (ext.start) {\n // Function to check for start of token\n if (ext.level === "block") {\n if (extensions.startBlock) {\n extensions.startBlock.push(ext.start)\n } else {\n extensions.startBlock = [ext.start]\n }\n } else if (ext.level === "inline") {\n if (extensions.startInline) {\n extensions.startInline.push(ext.start)\n } else {\n extensions.startInline = [ext.start]\n }\n }\n }\n }\n if (ext.childTokens) {\n // Child tokens to be visited by walkTokens\n extensions.childTokens[ext.name] = ext.childTokens\n }\n })\n }\n\n // ==-- Parse "overwrite" extensions --== //\n if (pack.renderer) {\n const renderer = marked.defaults.renderer || new Renderer()\n for (const prop in pack.renderer) {\n const prevRenderer = renderer[prop]\n // Replace renderer with func to run extension, but fall back if false\n renderer[prop] = (...args) => {\n let ret = pack.renderer[prop].apply(renderer, args)\n if (ret === false) {\n ret = prevRenderer.apply(renderer, args)\n }\n return ret\n }\n }\n opts.renderer = renderer\n }\n if (pack.tokenizer) {\n const tokenizer = marked.defaults.tokenizer || new Tokenizer()\n for (const prop in pack.tokenizer) {\n const prevTokenizer = tokenizer[prop]\n // Replace tokenizer with func to run extension, but fall back if false\n tokenizer[prop] = (...args) => {\n let ret = pack.tokenizer[prop].apply(tokenizer, args)\n if (ret === false) {\n ret = prevTokenizer.apply(tokenizer, args)\n }\n return ret\n }\n }\n opts.tokenizer = tokenizer\n }\n\n // ==-- Parse WalkTokens extensions --== //\n if (pack.walkTokens) {\n const walkTokens = marked.defaults.walkTokens\n opts.walkTokens = function (token) {\n pack.walkTokens.call(this, token)\n if (walkTokens) {\n walkTokens.call(this, token)\n }\n }\n }\n\n if (hasExtensions) {\n opts.extensions = extensions\n }\n\n marked.setOptions(opts)\n })\n}\n\n/**\n * Run callback for every token\n */\n\nmarked.walkTokens = function (tokens, callback) {\n for (const token of tokens) {\n callback.call(marked, token)\n switch (token.type) {\n case "table": {\n for (const cell of token.header) {\n marked.walkTokens(cell.tokens, callback)\n }\n for (const row of token.rows) {\n for (const cell of row) {\n marked.walkTokens(cell.tokens, callback)\n }\n }\n break\n }\n case "list": {\n marked.walkTokens(token.items, callback)\n break\n }\n default: {\n if (\n marked.defaults.extensions &&\n marked.defaults.extensions.childTokens &&\n marked.defaults.extensions.childTokens[token.type]\n ) {\n // Walk any extensions\n marked.defaults.extensions.childTokens[token.type].forEach(function (\n childTokens\n ) {\n marked.walkTokens(token[childTokens], callback)\n })\n } else if (token.tokens) {\n marked.walkTokens(token.tokens, callback)\n }\n }\n }\n }\n}\n\n/**\n * Parse Inline\n * @param {string} src\n */\nmarked.parseInline = function (src, opt) {\n // throw error in case of non string input\n if (typeof src === "undefined" || src === null) {\n throw new Error(\n "marked.parseInline(): input parameter is undefined or null"\n )\n }\n if (typeof src !== "string") {\n throw new Error(\n "marked.parseInline(): input parameter is of type " +\n Object.prototype.toString.call(src) +\n ", string expected"\n )\n }\n\n opt = merge({}, marked.defaults, opt || {})\n checkSanitizeDeprecation(opt)\n\n try {\n const tokens = Lexer.lexInline(src, opt)\n if (opt.walkTokens) {\n marked.walkTokens(tokens, opt.walkTokens)\n }\n return Parser.parseInline(tokens, opt)\n } catch (e) {\n e.message += "\\nPlease report this to https://github.com/markedjs/marked."\n if (opt.silent) {\n return (\n "

    An error occurred:

    " +\n        escape(e.message + "", true) +\n        "
    "\n )\n }\n throw e\n }\n}\n\n/**\n * Expose\n */\nmarked.Parser = Parser\nmarked.parser = Parser.parse\nmarked.Renderer = Renderer\nmarked.TextRenderer = TextRenderer\nmarked.Lexer = Lexer\nmarked.lexer = Lexer.lex\nmarked.Tokenizer = Tokenizer\nmarked.Slugger = Slugger\nmarked.parse = marked\n\nconst options = marked.options\nconst setOptions = marked.setOptions\nconst use = marked.use\nconst walkTokens = marked.walkTokens\nconst parseInline = marked.parseInline\nconst parse = marked\nconst parser = Parser.parse\nconst lexer = Lexer.lex\n\nconst email = trigger.row\nreturn marked(email.Message)' From 65ca394f61b7a09935a1f013dfb80e48aa1e7470 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 5 Mar 2024 16:56:55 +0000 Subject: [PATCH 04/59] Add snippets panel --- .../common/bindings/SnippetSidePanel.svelte | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 packages/builder/src/components/common/bindings/SnippetSidePanel.svelte diff --git a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte new file mode 100644 index 0000000000..6a62e7a318 --- /dev/null +++ b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte @@ -0,0 +1,186 @@ + + + + +
    {@html hoveredSnippet.code}
    +
    + +
    + +
    + {#if searching} +
    + +
    + + {:else} +
    Snippets
    + + + {/if} +
    + +
    + {#each filteredSnippets as snippet} +
    +
    showSnippet(snippet, e.target)} + on:mouseleave={hidePopover} + on:click={() => addSnippet(snippet)} + > + {snippet.name} +
    +
    + {/each} +
    +
    +
    + + From 5b3280832cadc4c480b6f5680f6189b6c848fe32 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 5 Mar 2024 18:38:48 +0000 Subject: [PATCH 05/59] Improve logic around swapping binding panel tabs --- .../common/CodeEditor/CodeEditor.svelte | 51 +++++++----- .../common/bindings/BindingPanel.svelte | 56 ++++++++++--- .../common/bindings/SnippetSidePanel.svelte | 81 +++++++++++++------ .../src/components/common/bindings/utils.js | 9 +++ .../src/helpers/javascript.js | 14 ---- 5 files changed, 141 insertions(+), 70 deletions(-) diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index 88661467be..90188d3094 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -40,20 +40,20 @@ indentMore, indentLess, } from "@codemirror/commands" - import { Compartment } from "@codemirror/state" + import { Compartment, EditorState } from "@codemirror/state" import { javascript } from "@codemirror/lang-javascript" import { EditorModes } from "./" import { themeStore } from "stores/portal" export let label export let completions = [] - export let resize = "none" export let mode = EditorModes.Handlebars export let value = "" export let placeholder = null export let autocompleteEnabled = true export let autofocus = false export let jsBindingWrapping = true + export let readonly = false // Export a function to expose caret position export const getCaretPosition = () => { @@ -143,32 +143,21 @@ const buildBaseExtensions = () => { return [ ...(mode.name === "handlebars" ? [plugin] : []), - history(), drawSelection(), dropCursor(), bracketMatching(), closeBrackets(), - highlightActiveLine(), syntaxHighlighting(oneDarkHighlightStyle, { fallback: true }), - highlightActiveLineGutter(), highlightSpecialChars(), - lineNumbers(), - foldGutter(), EditorView.lineWrapping, - EditorView.updateListener.of(v => { - const docStr = v.state.doc?.toString() - if (docStr === value) { - return - } - dispatch("change", docStr) - }), - keymap.of(buildKeymap()), themeConfig.of([...(isDark ? [oneDark] : [])]), ] } + // None of this is reactive, but it never has been, so we just assume most + // config flags aren't changed at runtime const buildExtensions = base => { - const complete = [...base] + let complete = [...base] if (autocompleteEnabled) { complete.push( @@ -210,12 +199,36 @@ if (mode.name === "javascript") { complete.push(javascript()) - complete.push(highlightWhitespace()) + if (!readonly) { + complete.push(highlightWhitespace()) + } } if (placeholder) { complete.push(placeholderFn(placeholder)) } + + if (readonly) { + complete.push(EditorState.readOnly.of(true)) + } else { + complete = [ + ...complete, + history(), + highlightActiveLine(), + highlightActiveLineGutter(), + lineNumbers(), + foldGutter(), + keymap.of(buildKeymap()), + EditorView.updateListener.of(v => { + const docStr = v.state.doc?.toString() + if (docStr === value) { + return + } + dispatch("change", docStr) + }), + ] + } + return complete } @@ -301,7 +314,6 @@ /* Active line */ .code-editor :global(.cm-line) { - height: 16px; padding: 0 var(--spacing-s); color: var(--spectrum-alias-text-color); } @@ -319,6 +331,9 @@ background: var(--spectrum-global-color-gray-100) !important; z-index: -2; } + .code-editor :global(.cm-highlightSpace:before) { + color: var(--spectrum-global-color-gray-500); + } /* Code selection */ .code-editor :global(.cm-selectionBackground) { diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index b40ce1aa6a..8951ba1207 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -25,6 +25,7 @@ } from "../CodeEditor" import BindingSidePanel from "./BindingSidePanel.svelte" import EvaluationSidePanel from "./EvaluationSidePanel.svelte" + import SnippetSidePanel from "./SnippetSidePanel.svelte" import { BindingHelpers } from "./utils" import formatHighlight from "json-format-highlight" import { capitalise } from "helpers" @@ -38,6 +39,7 @@ export let valid export let allowJS = false export let allowHelpers = true + export let allowSnippets = true export let context = null export let autofocusEditor = false @@ -49,6 +51,7 @@ const SidePanels = { Bindings: "FlashOn", Evaluation: "Play", + Snippets: "Code", } let initialValueJS = value?.startsWith?.("{{ js ") @@ -64,10 +67,8 @@ let evaluating = false $: drawerContext?.modal.subscribe(val => (drawerIsModal = val)) - $: editorTabs = allowJS ? [Modes.Text, Modes.JavaScript] : [Modes.Text] - $: sideTabs = context - ? [SidePanels.Evaluation, SidePanels.Bindings] - : [SidePanels.Bindings] + $: editorModeOptions = allowJS ? [Modes.Text, Modes.JavaScript] : [Modes.Text] + $: sidePanelOptions = getSidePanelOptions(context, allowSnippets, mode) $: enrichedBindings = enrichBindings(bindings, context) $: usingJS = mode === Modes.JavaScript $: editorMode = @@ -77,6 +78,22 @@ $: runtimeExpression = readableToRuntimeBinding(enrichedBindings, value) $: requestUpdateEvaluation(runtimeExpression, context) $: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos) + $: { + if (!sidePanelOptions.includes(sidePanel)) { + sidePanel = SidePanels.Bindings + } + } + + const getSidePanelOptions = (context, allowSnippets, mode) => { + let options = [SidePanels.Bindings] + if (context) { + options.unshift(SidePanels.Evaluation) + } + if (allowSnippets && mode === Modes.JavaScript) { + options.push(SidePanels.Snippets) + } + return options + } const debouncedUpdateEvaluation = Utils.debounce((expression, context) => { expressionResult = processStringSync(expression || "", context) @@ -135,11 +152,22 @@ bindingHelpers.onSelectBinding(js ? jsValue : hbsValue, binding, { js }) } + const onSelectSnippet = snippet => { + bindingHelpers.onSelectSnippet(jsValue, snippet) + } + const changeMode = newMode => { if (targetMode || newMode === mode) { return } - if (editorValue) { + + // Get the raw editor value to see if we are abandoning changes + let rawValue = editorValue + if (mode === Modes.JavaScript) { + rawValue = decodeJSBinding(rawValue) + } + + if (rawValue?.length) { targetMode = newMode } else { mode = newMode @@ -178,26 +206,26 @@
    - {#each editorTabs as tab} + {#each editorModeOptions as editorMode} changeMode(tab)} + selected={mode === editorMode} + on:click={() => changeMode(editorMode)} > - {capitalise(tab)} + {capitalise(editorMode)} {/each}
    - {#each sideTabs as tab} + {#each sidePanelOptions as panel} changeSidePanel(tab)} + selected={sidePanel === panel} + on:click={() => changeSidePanel(panel)} > - + {/each} {#if drawerContext && get(drawerContext.resizable)} @@ -287,6 +315,8 @@ {evaluating} expression={editorValue} /> + {:else if sidePanel === SidePanels.Snippets} + {/if}
    diff --git a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte index 6a62e7a318..0539c4a8f5 100644 --- a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte @@ -1,5 +1,7 @@ @@ -254,7 +288,8 @@ ]), ]} autofocus={autofocusEditor} - placeholder="Add bindings by typing {{ or use the menu on the right" + placeholder={placeholder || + "Add bindings by typing {{ or use the menu on the right"} /> {:else if mode === Modes.JavaScript} {/if} {#if targetMode} diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte new file mode 100644 index 0000000000..7e959053fa --- /dev/null +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -0,0 +1,53 @@ + + + + + {#if snippet} + + {/if} + + + + {#key key} + (code = e.detail)} + /> + {/key} + + diff --git a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte index c1f3ca1531..592ca7cfcd 100644 --- a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte @@ -2,6 +2,7 @@ import { Input, Layout, Icon, Popover } from "@budibase/bbui" import CodeEditor from "components/common/CodeEditor/CodeEditor.svelte" import { EditorModes } from "components/common/CodeEditor" + import SnippetDrawer from "./SnippetDrawer.svelte" export let addSnippet export let snippets @@ -12,6 +13,8 @@ let popoverAnchor let hoveredSnippet let hideTimeout + let snippetDrawer + let editableSnippet $: filteredSnippets = getFilteredSnippets(snippets, search) @@ -60,9 +63,61 @@ search = "" } - const openSnippetModal = () => {} + const createSnippet = () => { + editableSnippet = null + snippetDrawer.show() + } + + const editSnippet = (e, snippet) => { + e.preventDefault() + e.stopPropagation() + editableSnippet = snippet + snippetDrawer.show() + } + + +
    + +
    + {#if searching} +
    + +
    + + {:else} +
    Snippets
    + + + {/if} +
    +
    + {#each filteredSnippets as snippet} +
    showSnippet(snippet, e.target)} + on:mouseleave={hidePopover} + on:click={() => addSnippet(snippet)} + > + {snippet.name} + editSnippet(e, snippet)} + color="var(--spectrum-global-color-gray-700)" + /> +
    + {/each} +
    +
    +
    + -
    - -
    - {#if searching} -
    - -
    - - {:else} -
    Snippets
    - - - {/if} -
    - -
    - {#each filteredSnippets as snippet} -
    showSnippet(snippet, e.target)} - on:mouseleave={hidePopover} - on:click={() => addSnippet(snippet)} - > - {snippet.name} -
    - {/each} -
    -
    -
    + diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 232aabc21c..bba3832efd 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -30,7 +30,6 @@ import formatHighlight from "json-format-highlight" import { capitalise } from "helpers" import { Utils } from "@budibase/frontend-core" - import { get } from "svelte/store" const dispatch = createEventDispatcher() @@ -46,7 +45,6 @@ export let autofocusEditor = false export let placeholder = null - const drawerContext = getContext("drawer") const Modes = { Text: "Text", JavaScript: "JavaScript", @@ -66,10 +64,8 @@ let insertAtPos let targetMode = null let expressionResult - let drawerIsModal let evaluating = false - $: drawerContext?.modal.subscribe(val => (drawerIsModal = val)) $: editorModeOptions = getModeOptions(allowHBS, allowJS) $: sidePanelOptions = getSidePanelOptions( bindings, @@ -239,18 +235,22 @@
    -
    - {#each editorModeOptions as editorMode} - changeMode(editorMode)} - > - {capitalise(editorMode)} - - {/each} -
    + {#if $$slots.tabs} + + {:else} +
    + {#each editorModeOptions as editorMode} + changeMode(editorMode)} + > + {capitalise(editorMode)} + + {/each} +
    + {/if}
    {#each sidePanelOptions as panel} {/each} - {#if drawerContext && get(drawerContext.resizable)} - drawerContext.modal.set(!drawerIsModal)} - > - - - {/if}
    diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index 7e959053fa..05c2b9b000 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -1,5 +1,5 @@ - + + + {#if snippet} + {snippet.name} + {:else} +
    + Name + + {#if !nameValid} + + + + {/if} +
    + {/if} +
    {#if snippet} {/if} - + {#key key} @@ -44,6 +97,7 @@ allowHBS={false} allowJS allowSnippets={false} + showTabBar={false} placeholder="return function(input) ❴ ... ❵" value={code} on:change={e => (code = e.detail)} @@ -55,3 +109,19 @@ {/key}
    + + From 01679fbd0133deed9c0c49ef299e6d4cf34a87e7 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 6 Mar 2024 18:36:22 +0000 Subject: [PATCH 10/59] Add name validation to snippets --- .../common/bindings/SnippetDrawer.svelte | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index f3a605bed4..d660f295c0 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -17,6 +17,7 @@ export const hide = () => drawer.hide() const roughValidNameRegex = /^[_$A-Z\xA0-\uFFFF][_$A-Z0-9\xA0-\uFFFF]*$/i + const firstCharNumberRegex = /^[0-9].*$/ let drawer let name = "" @@ -26,7 +27,7 @@ $: name = snippet?.name || "MySnippet" $: code = snippet?.code ? encodeJSBinding(snippet.code) : "" $: rawJS = decodeJSBinding(code) - $: nameValid = validateName(name) + $: nameError = validateName(name) const saveSnippet = async () => { await snippetStore.saveSnippet({ @@ -48,14 +49,20 @@ // try executing it and see if it's valid JS. The initial regex prevents // against any potential XSS attacks here. const validateName = name => { + if (!name?.length) { + return "Name is required" + } + if (firstCharNumberRegex.test(name)) { + return "Can't start with a number" + } if (!roughValidNameRegex.test(name)) { - return false + return "No special characters or spaces" } const js = `(function ${name}(){return true})()` try { - return eval(js) === true + return eval(js) === true ? null : "Invalid name" } catch (error) { - return false + return "Invalid name" } } @@ -65,14 +72,11 @@ {#if snippet} {snippet.name} {:else} -
    +
    Name - {#if !nameValid} - + {#if nameError} + Delete {/if} - @@ -117,9 +121,12 @@ align-items: center; position: relative; } - .name.invalid :global(input) { + .name :global(input) { width: 200px; } + .name.invalid :global(input) { + padding-right: 32px; + } .name :global(.icon) { position: absolute; right: 10px; From 4d271ccb5395986167cd76e4dd4ef4d6c155e615 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 6 Mar 2024 19:07:16 +0000 Subject: [PATCH 11/59] Add real snippet saving and fix snippet evaluation in client apps --- .../common/bindings/SnippetDrawer.svelte | 42 +++++++++--- .../builder/src/stores/builder/snippets.js | 67 ++++++------------- .../client/src/stores/derived/snippets.js | 6 +- 3 files changed, 55 insertions(+), 60 deletions(-) diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index d660f295c0..05a397a3c7 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -6,6 +6,7 @@ Icon, AbsTooltip, TooltipType, + notifications, } from "@budibase/bbui" import BindingPanel from "components/common/bindings/BindingPanel.svelte" import { decodeJSBinding, encodeJSBinding } from "@budibase/string-templates" @@ -22,24 +23,38 @@ let drawer let name = "" let code = "" + let loading = false $: key = snippet?.name $: name = snippet?.name || "MySnippet" $: code = snippet?.code ? encodeJSBinding(snippet.code) : "" $: rawJS = decodeJSBinding(code) - $: nameError = validateName(name) + $: nameError = validateName(name, $snippetStore) const saveSnippet = async () => { - await snippetStore.saveSnippet({ - name, - code: rawJS, - }) - drawer.hide() + loading = true + try { + await snippetStore.saveSnippet({ + name, + code: rawJS, + }) + drawer.hide() + notifications.success(`Snippet ${name} saved`) + } catch (error) { + notifications.error("Error saving snippet") + } + loading = false } const deleteSnippet = async () => { - await snippetStore.deleteSnippet(snippet.name) - drawer.hide() + loading = true + try { + await snippetStore.deleteSnippet(snippet.name) + drawer.hide() + } catch (error) { + notifications.error("Error deleting snippet") + } + loading = false } // Validating function names is not as easy as you think. A simple regex does @@ -48,7 +63,7 @@ // Instead, we can run a simple regex to roughly validate it, then basically // try executing it and see if it's valid JS. The initial regex prevents // against any potential XSS attacks here. - const validateName = name => { + const validateName = (name, snippets) => { if (!name?.length) { return "Name is required" } @@ -58,6 +73,9 @@ if (!roughValidNameRegex.test(name)) { return "No special characters or spaces" } + if (snippets.some(snippet => snippet.name === name)) { + return "That name is already in use" + } const js = `(function ${name}(){return true})()` try { return eval(js) === true ? null : "Invalid name" @@ -89,9 +107,11 @@ {#if snippet} - + {/if} - diff --git a/packages/builder/src/stores/builder/snippets.js b/packages/builder/src/stores/builder/snippets.js index 9c28536298..624721317a 100644 --- a/packages/builder/src/stores/builder/snippets.js +++ b/packages/builder/src/stores/builder/snippets.js @@ -1,58 +1,33 @@ -import { writable } from "svelte/store" - -const EXAMPLE_SNIPPETS = [ - { - name: "Square", - code: ` - return function(num) { - return num * num - } - `, - }, - { - name: "HelloWorld", - code: ` - return "Hello, world!" - `, - }, - { - name: "Colorful", - code: ` - let a = null - let b = "asdasd" - let c = 123123 - let d = undefined - let e = [1, 2, 3] - let f = { foo: "bar" } - let g = Math.round(1.234) - if (a === b) { - return c ?? e - } - return d || f - // comment - let h = 1 + 2 + 3 * 3 - let i = true - let j = false - `, - }, -] +import { writable, get } from "svelte/store" +import { API } from "api" +import { appStore } from "./app" const createSnippetStore = () => { - const store = writable(EXAMPLE_SNIPPETS) + const store = writable([]) const syncMetadata = metadata => { - store.set(metadata?.snippets || EXAMPLE_SNIPPETS) + store.set(metadata?.snippets || []) } - const saveSnippet = updatedSnippet => { - store.update(state => [ - ...state.filter(snippet => snippet.name !== updatedSnippet.name), + const saveSnippet = async updatedSnippet => { + const snippets = [ + ...get(store).filter(snippet => snippet.name !== updatedSnippet.name), updatedSnippet, - ]) + ] + const app = await API.saveAppMetadata({ + appId: get(appStore).appId, + metadata: { snippets }, + }) + syncMetadata(app) } - const deleteSnippet = snippetName => { - store.update(state => state.filter(snippet => snippet.name !== snippetName)) + const deleteSnippet = async snippetName => { + const snippets = get(store).filter(snippet => snippet.name !== snippetName) + const app = await API.saveAppMetadata({ + appId: get(appStore).appId, + metadata: { snippets }, + }) + syncMetadata(app) } return { diff --git a/packages/client/src/stores/derived/snippets.js b/packages/client/src/stores/derived/snippets.js index 55e7276edc..3f11c040bd 100644 --- a/packages/client/src/stores/derived/snippets.js +++ b/packages/client/src/stores/derived/snippets.js @@ -1,6 +1,6 @@ import { derived } from "svelte/store" import { appStore } from "../app.js" -export const snippets = derived(appStore, $appStore => $appStore.snippets) - -snippets.subscribe(console.log) +export const snippets = derived(appStore, $appStore => { + return $appStore?.application?.snippets || [] +}) From cb7f33de77006c7db9a2b040c8894b65bfad3a74 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 6 Mar 2024 20:27:46 +0000 Subject: [PATCH 12/59] Add automatic naming of snippets --- .../common/bindings/ClientBindingPanel.svelte | 4 +- .../common/bindings/ServerBindingPanel.svelte | 4 +- .../common/bindings/SnippetDrawer.svelte | 12 ++-- packages/builder/src/helpers/duplicate.js | 50 +++++++++++++++ .../src/helpers/tests/duplicate.test.js | 63 ++++++++++++++++++- packages/builder/src/stores/builder/index.js | 6 +- .../builder/src/stores/builder/snippets.js | 4 +- .../builder/src/stores/builder/websocket.js | 4 +- 8 files changed, 130 insertions(+), 17 deletions(-) diff --git a/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte b/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte index d3e80cf696..efdaa51dba 100644 --- a/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/ClientBindingPanel.svelte @@ -1,6 +1,6 @@ From 79ae159329718780cf755225b4d2cbabc2ab51fb Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 11 Mar 2024 21:10:53 +0000 Subject: [PATCH 18/59] Add code mirror completions for snippets --- .../common/CodeEditor/CodeEditor.svelte | 67 +++++++++++++++---- .../src/components/common/CodeEditor/index.js | 38 +++++++++++ .../common/bindings/BindingPanel.svelte | 2 + 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index b63c646ed4..1ddc3d802a 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -83,8 +83,8 @@ }) } - // For handlebars only. - const bindStyle = new MatchDecorator({ + // Match decoration for HBS bindings + const hbsMatchDeco = new MatchDecorator({ regexp: FIND_ANY_HBS_REGEX, decoration: () => { return Decoration.mark({ @@ -95,12 +95,35 @@ }) }, }) - - let plugin = ViewPlugin.define( + const hbsMatchDecoPlugin = ViewPlugin.define( view => ({ - decorations: bindStyle.createDeco(view), + decorations: hbsMatchDeco.createDeco(view), update(u) { - this.decorations = bindStyle.updateDeco(u, this.decorations) + this.decorations = hbsMatchDeco.updateDeco(u, this.decorations) + }, + }), + { + decorations: v => v.decorations, + } + ) + + // Match decoration for snippets + const snippetMatchDeco = new MatchDecorator({ + regexp: /snippets.[^\s(]+/g, + decoration: () => { + return Decoration.mark({ + tag: "span", + attributes: { + class: "snippet-wrap", + }, + }) + }, + }) + const snippetMatchDecoPlugin = ViewPlugin.define( + view => ({ + decorations: snippetMatchDeco.createDeco(view), + update(u) { + this.decorations = snippetMatchDeco.updateDeco(u, this.decorations) }, }), { @@ -142,7 +165,6 @@ const buildBaseExtensions = () => { return [ - ...(mode.name === "handlebars" ? [plugin] : []), drawSelection(), dropCursor(), bracketMatching(), @@ -165,7 +187,10 @@ override: [...completions], closeOnBlur: true, icons: false, - optionClass: () => "autocomplete-option", + optionClass: completion => + completion.simple + ? "autocomplete-option-simple" + : "autocomplete-option", }) ) complete.push( @@ -191,18 +216,23 @@ view.dispatch(tr) return true } - return false }) ) } + // JS only plugins if (mode.name === "javascript") { + complete.push(snippetMatchDecoPlugin) complete.push(javascript()) if (!readonly) { complete.push(highlightWhitespace()) } } + // HBS only plugins + else { + complete.push(hbsMatchDecoPlugin) + } if (placeholder) { complete.push(placeholderFn(placeholder)) @@ -376,9 +406,12 @@ font-style: italic; } - /* Highlight bindings */ + /* Highlight bindings and snippets */ .code-editor :global(.binding-wrap) { - color: var(--spectrum-global-color-blue-700); + color: var(--spectrum-global-color-blue-700) !important; + } + .code-editor :global(.snippet-wrap *) { + color: #61afef !important; } /* Completion popover */ @@ -407,7 +440,8 @@ } /* Completion item container */ - .code-editor :global(.autocomplete-option) { + .code-editor :global(.autocomplete-option), + .code-editor :global(.autocomplete-option-simple) { padding: var(--spacing-s) var(--spacing-m) !important; padding-left: calc(16px + 2 * var(--spacing-m)) !important; display: flex; @@ -415,9 +449,13 @@ align-items: center; color: var(--spectrum-alias-text-color); } + .code-editor :global(.autocomplete-option-simple) { + padding-left: var(--spacing-s) !important; + } /* Highlighted completion item */ - .code-editor :global(.autocomplete-option[aria-selected]) { + .code-editor :global(.autocomplete-option[aria-selected]), + .code-editor :global(.autocomplete-option-simple[aria-selected]) { background: var(--spectrum-global-color-blue-400); color: white; } @@ -433,6 +471,9 @@ font-family: var(--font-sans); text-transform: capitalize; } + .code-editor :global(.autocomplete-option-simple .cm-completionLabel) { + text-transform: none; + } /* Completion item type */ .code-editor :global(.autocomplete-option .cm-completionDetail) { diff --git a/packages/builder/src/components/common/CodeEditor/index.js b/packages/builder/src/components/common/CodeEditor/index.js index c104267aa4..14c0084f3a 100644 --- a/packages/builder/src/components/common/CodeEditor/index.js +++ b/packages/builder/src/components/common/CodeEditor/index.js @@ -102,6 +102,29 @@ export const getHelperCompletions = mode => { }, []) } +export const snippetAutoComplete = snippets => { + return function myCompletions(context) { + if (!snippets?.length) { + return null + } + const word = context.matchBefore(/\w*/) + if (word.from == word.to && !context.explicit) { + return null + } + return { + from: word.from, + options: snippets.map(snippet => ({ + label: `snippets.${snippet.name}`, + type: "text", + simple: true, + apply: (view, completion, from, to) => { + insertSnippet(view, from, to, completion.label) + }, + })), + } + } +} + const bindingFilter = (options, query) => { return options.filter(completion => { const section_parsed = completion.section.name.toLowerCase() @@ -247,6 +270,21 @@ export const insertBinding = (view, from, to, text, mode) => { }) } +export const insertSnippet = (view, from, to, text, mode) => { + const parsedInsert = `${text}()` + let cursorPos = from + parsedInsert.length - 1 + view.dispatch({ + changes: { + from, + to, + insert: parsedInsert, + }, + selection: { + anchor: cursorPos, + }, + }) +} + export const bindingsToCompletions = (bindings, mode) => { const bindingByCategory = groupBy(bindings, "category") const categoryMeta = bindings?.reduce((acc, ele) => { diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 96a2187755..01c2f5d55b 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -19,6 +19,7 @@ getHelperCompletions, jsAutocomplete, hbAutocomplete, + snippetAutoComplete, EditorModes, bindingsToCompletions, } from "../CodeEditor" @@ -98,6 +99,7 @@ ...bindingCompletions, ...getHelperCompletions(EditorModes.JS), ]), + snippetAutoComplete(snippets), ] const getModeOptions = (allowHBS, allowJS) => { From 10c581c3be7960aa3c063ec490589802f4e01f12 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 12 Mar 2024 15:39:26 +0000 Subject: [PATCH 19/59] Fetch snippets from app doc when creating a new isolate --- .../jsRunner/bundles/snippets.ivm.bundle.js | 4 +- .../server/src/jsRunner/bundles/snippets.ts | 7 ++- packages/server/src/jsRunner/index.ts | 47 +++++++++++++------ .../server/src/jsRunner/vm/isolated-vm.ts | 12 +++-- packages/types/src/documents/app/app.ts | 3 +- packages/types/src/documents/app/index.ts | 1 + packages/types/src/documents/app/snippet.ts | 4 ++ 7 files changed, 52 insertions(+), 26 deletions(-) create mode 100644 packages/types/src/documents/app/snippet.ts diff --git a/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js index fc49a121a4..5adb19eaf7 100644 --- a/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js +++ b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js @@ -1,3 +1,3 @@ -"use strict";var snippets=(()=>{var u=Object.create;var i=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var m=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var l=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports),W=(e,n)=>{for(var r in n)i(e,r,{get:n[r],enumerable:!0})},o=(e,n,r,p)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of d(n))!x.call(e,t)&&t!==r&&i(e,t,{get:()=>n[t],enumerable:!(p=c(n,t))||p.enumerable});return e};var g=(e,n,r)=>(r=e!=null?u(m(e)):{},o(n||!e||!e.__esModule?i(r,"default",{value:e,enumerable:!0}):r,e)),v=e=>o(i({},"__esModule",{value:!0}),e);var a=l((_,f)=>{f.exports.iifeWrapper=e=>`(function(){ +"use strict";var snippets=(()=>{var u=Object.create;var p=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var m=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var l=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports),W=(e,n)=>{for(var i in n)p(e,i,{get:n[i],enumerable:!0})},o=(e,n,i,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of d(n))!x.call(e,r)&&r!==i&&p(e,r,{get:()=>n[r],enumerable:!(t=c(n,r))||t.enumerable});return e};var g=(e,n,i)=>(i=e!=null?u(m(e)):{},o(n||!e||!e.__esModule?p(i,"default",{value:e,enumerable:!0}):i,e)),v=e=>o(p({},"__esModule",{value:!0}),e);var a=l((P,f)=>{f.exports.iifeWrapper=e=>`(function(){ ${e} -})();`});var y={};W(y,{default:()=>w});var s=g(a()),w=new Proxy({},{get:function(e,n){let r=($("snippets")||[]).find(p=>p.name===n);return[eval][0]((0,s.iifeWrapper)(r.code))}});return v(y);})(); +})();`});var y={};W(y,{default:()=>w});var s=g(a()),w=new Proxy({},{get:function(e,n){let i=(snippetDefinitions||[]).find(t=>t.name===n);return[eval][0]((0,s.iifeWrapper)(i.code))}});return v(y);})(); diff --git a/packages/server/src/jsRunner/bundles/snippets.ts b/packages/server/src/jsRunner/bundles/snippets.ts index 861bacaec5..f473aaf7b4 100644 --- a/packages/server/src/jsRunner/bundles/snippets.ts +++ b/packages/server/src/jsRunner/bundles/snippets.ts @@ -6,14 +6,13 @@ export default new Proxy( {}, { get: function (_, name) { - // Get snippet definitions from global context, get the correct snippet - // then eval the JS. This will error if the snippet doesn't exist, but - // that's intended. + // Snippet definitions are injected to the isolate global scope before + // this bundle is loaded, so we can access it from there. // https://esbuild.github.io/content-types/#direct-eval for info on why // eval is being called this way. // @ts-ignore // eslint-disable-next-line no-undef - const snippet = ($("snippets") || []).find(x => x.name === name) + const snippet = (snippetDefinitions || []).find(x => x.name === name) return [eval][0](iifeWrapper(snippet.code)) }, } diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index 67aaffae7f..d97fa4cc94 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -8,29 +8,48 @@ import { import { context, logging } from "@budibase/backend-core" import tracer from "dd-trace" import { IsolatedVM } from "./vm" +import { App, DocumentType, Snippet, VM } from "@budibase/types" + +async function getIsolate(ctx: any): Promise { + // Reuse the existing isolate if one exists + if (ctx?.vm) { + return ctx.vm + } + + // Get snippets to build into new isolate, if inside app context + let snippets: Snippet[] | undefined + const db = context.getAppDB() + if (db) { + console.log("READ APP METADATA") + const app = await db.get(DocumentType.APP_METADATA) + snippets = app.snippets + } + + // Build a new isolate + return new IsolatedVM({ + memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, + invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, + isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS, + }) + .withHelpers() + .withSnippets(snippets) +} export function init() { setJSRunner((js: string, ctx: Record) => { - return tracer.trace("runJS", {}, span => { + return tracer.trace("runJS", {}, async span => { try { + // Reuse an existing isolate from context, or make a new one const bbCtx = context.getCurrentContext() - - const vm = bbCtx?.vm - ? bbCtx.vm - : new IsolatedVM({ - memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, - invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, - isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS, - }) - .withHelpers() - .withSnippets() - + const vm = await getIsolate(bbCtx) if (bbCtx) { - // If we have a context, we want to persist it to reuse the isolate bbCtx.vm = vm } + + // Strip helpers (an array) and snippets (a proxy isntance) as these + // will not survive the isolated-vm barrier const { helpers, snippets, ...rest } = ctx - return vm.withContext(rest, () => vm.execute(js)) + return vm.withContext(rest, () => vm!.execute(js)) } catch (error: any) { if (error.message === "Script execution timed out.") { throw new JsErrorTimeout() diff --git a/packages/server/src/jsRunner/vm/isolated-vm.ts b/packages/server/src/jsRunner/vm/isolated-vm.ts index fb45abf5df..f18888895f 100644 --- a/packages/server/src/jsRunner/vm/isolated-vm.ts +++ b/packages/server/src/jsRunner/vm/isolated-vm.ts @@ -6,7 +6,7 @@ import crypto from "crypto" import querystring from "querystring" import { BundleType, loadBundle } from "../bundles" -import { VM } from "@budibase/types" +import { Snippet, VM } from "@budibase/types" import { iifeWrapper } from "@budibase/string-templates" import environment from "../../environment" @@ -98,11 +98,13 @@ export class IsolatedVM implements VM { return this } - withSnippets() { + withSnippets(snippets?: Snippet[]) { const snippetsSource = loadBundle(BundleType.SNIPPETS) - const script = this.isolate.compileScriptSync( - `${snippetsSource};snippets=snippets.default;` - ) + const script = this.isolate.compileScriptSync(` + const snippetDefinitions = ${JSON.stringify(snippets || [])}; + ${snippetsSource}; + snippets = snippets.default; + `) script.runSync(this.vm, { timeout: this.invocationTimeout, release: false }) new Promise(() => { script.release() diff --git a/packages/types/src/documents/app/app.ts b/packages/types/src/documents/app/app.ts index ae4f3fa6da..3b7f481253 100644 --- a/packages/types/src/documents/app/app.ts +++ b/packages/types/src/documents/app/app.ts @@ -1,4 +1,4 @@ -import { User, Document, Plugin } from "../" +import { User, Document, Plugin, Snippet } from "../" import { SocketSession } from "../../sdk" export type AppMetadataErrors = { [key: string]: string[] } @@ -26,6 +26,7 @@ export interface App extends Document { automations?: AutomationSettings usedPlugins?: Plugin[] upgradableVersion?: string + snippets?: Snippet[] } export interface AppInstance { diff --git a/packages/types/src/documents/app/index.ts b/packages/types/src/documents/app/index.ts index b81c9e36ac..a58b708de3 100644 --- a/packages/types/src/documents/app/index.ts +++ b/packages/types/src/documents/app/index.ts @@ -14,3 +14,4 @@ export * from "./backup" export * from "./webhook" export * from "./links" export * from "./component" +export * from "./snippet" diff --git a/packages/types/src/documents/app/snippet.ts b/packages/types/src/documents/app/snippet.ts new file mode 100644 index 0000000000..1b8433b32e --- /dev/null +++ b/packages/types/src/documents/app/snippet.ts @@ -0,0 +1,4 @@ +export interface Snippet { + name: string + code: string +} From 16ce5ac65e4c9347d2329a4adf3ba9f3ae731821 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 12 Mar 2024 17:02:01 +0000 Subject: [PATCH 20/59] Update how snippets are fetched and enriched into context, because HBS helpers can't be async --- .../backend-core/src/context/mainContext.ts | 23 +++++++++- packages/backend-core/src/context/types.ts | 3 +- .../api/controllers/row/ExternalRequest.ts | 23 ++++++---- .../src/api/controllers/row/staticFormula.ts | 6 +-- packages/server/src/db/linkedRows/index.ts | 3 +- packages/server/src/jsRunner/index.ts | 42 ++++++------------- .../src/utilities/rowProcessor/index.ts | 2 +- .../src/utilities/rowProcessor/utils.ts | 11 +++-- 8 files changed, 66 insertions(+), 47 deletions(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index ae86695168..c45536c2e2 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -10,7 +10,7 @@ import { StaticDatabases, DEFAULT_TENANT_ID, } from "../constants" -import { Database, IdentityContext } from "@budibase/types" +import { Database, IdentityContext, Snippet, App } from "@budibase/types" import { ContextMap } from "./types" let TEST_APP_ID: string | null = null @@ -281,6 +281,27 @@ export function doInScimContext(task: any) { return newContext(updates, task) } +export async function ensureSnippetContext() { + const ctx = getCurrentContext() + + // If we've already added snippets to context, continue + if (!ctx || ctx.snippets) { + return + } + + // Otherwise get snippets for this app and update context + let snippets: Snippet[] | undefined + const db = getAppDB() + if (db) { + const app = await db.get(DocumentType.APP_METADATA) + snippets = app.snippets + } + + // Always set snippets to a non-null value so that we can tell we've attempted + // to load snippets + ctx.snippets = snippets || [] +} + export function getEnvironmentVariables() { const context = Context.get() if (!context.environmentVariables) { diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index 6fb9f44fad..3d5e106ed3 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -1,4 +1,4 @@ -import { IdentityContext, VM } from "@budibase/types" +import { IdentityContext, Snippet, VM } from "@budibase/types" import { ExecutionTimeTracker } from "../timers" // keep this out of Budibase types, don't want to expose context info @@ -12,4 +12,5 @@ export type ContextMap = { isMigrating?: boolean jsExecutionTracker?: ExecutionTimeTracker vm?: VM + snippets?: Snippet[] } diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index 685af4e98e..814b57567f 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -437,11 +437,11 @@ export class ExternalRequest { return { row: newRow, manyRelationships } } - processRelationshipFields( + async processRelationshipFields( table: Table, row: Row, relationships: RelationshipsJson[] - ): Row { + ): Promise { for (let relationship of relationships) { const linkedTable = this.tables[relationship.tableName] if (!linkedTable || !row[relationship.column]) { @@ -457,7 +457,7 @@ export class ExternalRequest { } // process additional types relatedRow = processDates(table, relatedRow) - relatedRow = processFormulas(linkedTable, relatedRow) + relatedRow = await processFormulas(linkedTable, relatedRow) row[relationship.column][key] = relatedRow } } @@ -521,7 +521,7 @@ export class ExternalRequest { return rows } - outputProcessing( + async outputProcessing( rows: Row[] = [], table: Table, relationships: RelationshipsJson[] @@ -561,9 +561,12 @@ export class ExternalRequest { } // make sure all related rows are correct - let finalRowArray = Object.values(finalRows).map(row => - this.processRelationshipFields(table, row, relationships) - ) + let finalRowArray = [] + for (let row of Object.values(finalRows)) { + finalRowArray.push( + await this.processRelationshipFields(table, row, relationships) + ) + } // process some additional types finalRowArray = processDates(table, finalRowArray) @@ -934,7 +937,11 @@ export class ExternalRequest { processed.manyRelationships ) } - const output = this.outputProcessing(responseRows, table, relationships) + const output = await this.outputProcessing( + responseRows, + table, + relationships + ) // if reading it'll just be an array of rows, return whole thing if (operation === Operation.READ) { return ( diff --git a/packages/server/src/api/controllers/row/staticFormula.ts b/packages/server/src/api/controllers/row/staticFormula.ts index 0ea8b3560e..a75a6cd2cc 100644 --- a/packages/server/src/api/controllers/row/staticFormula.ts +++ b/packages/server/src/api/controllers/row/staticFormula.ts @@ -110,7 +110,7 @@ export async function updateAllFormulasInTable(table: Table) { (enriched: Row) => enriched._id === row._id ) if (enrichedRow) { - const processed = processFormulas(table, cloneDeep(row), { + const processed = await processFormulas(table, cloneDeep(row), { dynamic: false, contextRows: [enrichedRow], }) @@ -143,7 +143,7 @@ export async function finaliseRow( squash: false, })) as Row // use enriched row to generate formulas for saving, specifically only use as context - row = processFormulas(table, row, { + row = await processFormulas(table, row, { dynamic: false, contextRows: [enrichedRow], }) @@ -179,7 +179,7 @@ export async function finaliseRow( const response = await db.put(row) // for response, calculate the formulas for the enriched row enrichedRow._rev = response.rev - enrichedRow = processFormulas(table, enrichedRow, { + enrichedRow = await processFormulas(table, enrichedRow, { dynamic: false, }) // this updates the related formulas in other rows based on the relations to this row diff --git a/packages/server/src/db/linkedRows/index.ts b/packages/server/src/db/linkedRows/index.ts index 7af3f9392f..513e0c0df2 100644 --- a/packages/server/src/db/linkedRows/index.ts +++ b/packages/server/src/db/linkedRows/index.ts @@ -202,7 +202,8 @@ export async function attachFullLinkedDocs( table => table._id === linkedTableId ) if (linkedTable) { - row[link.fieldName].push(processFormulas(linkedTable, linkedRow)) + const processed = await processFormulas(linkedTable, linkedRow) + row[link.fieldName].push(processed) } } } diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index d97fa4cc94..ff39ce0666 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -8,40 +8,24 @@ import { import { context, logging } from "@budibase/backend-core" import tracer from "dd-trace" import { IsolatedVM } from "./vm" -import { App, DocumentType, Snippet, VM } from "@budibase/types" - -async function getIsolate(ctx: any): Promise { - // Reuse the existing isolate if one exists - if (ctx?.vm) { - return ctx.vm - } - - // Get snippets to build into new isolate, if inside app context - let snippets: Snippet[] | undefined - const db = context.getAppDB() - if (db) { - console.log("READ APP METADATA") - const app = await db.get(DocumentType.APP_METADATA) - snippets = app.snippets - } - - // Build a new isolate - return new IsolatedVM({ - memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, - invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, - isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS, - }) - .withHelpers() - .withSnippets(snippets) -} export function init() { setJSRunner((js: string, ctx: Record) => { - return tracer.trace("runJS", {}, async span => { + return tracer.trace("runJS", {}, span => { try { // Reuse an existing isolate from context, or make a new one const bbCtx = context.getCurrentContext() - const vm = await getIsolate(bbCtx) + const vm = + bbCtx?.vm || + new IsolatedVM({ + memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, + invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, + isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS, + }) + .withHelpers() + .withSnippets(bbCtx?.snippets) + + // Persist isolate in context so we can reuse it if (bbCtx) { bbCtx.vm = vm } @@ -49,7 +33,7 @@ export function init() { // Strip helpers (an array) and snippets (a proxy isntance) as these // will not survive the isolated-vm barrier const { helpers, snippets, ...rest } = ctx - return vm.withContext(rest, () => vm!.execute(js)) + return vm.withContext(rest, () => vm.execute(js)) } catch (error: any) { if (error.message === "Script execution timed out.") { throw new JsErrorTimeout() diff --git a/packages/server/src/utilities/rowProcessor/index.ts b/packages/server/src/utilities/rowProcessor/index.ts index e3ca576fb7..d956a94d0b 100644 --- a/packages/server/src/utilities/rowProcessor/index.ts +++ b/packages/server/src/utilities/rowProcessor/index.ts @@ -245,7 +245,7 @@ export async function outputProcessing( } // process formulas after the complex types had been processed - enriched = processFormulas(table, enriched, { dynamic: true }) + enriched = await processFormulas(table, enriched, { dynamic: true }) if (opts.squash) { enriched = (await linkRows.squashLinksToPrimaryDisplay( diff --git a/packages/server/src/utilities/rowProcessor/utils.ts b/packages/server/src/utilities/rowProcessor/utils.ts index d0fc82b3ee..7292886084 100644 --- a/packages/server/src/utilities/rowProcessor/utils.ts +++ b/packages/server/src/utilities/rowProcessor/utils.ts @@ -10,6 +10,8 @@ import { FieldType, } from "@budibase/types" import tracer from "dd-trace" +import { context } from "@budibase/backend-core" +import { getCurrentContext } from "@budibase/backend-core/src/context" interface FormulaOpts { dynamic?: boolean @@ -44,16 +46,19 @@ export function fixAutoColumnSubType( /** * Looks through the rows provided and finds formulas - which it then processes. */ -export function processFormulas( +export async function processFormulas( table: Table, inputRows: T, { dynamic, contextRows }: FormulaOpts = { dynamic: true } -): T { - return tracer.trace("processFormulas", {}, span => { +): Promise { + return tracer.trace("processFormulas", {}, async span => { const numRows = Array.isArray(inputRows) ? inputRows.length : 1 span?.addTags({ table_id: table._id, dynamic, numRows }) const rows = Array.isArray(inputRows) ? inputRows : [inputRows] if (rows) { + // Ensure we have snippet context + await context.ensureSnippetContext() + for (let [column, schema] of Object.entries(table.schema)) { if (schema.type !== FieldType.FORMULA) { continue From fda71de7c2147e93277f7bacc84cf65ac30b5d25 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 12 Mar 2024 19:07:38 +0000 Subject: [PATCH 21/59] Remove unused import --- packages/builder/src/components/common/CodeEditor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/common/CodeEditor/index.js b/packages/builder/src/components/common/CodeEditor/index.js index 14c0084f3a..b93c95b944 100644 --- a/packages/builder/src/components/common/CodeEditor/index.js +++ b/packages/builder/src/components/common/CodeEditor/index.js @@ -270,7 +270,7 @@ export const insertBinding = (view, from, to, text, mode) => { }) } -export const insertSnippet = (view, from, to, text, mode) => { +export const insertSnippet = (view, from, to, text) => { const parsedInsert = `${text}()` let cursorPos = from + parsedInsert.length - 1 view.dispatch({ From 28d938ba3e19c67470a4dea108287e2f6c7dc0f5 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 12 Mar 2024 19:09:32 +0000 Subject: [PATCH 22/59] Lint --- packages/server/src/utilities/rowProcessor/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/server/src/utilities/rowProcessor/utils.ts b/packages/server/src/utilities/rowProcessor/utils.ts index 7292886084..8201680f13 100644 --- a/packages/server/src/utilities/rowProcessor/utils.ts +++ b/packages/server/src/utilities/rowProcessor/utils.ts @@ -11,7 +11,6 @@ import { } from "@budibase/types" import tracer from "dd-trace" import { context } from "@budibase/backend-core" -import { getCurrentContext } from "@budibase/backend-core/src/context" interface FormulaOpts { dynamic?: boolean From 3b54daf2c8d798c44b8fced765c5c6a70ba96649 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 12 Mar 2024 21:40:48 +0000 Subject: [PATCH 23/59] Add snippet context before executing automations --- packages/backend-core/src/context/mainContext.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index c45536c2e2..f575d6950d 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -129,7 +129,10 @@ export async function doInAutomationContext(params: { appId: params.appId, automationId: params.automationId, }, - params.task + async () => { + await ensureSnippetContext() + return await params.task() + } ) } From 20f4c5a77d993a38c16daabc6cc0cb9398668695 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 12 Mar 2024 21:41:00 +0000 Subject: [PATCH 24/59] Add snippet context before testing automations manually --- packages/server/src/automations/triggers.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/automations/triggers.ts b/packages/server/src/automations/triggers.ts index 08e3199a11..3336211c5d 100644 --- a/packages/server/src/automations/triggers.ts +++ b/packages/server/src/automations/triggers.ts @@ -112,6 +112,7 @@ export async function externalTrigger( const data: AutomationData = { automation, event: params as any } if (getResponses) { + await context.ensureSnippetContext() data.event = { ...data.event, appId: context.getAppId(), From 70821182fe1b26673a9fb289295ef478d3f8a7ae Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 09:15:33 +0000 Subject: [PATCH 25/59] Update automation context to simplify applying snippet context --- packages/backend-core/src/context/mainContext.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index f575d6950d..9d4cc9096d 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -122,17 +122,14 @@ export async function doInAutomationContext(params: { automationId: string task: () => T }): Promise { - const tenantId = getTenantIDFromAppID(params.appId) + await ensureSnippetContext() return newContext( { - tenantId, + tenantId: getTenantIDFromAppID(params.appId), appId: params.appId, automationId: params.automationId, }, - async () => { - await ensureSnippetContext() - return await params.task() - } + params.task ) } From 1eafd5e8437ca66096f54ff017f62530528f3bb7 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 09:22:13 +0000 Subject: [PATCH 26/59] Fix issue with drawer positioning when nesting drawers with no target --- packages/bbui/src/Drawer/Drawer.svelte | 66 ++++++++++++-------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 8bb11b833a..04e678c4e5 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -172,43 +172,37 @@ {#if visible} -
    -
    -
    0} - class:modal={$modal} - transition:drawerSlide|local - {style} - > -
    - {#if $$slots.title} - - {:else} -
    {title || "Bindings"}
    +
    +
    0} + class:modal={$modal} + transition:drawerSlide|local + {style} + > +
    + {#if $$slots.title} + + {:else} +
    {title || "Bindings"}
    + {/if} +
    + + + {#if $resizable} + modal.set(!$modal)} + > + + {/if} -
    - - - {#if $resizable} - modal.set(!$modal)} - > - - - {/if} -
    -
    - -
    -
    +
    +
    + +
    {/if} From c25ea7a9d7e3e3225735aad706207163777408ee Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 09:42:31 +0000 Subject: [PATCH 27/59] Fix external triggers not getting snippet context --- packages/server/src/automations/triggers.ts | 1 - packages/server/src/threads/automation.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/automations/triggers.ts b/packages/server/src/automations/triggers.ts index 3336211c5d..08e3199a11 100644 --- a/packages/server/src/automations/triggers.ts +++ b/packages/server/src/automations/triggers.ts @@ -112,7 +112,6 @@ export async function externalTrigger( const data: AutomationData = { automation, event: params as any } if (getResponses) { - await context.ensureSnippetContext() data.event = { ...data.event, appId: context.getAppId(), diff --git a/packages/server/src/threads/automation.ts b/packages/server/src/threads/automation.ts index a4938bb138..4e33fadce6 100644 --- a/packages/server/src/threads/automation.ts +++ b/packages/server/src/threads/automation.ts @@ -625,6 +625,7 @@ export async function executeInThread(job: Job) { }) return await context.doInAppContext(appId, async () => { + await context.ensureSnippetContext() const envVars = await sdkUtils.getEnvironmentVariables() // put into automation thread for whole context return await context.doInEnvironmentContext(envVars, async () => { From 0ddf48f7ffc39de96c3ae1f8082a7574b8291ae2 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 09:54:27 +0000 Subject: [PATCH 28/59] Update automation page to use full width drawers like the design section does --- .../pages/builder/app/[application]/automation/_layout.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte index c4ee060149..57180625b1 100644 --- a/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/automation/_layout.svelte @@ -40,7 +40,7 @@
    -
    +
    {#if $automationStore.automations?.length} {:else} From 2d12a1a8fa2aeca8e68d9643c17d971eac7792cd Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 11:48:17 +0000 Subject: [PATCH 29/59] Add server-side validation for snippet names --- .../common/bindings/SnippetDrawer.svelte | 21 +++++++------------ .../server/src/api/routes/utils/validators.ts | 17 +++++++++++++++ packages/shared-core/src/constants/index.ts | 1 + 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index e7dd5c7a22..3badf0d8c3 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -13,13 +13,13 @@ import { snippets } from "stores/builder" import { getSequentialName } from "helpers/duplicate" import ConfirmDialog from "components/common/ConfirmDialog.svelte" + import { ValidSnippetNameRegex } from "@budibase/shared-core" export let snippet export const show = () => drawer.show() export const hide = () => drawer.hide() - const roughValidNameRegex = /^[_$A-Z\xA0-\uFFFF][_$A-Z0-9\xA0-\uFFFF]*$/i const firstCharNumberRegex = /^[0-9].*$/ let drawer @@ -43,7 +43,7 @@ drawer.hide() notifications.success(`Snippet ${newSnippet.name} saved`) } catch (error) { - notifications.error("Error saving snippet") + notifications.error(error.message || "Error saving snippet") } loading = false } @@ -69,21 +69,16 @@ if (!name?.length) { return "Name is required" } - if (firstCharNumberRegex.test(name)) { - return "Can't start with a number" - } - if (!roughValidNameRegex.test(name)) { - return "No special characters or spaces" - } if (snippets.some(snippet => snippet.name === name)) { return "That name is already in use" } - const js = `(function ${name}(){return true})()` - try { - return eval(js) === true ? null : "Invalid name" - } catch (error) { - return "Invalid name" + if (firstCharNumberRegex.test(name)) { + return "Can't start with a number" } + if (!ValidSnippetNameRegex.test(name)) { + return "No special characters or spaces" + } + return null } diff --git a/packages/server/src/api/routes/utils/validators.ts b/packages/server/src/api/routes/utils/validators.ts index 55766cd120..424d0d6c79 100644 --- a/packages/server/src/api/routes/utils/validators.ts +++ b/packages/server/src/api/routes/utils/validators.ts @@ -2,6 +2,7 @@ import { auth, permissions } from "@budibase/backend-core" import { DataSourceOperation } from "../../../constants" import { WebhookActionType } from "@budibase/types" import Joi from "joi" +import { ValidSnippetNameRegex } from "@budibase/shared-core" const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("") const OPTIONAL_NUMBER = Joi.number().optional().allow(null) @@ -226,6 +227,21 @@ export function applicationValidator(opts = { isCreate: true }) { base.name = appNameValidator.optional() } + const snippetValidator = Joi.array() + .optional() + .items( + Joi.object({ + name: Joi.string() + .pattern(new RegExp(ValidSnippetNameRegex)) + .error( + new Error( + "Snippet name cannot include spaces or special characters, and cannot start with a number" + ) + ), + code: OPTIONAL_STRING, + }) + ) + return auth.joiValidator.body( Joi.object({ _id: OPTIONAL_STRING, @@ -235,6 +251,7 @@ export function applicationValidator(opts = { isCreate: true }) { template: Joi.object({ templateString: OPTIONAL_STRING, }).unknown(true), + snippets: snippetValidator, }).unknown(true) ) } diff --git a/packages/shared-core/src/constants/index.ts b/packages/shared-core/src/constants/index.ts index 99fb5c2a73..b5b651a3da 100644 --- a/packages/shared-core/src/constants/index.ts +++ b/packages/shared-core/src/constants/index.ts @@ -98,6 +98,7 @@ export enum BuilderSocketEvent { export const SocketSessionTTL = 60 export const ValidQueryNameRegex = /^[^()]*$/ export const ValidColumnNameRegex = /^[_a-zA-Z0-9\s]*$/g +export const ValidSnippetNameRegex = /^[a-z-_][a-z0-9-_]*$/i export const REBOOT_CRON = "@reboot" From a1186cd6d30845ab66d0a268b9a86b1001e41f33 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:01:26 +0000 Subject: [PATCH 30/59] Remove testing snippet code --- packages/string-templates/package.json | 3 +-- packages/string-templates/src/helpers/snippet.js | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 packages/string-templates/src/helpers/snippet.js diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 1f3e1b618a..340d74ef8a 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -13,8 +13,7 @@ }, "./package.json": "./package.json", "./test/utils": "./test/utils.js", - "./iife": "./src/iife.js", - "./snippet": "./src/helpers/snippet.js" + "./iife": "./src/iife.js" }, "files": [ "dist", diff --git a/packages/string-templates/src/helpers/snippet.js b/packages/string-templates/src/helpers/snippet.js deleted file mode 100644 index b7269b56cc..0000000000 --- a/packages/string-templates/src/helpers/snippet.js +++ /dev/null @@ -1,2 +0,0 @@ -module.exports.CrazyLongSnippet = - '/**\n * marked - a markdown parser\n * Copyright (c) 2011-2022, Christopher Jeffrey. (MIT Licensed)\n * https://github.com/markedjs/marked\n */\n\n/**\n * DO NOT EDIT THIS FILE\n * The code in this file is generated from files in ./src/\n */\n\nfunction getDefaults() {\n return {\n baseUrl: null,\n breaks: false,\n extensions: null,\n gfm: true,\n headerIds: true,\n headerPrefix: "",\n highlight: null,\n langPrefix: "language-",\n mangle: true,\n pedantic: false,\n renderer: null,\n sanitize: false,\n sanitizer: null,\n silent: false,\n smartLists: false,\n smartypants: false,\n tokenizer: null,\n walkTokens: null,\n xhtml: false,\n }\n}\n\nlet defaults = getDefaults()\n\nfunction changeDefaults(newDefaults) {\n defaults = newDefaults\n}\n\n/**\n * Helpers\n */\nconst escapeTest = /[&<>"\']/\nconst escapeReplace = /[&<>"\']/g\nconst escapeTestNoEncode = /[<>"\']|&(?!#?\\w+;)/\nconst escapeReplaceNoEncode = /[<>"\']|&(?!#?\\w+;)/g\nconst escapeReplacements = {\n "&": "&",\n "<": "<",\n ">": ">",\n \'"\': """,\n "\'": "'",\n}\nconst getEscapeReplacement = ch => escapeReplacements[ch]\nfunction escape(html, encode) {\n if (encode) {\n if (escapeTest.test(html)) {\n return html.replace(escapeReplace, getEscapeReplacement)\n }\n } else {\n if (escapeTestNoEncode.test(html)) {\n return html.replace(escapeReplaceNoEncode, getEscapeReplacement)\n }\n }\n\n return html\n}\n\nconst unescapeTest = /&(#(?:\\d+)|(?:#x[0-9A-Fa-f]+)|(?:\\w+));?/gi\n\n/**\n * @param {string} html\n */\nfunction unescape(html) {\n // explicitly match decimal, hex, and named HTML entities\n return html.replace(unescapeTest, (_, n) => {\n n = n.toLowerCase()\n if (n === "colon") return ":"\n if (n.charAt(0) === "#") {\n return n.charAt(1) === "x"\n ? String.fromCharCode(parseInt(n.substring(2), 16))\n : String.fromCharCode(+n.substring(1))\n }\n return ""\n })\n}\n\nconst caret = /(^|[^\\[])\\^/g\n\n/**\n * @param {string | RegExp} regex\n * @param {string} opt\n */\nfunction edit(regex, opt) {\n regex = typeof regex === "string" ? regex : regex.source\n opt = opt || ""\n const obj = {\n replace: (name, val) => {\n val = val.source || val\n val = val.replace(caret, "$1")\n regex = regex.replace(name, val)\n return obj\n },\n getRegex: () => {\n return new RegExp(regex, opt)\n },\n }\n return obj\n}\n\nconst nonWordAndColonTest = /[^\\w:]/g\nconst originIndependentUrl = /^$|^[a-z][a-z0-9+.-]*:|^[?#]/i\n\n/**\n * @param {boolean} sanitize\n * @param {string} base\n * @param {string} href\n */\nfunction cleanUrl(sanitize, base, href) {\n if (sanitize) {\n let prot\n try {\n prot = decodeURIComponent(unescape(href))\n .replace(nonWordAndColonTest, "")\n .toLowerCase()\n } catch (e) {\n return null\n }\n if (\n prot.indexOf("javascript:") === 0 ||\n prot.indexOf("vbscript:") === 0 ||\n prot.indexOf("data:") === 0\n ) {\n return null\n }\n }\n if (base && !originIndependentUrl.test(href)) {\n href = resolveUrl(base, href)\n }\n try {\n href = encodeURI(href).replace(/%25/g, "%")\n } catch (e) {\n return null\n }\n return href\n}\n\nconst baseUrls = {}\nconst justDomain = /^[^:]+:\\/*[^/]*$/\nconst protocol = /^([^:]+:)[\\s\\S]*$/\nconst domain = /^([^:]+:\\/*[^/]*)[\\s\\S]*$/\n\n/**\n * @param {string} base\n * @param {string} href\n */\nfunction resolveUrl(base, href) {\n if (!baseUrls[" " + base]) {\n // we can ignore everything in base after the last slash of its path component,\n // but we might need to add _that_\n // https://tools.ietf.org/html/rfc3986#section-3\n if (justDomain.test(base)) {\n baseUrls[" " + base] = base + "/"\n } else {\n baseUrls[" " + base] = rtrim(base, "/", true)\n }\n }\n base = baseUrls[" " + base]\n const relativeBase = base.indexOf(":") === -1\n\n if (href.substring(0, 2) === "//") {\n if (relativeBase) {\n return href\n }\n return base.replace(protocol, "$1") + href\n } else if (href.charAt(0) === "/") {\n if (relativeBase) {\n return href\n }\n return base.replace(domain, "$1") + href\n } else {\n return base + href\n }\n}\n\nconst noopTest = { exec: function noopTest() {} }\n\nfunction merge(obj) {\n let i = 1,\n target,\n key\n\n for (; i < arguments.length; i++) {\n target = arguments[i]\n for (key in target) {\n if (Object.prototype.hasOwnProperty.call(target, key)) {\n obj[key] = target[key]\n }\n }\n }\n\n return obj\n}\n\nfunction splitCells(tableRow, count) {\n // ensure that every cell-delimiting pipe has a space\n // before it to distinguish it from an escaped pipe\n const row = tableRow.replace(/\\|/g, (match, offset, str) => {\n let escaped = false,\n curr = offset\n while (--curr >= 0 && str[curr] === "\\\\") escaped = !escaped\n if (escaped) {\n // odd number of slashes means | is escaped\n // so we leave it alone\n return "|"\n } else {\n // add space before unescaped |\n return " |"\n }\n }),\n cells = row.split(/ \\|/)\n let i = 0\n\n // First/last cell in a row cannot be empty if it has no leading/trailing pipe\n if (!cells[0].trim()) {\n cells.shift()\n }\n if (cells.length > 0 && !cells[cells.length - 1].trim()) {\n cells.pop()\n }\n\n if (cells.length > count) {\n cells.splice(count)\n } else {\n while (cells.length < count) cells.push("")\n }\n\n for (; i < cells.length; i++) {\n // leading or trailing whitespace is ignored per the gfm spec\n cells[i] = cells[i].trim().replace(/\\\\\\|/g, "|")\n }\n return cells\n}\n\n/**\n * Remove trailing \'c\'s. Equivalent to str.replace(/c*$/, \'\').\n * /c*$/ is vulnerable to REDOS.\n *\n * @param {string} str\n * @param {string} c\n * @param {boolean} invert Remove suffix of non-c chars instead. Default falsey.\n */\nfunction rtrim(str, c, invert) {\n const l = str.length\n if (l === 0) {\n return ""\n }\n\n // Length of suffix matching the invert condition.\n let suffLen = 0\n\n // Step left until we fail to match the invert condition.\n while (suffLen < l) {\n const currChar = str.charAt(l - suffLen - 1)\n if (currChar === c && !invert) {\n suffLen++\n } else if (currChar !== c && invert) {\n suffLen++\n } else {\n break\n }\n }\n\n return str.slice(0, l - suffLen)\n}\n\nfunction findClosingBracket(str, b) {\n if (str.indexOf(b[1]) === -1) {\n return -1\n }\n const l = str.length\n let level = 0,\n i = 0\n for (; i < l; i++) {\n if (str[i] === "\\\\") {\n i++\n } else if (str[i] === b[0]) {\n level++\n } else if (str[i] === b[1]) {\n level--\n if (level < 0) {\n return i\n }\n }\n }\n return -1\n}\n\nfunction checkSanitizeDeprecation(opt) {\n if (opt && opt.sanitize && !opt.silent) {\n console.warn(\n "marked(): sanitize and sanitizer parameters are deprecated since version 0.7.0, should not be used and will be removed in the future. Read more here: https://marked.js.org/#/USING_ADVANCED.md#options"\n )\n }\n}\n\n// copied from https://stackoverflow.com/a/5450113/806777\n/**\n * @param {string} pattern\n * @param {number} count\n */\nfunction repeatString(pattern, count) {\n if (count < 1) {\n return ""\n }\n let result = ""\n while (count > 1) {\n if (count & 1) {\n result += pattern\n }\n count >>= 1\n pattern += pattern\n }\n return result + pattern\n}\n\nfunction outputLink(cap, link, raw, lexer) {\n const href = link.href\n const title = link.title ? escape(link.title) : null\n const text = cap[1].replace(/\\\\([\\[\\]])/g, "$1")\n\n if (cap[0].charAt(0) !== "!") {\n lexer.state.inLink = true\n const token = {\n type: "link",\n raw,\n href,\n title,\n text,\n tokens: lexer.inlineTokens(text, []),\n }\n lexer.state.inLink = false\n return token\n }\n return {\n type: "image",\n raw,\n href,\n title,\n text: escape(text),\n }\n}\n\nfunction indentCodeCompensation(raw, text) {\n const matchIndentToCode = raw.match(/^(\\s+)(?:```)/)\n\n if (matchIndentToCode === null) {\n return text\n }\n\n const indentToCode = matchIndentToCode[1]\n\n return text\n .split("\\n")\n .map(node => {\n const matchIndentInNode = node.match(/^\\s+/)\n if (matchIndentInNode === null) {\n return node\n }\n\n const [indentInNode] = matchIndentInNode\n\n if (indentInNode.length >= indentToCode.length) {\n return node.slice(indentToCode.length)\n }\n\n return node\n })\n .join("\\n")\n}\n\n/**\n * Tokenizer\n */\nclass Tokenizer {\n constructor(options) {\n this.options = options || defaults\n }\n\n space(src) {\n const cap = this.rules.block.newline.exec(src)\n if (cap && cap[0].length > 0) {\n return {\n type: "space",\n raw: cap[0],\n }\n }\n }\n\n code(src) {\n const cap = this.rules.block.code.exec(src)\n if (cap) {\n const text = cap[0].replace(/^ {1,4}/gm, "")\n return {\n type: "code",\n raw: cap[0],\n codeBlockStyle: "indented",\n text: !this.options.pedantic ? rtrim(text, "\\n") : text,\n }\n }\n }\n\n fences(src) {\n const cap = this.rules.block.fences.exec(src)\n if (cap) {\n const raw = cap[0]\n const text = indentCodeCompensation(raw, cap[3] || "")\n\n return {\n type: "code",\n raw,\n lang: cap[2] ? cap[2].trim() : cap[2],\n text,\n }\n }\n }\n\n heading(src) {\n const cap = this.rules.block.heading.exec(src)\n if (cap) {\n let text = cap[2].trim()\n\n // remove trailing #s\n if (/#$/.test(text)) {\n const trimmed = rtrim(text, "#")\n if (this.options.pedantic) {\n text = trimmed.trim()\n } else if (!trimmed || / $/.test(trimmed)) {\n // CommonMark requires space before trailing #s\n text = trimmed.trim()\n }\n }\n\n const token = {\n type: "heading",\n raw: cap[0],\n depth: cap[1].length,\n text,\n tokens: [],\n }\n this.lexer.inline(token.text, token.tokens)\n return token\n }\n }\n\n hr(src) {\n const cap = this.rules.block.hr.exec(src)\n if (cap) {\n return {\n type: "hr",\n raw: cap[0],\n }\n }\n }\n\n blockquote(src) {\n const cap = this.rules.block.blockquote.exec(src)\n if (cap) {\n const text = cap[0].replace(/^ *>[ \\t]?/gm, "")\n\n return {\n type: "blockquote",\n raw: cap[0],\n tokens: this.lexer.blockTokens(text, []),\n text,\n }\n }\n }\n\n list(src) {\n let cap = this.rules.block.list.exec(src)\n if (cap) {\n let raw,\n istask,\n ischecked,\n indent,\n i,\n blankLine,\n endsWithBlankLine,\n line,\n nextLine,\n rawLine,\n itemContents,\n endEarly\n\n let bull = cap[1].trim()\n const isordered = bull.length > 1\n\n const list = {\n type: "list",\n raw: "",\n ordered: isordered,\n start: isordered ? +bull.slice(0, -1) : "",\n loose: false,\n items: [],\n }\n\n bull = isordered ? `\\\\d{1,9}\\\\${bull.slice(-1)}` : `\\\\${bull}`\n\n if (this.options.pedantic) {\n bull = isordered ? bull : "[*+-]"\n }\n\n // Get next list item\n const itemRegex = new RegExp(\n `^( {0,3}${bull})((?:[\\t ][^\\\\n]*)?(?:\\\\n|$))`\n )\n\n // Check if current bullet point can start a new List Item\n while (src) {\n endEarly = false\n if (!(cap = itemRegex.exec(src))) {\n break\n }\n\n if (this.rules.block.hr.test(src)) {\n // End list if bullet was actually HR (possibly move into itemRegex?)\n break\n }\n\n raw = cap[0]\n src = src.substring(raw.length)\n\n line = cap[2].split("\\n", 1)[0]\n nextLine = src.split("\\n", 1)[0]\n\n if (this.options.pedantic) {\n indent = 2\n itemContents = line.trimLeft()\n } else {\n indent = cap[2].search(/[^ ]/) // Find first non-space char\n indent = indent > 4 ? 1 : indent // Treat indented code blocks (> 4 spaces) as having only 1 indent\n itemContents = line.slice(indent)\n indent += cap[1].length\n }\n\n blankLine = false\n\n if (!line && /^ *$/.test(nextLine)) {\n // Items begin with at most one blank line\n raw += nextLine + "\\n"\n src = src.substring(nextLine.length + 1)\n endEarly = true\n }\n\n if (!endEarly) {\n const nextBulletRegex = new RegExp(\n `^ {0,${Math.min(\n 3,\n indent - 1\n )}}(?:[*+-]|\\\\d{1,9}[.)])((?: [^\\\\n]*)?(?:\\\\n|$))`\n )\n const hrRegex = new RegExp(\n `^ {0,${Math.min(\n 3,\n indent - 1\n )}}((?:- *){3,}|(?:_ *){3,}|(?:\\\\* *){3,})(?:\\\\n+|$)`\n )\n\n // Check if following lines should be included in List Item\n while (src) {\n rawLine = src.split("\\n", 1)[0]\n line = rawLine\n\n // Re-align to follow commonmark nesting rules\n if (this.options.pedantic) {\n line = line.replace(/^ {1,4}(?=( {4})*[^ ])/g, " ")\n }\n\n // End list item if found start of new bullet\n if (nextBulletRegex.test(line)) {\n break\n }\n\n // Horizontal rule found\n if (hrRegex.test(src)) {\n break\n }\n\n if (line.search(/[^ ]/) >= indent || !line.trim()) {\n // Dedent if possible\n itemContents += "\\n" + line.slice(indent)\n } else if (!blankLine) {\n // Until blank line, item doesn\'t need indentation\n itemContents += "\\n" + line\n } else {\n // Otherwise, improper indentation ends this item\n break\n }\n\n if (!blankLine && !line.trim()) {\n // Check if current line is blank\n blankLine = true\n }\n\n raw += rawLine + "\\n"\n src = src.substring(rawLine.length + 1)\n }\n }\n\n if (!list.loose) {\n // If the previous item ended with a blank line, the list is loose\n if (endsWithBlankLine) {\n list.loose = true\n } else if (/\\n *\\n *$/.test(raw)) {\n endsWithBlankLine = true\n }\n }\n\n // Check for task list items\n if (this.options.gfm) {\n istask = /^\\[[ xX]\\] /.exec(itemContents)\n if (istask) {\n ischecked = istask[0] !== "[ ] "\n itemContents = itemContents.replace(/^\\[[ xX]\\] +/, "")\n }\n }\n\n list.items.push({\n type: "list_item",\n raw,\n task: !!istask,\n checked: ischecked,\n loose: false,\n text: itemContents,\n })\n\n list.raw += raw\n }\n\n // Do not consume newlines at end of final item. Alternatively, make itemRegex *start* with any newlines to simplify/speed up endsWithBlankLine logic\n list.items[list.items.length - 1].raw = raw.trimRight()\n list.items[list.items.length - 1].text = itemContents.trimRight()\n list.raw = list.raw.trimRight()\n\n const l = list.items.length\n\n // Item child tokens handled here at end because we needed to have the final item to trim it first\n for (i = 0; i < l; i++) {\n this.lexer.state.top = false\n list.items[i].tokens = this.lexer.blockTokens(list.items[i].text, [])\n const spacers = list.items[i].tokens.filter(t => t.type === "space")\n const hasMultipleLineBreaks = spacers.every(t => {\n const chars = t.raw.split("")\n let lineBreaks = 0\n for (const char of chars) {\n if (char === "\\n") {\n lineBreaks += 1\n }\n if (lineBreaks > 1) {\n return true\n }\n }\n\n return false\n })\n\n if (!list.loose && spacers.length && hasMultipleLineBreaks) {\n // Having a single line break doesn\'t mean a list is loose. A single line break is terminating the last list item\n list.loose = true\n list.items[i].loose = true\n }\n }\n\n return list\n }\n }\n\n html(src) {\n const cap = this.rules.block.html.exec(src)\n if (cap) {\n const token = {\n type: "html",\n raw: cap[0],\n pre:\n !this.options.sanitizer &&\n (cap[1] === "pre" || cap[1] === "script" || cap[1] === "style"),\n text: cap[0],\n }\n if (this.options.sanitize) {\n token.type = "paragraph"\n token.text = this.options.sanitizer\n ? this.options.sanitizer(cap[0])\n : escape(cap[0])\n token.tokens = []\n this.lexer.inline(token.text, token.tokens)\n }\n return token\n }\n }\n\n def(src) {\n const cap = this.rules.block.def.exec(src)\n if (cap) {\n if (cap[3]) cap[3] = cap[3].substring(1, cap[3].length - 1)\n const tag = cap[1].toLowerCase().replace(/\\s+/g, " ")\n return {\n type: "def",\n tag,\n raw: cap[0],\n href: cap[2],\n title: cap[3],\n }\n }\n }\n\n table(src) {\n const cap = this.rules.block.table.exec(src)\n if (cap) {\n const item = {\n type: "table",\n header: splitCells(cap[1]).map(c => {\n return { text: c }\n }),\n align: cap[2].replace(/^ *|\\| *$/g, "").split(/ *\\| */),\n rows:\n cap[3] && cap[3].trim()\n ? cap[3].replace(/\\n[ \\t]*$/, "").split("\\n")\n : [],\n }\n\n if (item.header.length === item.align.length) {\n item.raw = cap[0]\n\n let l = item.align.length\n let i, j, k, row\n for (i = 0; i < l; i++) {\n if (/^ *-+: *$/.test(item.align[i])) {\n item.align[i] = "right"\n } else if (/^ *:-+: *$/.test(item.align[i])) {\n item.align[i] = "center"\n } else if (/^ *:-+ *$/.test(item.align[i])) {\n item.align[i] = "left"\n } else {\n item.align[i] = null\n }\n }\n\n l = item.rows.length\n for (i = 0; i < l; i++) {\n item.rows[i] = splitCells(item.rows[i], item.header.length).map(c => {\n return { text: c }\n })\n }\n\n // parse child tokens inside headers and cells\n\n // header child tokens\n l = item.header.length\n for (j = 0; j < l; j++) {\n item.header[j].tokens = []\n this.lexer.inline(item.header[j].text, item.header[j].tokens)\n }\n\n // cell child tokens\n l = item.rows.length\n for (j = 0; j < l; j++) {\n row = item.rows[j]\n for (k = 0; k < row.length; k++) {\n row[k].tokens = []\n this.lexer.inline(row[k].text, row[k].tokens)\n }\n }\n\n return item\n }\n }\n }\n\n lheading(src) {\n const cap = this.rules.block.lheading.exec(src)\n if (cap) {\n const token = {\n type: "heading",\n raw: cap[0],\n depth: cap[2].charAt(0) === "=" ? 1 : 2,\n text: cap[1],\n tokens: [],\n }\n this.lexer.inline(token.text, token.tokens)\n return token\n }\n }\n\n paragraph(src) {\n const cap = this.rules.block.paragraph.exec(src)\n if (cap) {\n const token = {\n type: "paragraph",\n raw: cap[0],\n text:\n cap[1].charAt(cap[1].length - 1) === "\\n"\n ? cap[1].slice(0, -1)\n : cap[1],\n tokens: [],\n }\n this.lexer.inline(token.text, token.tokens)\n return token\n }\n }\n\n text(src) {\n const cap = this.rules.block.text.exec(src)\n if (cap) {\n const token = {\n type: "text",\n raw: cap[0],\n text: cap[0],\n tokens: [],\n }\n this.lexer.inline(token.text, token.tokens)\n return token\n }\n }\n\n escape(src) {\n const cap = this.rules.inline.escape.exec(src)\n if (cap) {\n return {\n type: "escape",\n raw: cap[0],\n text: escape(cap[1]),\n }\n }\n }\n\n tag(src) {\n const cap = this.rules.inline.tag.exec(src)\n if (cap) {\n if (!this.lexer.state.inLink && /^/i.test(cap[0])) {\n this.lexer.state.inLink = false\n }\n if (\n !this.lexer.state.inRawBlock &&\n /^<(pre|code|kbd|script)(\\s|>)/i.test(cap[0])\n ) {\n this.lexer.state.inRawBlock = true\n } else if (\n this.lexer.state.inRawBlock &&\n /^<\\/(pre|code|kbd|script)(\\s|>)/i.test(cap[0])\n ) {\n this.lexer.state.inRawBlock = false\n }\n\n return {\n type: this.options.sanitize ? "text" : "html",\n raw: cap[0],\n inLink: this.lexer.state.inLink,\n inRawBlock: this.lexer.state.inRawBlock,\n text: this.options.sanitize\n ? this.options.sanitizer\n ? this.options.sanitizer(cap[0])\n : escape(cap[0])\n : cap[0],\n }\n }\n }\n\n link(src) {\n const cap = this.rules.inline.link.exec(src)\n if (cap) {\n const trimmedUrl = cap[2].trim()\n if (!this.options.pedantic && /^$/.test(trimmedUrl)) {\n return\n }\n\n // ending angle bracket cannot be escaped\n const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), "\\\\")\n if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {\n return\n }\n } else {\n // find closing parenthesis\n const lastParenIndex = findClosingBracket(cap[2], "()")\n if (lastParenIndex > -1) {\n const start = cap[0].indexOf("!") === 0 ? 5 : 4\n const linkLen = start + cap[1].length + lastParenIndex\n cap[2] = cap[2].substring(0, lastParenIndex)\n cap[0] = cap[0].substring(0, linkLen).trim()\n cap[3] = ""\n }\n }\n let href = cap[2]\n let title = ""\n if (this.options.pedantic) {\n // split pedantic href and title\n const link = /^([^\'"]*[^\\s])\\s+([\'"])(.*)\\2/.exec(href)\n\n if (link) {\n href = link[1]\n title = link[3]\n }\n } else {\n title = cap[3] ? cap[3].slice(1, -1) : ""\n }\n\n href = href.trim()\n if (/^$/.test(trimmedUrl)) {\n // pedantic allows starting angle bracket without ending angle bracket\n href = href.slice(1)\n } else {\n href = href.slice(1, -1)\n }\n }\n return outputLink(\n cap,\n {\n href: href ? href.replace(this.rules.inline._escapes, "$1") : href,\n title: title\n ? title.replace(this.rules.inline._escapes, "$1")\n : title,\n },\n cap[0],\n this.lexer\n )\n }\n }\n\n reflink(src, links) {\n let cap\n if (\n (cap = this.rules.inline.reflink.exec(src)) ||\n (cap = this.rules.inline.nolink.exec(src))\n ) {\n let link = (cap[2] || cap[1]).replace(/\\s+/g, " ")\n link = links[link.toLowerCase()]\n if (!link || !link.href) {\n const text = cap[0].charAt(0)\n return {\n type: "text",\n raw: text,\n text,\n }\n }\n return outputLink(cap, link, cap[0], this.lexer)\n }\n }\n\n emStrong(src, maskedSrc, prevChar = "") {\n let match = this.rules.inline.emStrong.lDelim.exec(src)\n if (!match) return\n\n // _ can\'t be between two alphanumerics. \\p{L}\\p{N} includes non-english alphabet/numbers as well\n if (match[3] && prevChar.match(/[\\p{L}\\p{N}]/u)) return\n\n const nextChar = match[1] || match[2] || ""\n\n if (\n !nextChar ||\n (nextChar &&\n (prevChar === "" || this.rules.inline.punctuation.exec(prevChar)))\n ) {\n const lLength = match[0].length - 1\n let rDelim,\n rLength,\n delimTotal = lLength,\n midDelimTotal = 0\n\n const endReg =\n match[0][0] === "*"\n ? this.rules.inline.emStrong.rDelimAst\n : this.rules.inline.emStrong.rDelimUnd\n endReg.lastIndex = 0\n\n // Clip maskedSrc to same section of string as src (move to lexer?)\n maskedSrc = maskedSrc.slice(-1 * src.length + lLength)\n\n while ((match = endReg.exec(maskedSrc)) != null) {\n rDelim =\n match[1] || match[2] || match[3] || match[4] || match[5] || match[6]\n\n if (!rDelim) continue // skip single * in __abc*abc__\n\n rLength = rDelim.length\n\n if (match[3] || match[4]) {\n // found another Left Delim\n delimTotal += rLength\n continue\n } else if (match[5] || match[6]) {\n // either Left or Right Delim\n if (lLength % 3 && !((lLength + rLength) % 3)) {\n midDelimTotal += rLength\n continue // CommonMark Emphasis Rules 9-10\n }\n }\n\n delimTotal -= rLength\n\n if (delimTotal > 0) continue // Haven\'t found enough closing delimiters\n\n // Remove extra characters. *a*** -> *a*\n rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal)\n\n // Create `em` if smallest delimiter has odd char count. *a***\n if (Math.min(lLength, rLength) % 2) {\n const text = src.slice(1, lLength + match.index + rLength)\n return {\n type: "em",\n raw: src.slice(0, lLength + match.index + rLength + 1),\n text,\n tokens: this.lexer.inlineTokens(text, []),\n }\n }\n\n // Create \'strong\' if smallest delimiter has even char count. **a***\n const text = src.slice(2, lLength + match.index + rLength - 1)\n return {\n type: "strong",\n raw: src.slice(0, lLength + match.index + rLength + 1),\n text,\n tokens: this.lexer.inlineTokens(text, []),\n }\n }\n }\n }\n\n codespan(src) {\n const cap = this.rules.inline.code.exec(src)\n if (cap) {\n let text = cap[2].replace(/\\n/g, " ")\n const hasNonSpaceChars = /[^ ]/.test(text)\n const hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text)\n if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {\n text = text.substring(1, text.length - 1)\n }\n text = escape(text, true)\n return {\n type: "codespan",\n raw: cap[0],\n text,\n }\n }\n }\n\n br(src) {\n const cap = this.rules.inline.br.exec(src)\n if (cap) {\n return {\n type: "br",\n raw: cap[0],\n }\n }\n }\n\n del(src) {\n const cap = this.rules.inline.del.exec(src)\n if (cap) {\n return {\n type: "del",\n raw: cap[0],\n text: cap[2],\n tokens: this.lexer.inlineTokens(cap[2], []),\n }\n }\n }\n\n autolink(src, mangle) {\n const cap = this.rules.inline.autolink.exec(src)\n if (cap) {\n let text, href\n if (cap[2] === "@") {\n text = escape(this.options.mangle ? mangle(cap[1]) : cap[1])\n href = "mailto:" + text\n } else {\n text = escape(cap[1])\n href = text\n }\n\n return {\n type: "link",\n raw: cap[0],\n text,\n href,\n tokens: [\n {\n type: "text",\n raw: text,\n text,\n },\n ],\n }\n }\n }\n\n url(src, mangle) {\n let cap\n if ((cap = this.rules.inline.url.exec(src))) {\n let text, href\n if (cap[2] === "@") {\n text = escape(this.options.mangle ? mangle(cap[0]) : cap[0])\n href = "mailto:" + text\n } else {\n // do extended autolink path validation\n let prevCapZero\n do {\n prevCapZero = cap[0]\n cap[0] = this.rules.inline._backpedal.exec(cap[0])[0]\n } while (prevCapZero !== cap[0])\n text = escape(cap[0])\n if (cap[1] === "www.") {\n href = "http://" + text\n } else {\n href = text\n }\n }\n return {\n type: "link",\n raw: cap[0],\n text,\n href,\n tokens: [\n {\n type: "text",\n raw: text,\n text,\n },\n ],\n }\n }\n }\n\n inlineText(src, smartypants) {\n const cap = this.rules.inline.text.exec(src)\n if (cap) {\n let text\n if (this.lexer.state.inRawBlock) {\n text = this.options.sanitize\n ? this.options.sanitizer\n ? this.options.sanitizer(cap[0])\n : escape(cap[0])\n : cap[0]\n } else {\n text = escape(this.options.smartypants ? smartypants(cap[0]) : cap[0])\n }\n return {\n type: "text",\n raw: cap[0],\n text,\n }\n }\n }\n}\n\n/**\n * Block-Level Grammar\n */\nconst block = {\n newline: /^(?: *(?:\\n|$))+/,\n code: /^( {4}[^\\n]+(?:\\n(?: *(?:\\n|$))*)?)+/,\n fences:\n /^ {0,3}(`{3,}(?=[^`\\n]*\\n)|~{3,})([^\\n]*)\\n(?:|([\\s\\S]*?)\\n)(?: {0,3}\\1[~`]* *(?=\\n|$)|$)/,\n hr: /^ {0,3}((?:-[\\t ]*){3,}|(?:_[ \\t]*){3,}|(?:\\*[ \\t]*){3,})(?:\\n+|$)/,\n heading: /^ {0,3}(#{1,6})(?=\\s|$)(.*)(?:\\n+|$)/,\n blockquote: /^( {0,3}> ?(paragraph|[^\\n]*)(?:\\n|$))+/,\n list: /^( {0,3}bull)([ \\t][^\\n]+?)?(?:\\n|$)/,\n html:\n "^ {0,3}(?:" + // optional indentation\n "<(script|pre|style|textarea)[\\\\s>][\\\\s\\\\S]*?(?:[^\\\\n]*\\\\n+|$)" + // (1)\n "|comment[^\\\\n]*(\\\\n+|$)" + // (2)\n "|<\\\\?[\\\\s\\\\S]*?(?:\\\\?>\\\\n*|$)" + // (3)\n "|\\\\n*|$)" + // (4)\n "|\\\\n*|$)" + // (5)\n "|)[\\\\s\\\\S]*?(?:(?:\\\\n *)+\\\\n|$)" + // (6)\n "|<(?!script|pre|style|textarea)([a-z][\\\\w-]*)(?:attribute)*? */?>(?=[ \\\\t]*(?:\\\\n|$))[\\\\s\\\\S]*?(?:(?:\\\\n *)+\\\\n|$)" + // (7) open tag\n "|(?=[ \\\\t]*(?:\\\\n|$))[\\\\s\\\\S]*?(?:(?:\\\\n *)+\\\\n|$)" + // (7) closing tag\n ")",\n def: /^ {0,3}\\[(label)\\]: *(?:\\n *)?]+)>?(?:(?: +(?:\\n *)?| *\\n *)(title))? *(?:\\n+|$)/,\n table: noopTest,\n lheading: /^([^\\n]+)\\n {0,3}(=+|-+) *(?:\\n+|$)/,\n // regex template, placeholders will be replaced according to different paragraph\n // interruption rules of commonmark and the original markdown spec:\n _paragraph:\n /^([^\\n]+(?:\\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\\n)[^\\n]+)*)/,\n text: /^[^\\n]+/,\n}\n\nblock._label = /(?!\\s*\\])(?:\\\\.|[^\\[\\]\\\\])+/\nblock._title = /(?:"(?:\\\\"?|[^"\\\\])*"|\'[^\'\\n]*(?:\\n[^\'\\n]+)*\\n?\'|\\([^()]*\\))/\nblock.def = edit(block.def)\n .replace("label", block._label)\n .replace("title", block._title)\n .getRegex()\n\nblock.bullet = /(?:[*+-]|\\d{1,9}[.)])/\nblock.listItemStart = edit(/^( *)(bull) */)\n .replace("bull", block.bullet)\n .getRegex()\n\nblock.list = edit(block.list)\n .replace(/bull/g, block.bullet)\n .replace(\n "hr",\n "\\\\n+(?=\\\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\\\* *){3,})(?:\\\\n+|$))"\n )\n .replace("def", "\\\\n+(?=" + block.def.source + ")")\n .getRegex()\n\nblock._tag =\n "address|article|aside|base|basefont|blockquote|body|caption" +\n "|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption" +\n "|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe" +\n "|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option" +\n "|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr" +\n "|track|ul"\nblock._comment = /\x3C!--(?!-?>)[\\s\\S]*?(?:-->|$)/\nblock.html = edit(block.html, "i")\n .replace("comment", block._comment)\n .replace("tag", block._tag)\n .replace(\n "attribute",\n / +[a-zA-Z:_][\\w.:-]*(?: *= *"[^"\\n]*"| *= *\'[^\'\\n]*\'| *= *[^\\s"\'=<>`]+)?/\n )\n .getRegex()\n\nblock.paragraph = edit(block._paragraph)\n .replace("hr", block.hr)\n .replace("heading", " {0,3}#{1,6} ")\n .replace("|lheading", "") // setex headings don\'t interrupt commonmark paragraphs\n .replace("|table", "")\n .replace("blockquote", " {0,3}>")\n .replace("fences", " {0,3}(?:`{3,}(?=[^`\\\\n]*\\\\n)|~{3,})[^\\\\n]*\\\\n")\n .replace("list", " {0,3}(?:[*+-]|1[.)]) ") // only lists starting from 1 can interrupt\n .replace(\n "html",\n ")|<(?:script|pre|style|textarea|!--)"\n )\n .replace("tag", block._tag) // pars can be interrupted by type (6) html blocks\n .getRegex()\n\nblock.blockquote = edit(block.blockquote)\n .replace("paragraph", block.paragraph)\n .getRegex()\n\n/**\n * Normal Block Grammar\n */\n\nblock.normal = merge({}, block)\n\n/**\n * GFM Block Grammar\n */\n\nblock.gfm = merge({}, block.normal, {\n table:\n "^ *([^\\\\n ].*\\\\|.*)\\\\n" + // Header\n " {0,3}(?:\\\\| *)?(:?-+:? *(?:\\\\| *:?-+:? *)*)(?:\\\\| *)?" + // Align\n "(?:\\\\n((?:(?! *\\\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\\\n|$))*)\\\\n*|$)", // Cells\n})\n\nblock.gfm.table = edit(block.gfm.table)\n .replace("hr", block.hr)\n .replace("heading", " {0,3}#{1,6} ")\n .replace("blockquote", " {0,3}>")\n .replace("code", " {4}[^\\\\n]")\n .replace("fences", " {0,3}(?:`{3,}(?=[^`\\\\n]*\\\\n)|~{3,})[^\\\\n]*\\\\n")\n .replace("list", " {0,3}(?:[*+-]|1[.)]) ") // only lists starting from 1 can interrupt\n .replace(\n "html",\n ")|<(?:script|pre|style|textarea|!--)"\n )\n .replace("tag", block._tag) // tables can be interrupted by type (6) html blocks\n .getRegex()\n\nblock.gfm.paragraph = edit(block._paragraph)\n .replace("hr", block.hr)\n .replace("heading", " {0,3}#{1,6} ")\n .replace("|lheading", "") // setex headings don\'t interrupt commonmark paragraphs\n .replace("table", block.gfm.table) // interrupt paragraphs with table\n .replace("blockquote", " {0,3}>")\n .replace("fences", " {0,3}(?:`{3,}(?=[^`\\\\n]*\\\\n)|~{3,})[^\\\\n]*\\\\n")\n .replace("list", " {0,3}(?:[*+-]|1[.)]) ") // only lists starting from 1 can interrupt\n .replace(\n "html",\n ")|<(?:script|pre|style|textarea|!--)"\n )\n .replace("tag", block._tag) // pars can be interrupted by type (6) html blocks\n .getRegex()\n/**\n * Pedantic grammar (original John Gruber\'s loose markdown specification)\n */\n\nblock.pedantic = merge({}, block.normal, {\n html: edit(\n "^ *(?:comment *(?:\\\\n|\\\\s*$)" +\n "|<(tag)[\\\\s\\\\S]+? *(?:\\\\n{2,}|\\\\s*$)" + // closed tag\n "|\\\\s]*)*?/?> *(?:\\\\n{2,}|\\\\s*$))"\n )\n .replace("comment", block._comment)\n .replace(\n /tag/g,\n "(?!(?:" +\n "a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub" +\n "|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)" +\n "\\\\b)\\\\w+(?!:|[^\\\\w\\\\s@]*@)\\\\b"\n )\n .getRegex(),\n def: /^ *\\[([^\\]]+)\\]: *]+)>?(?: +(["(][^\\n]+[")]))? *(?:\\n+|$)/,\n heading: /^(#{1,6})(.*)(?:\\n+|$)/,\n fences: noopTest, // fences not supported\n paragraph: edit(block.normal._paragraph)\n .replace("hr", block.hr)\n .replace("heading", " *#{1,6} *[^\\n]")\n .replace("lheading", block.lheading)\n .replace("blockquote", " {0,3}>")\n .replace("|fences", "")\n .replace("|list", "")\n .replace("|html", "")\n .getRegex(),\n})\n\n/**\n * Inline-Level Grammar\n */\nconst inline = {\n escape: /^\\\\([!"#$%&\'()*+,\\-./:;<=>?@\\[\\]\\\\^_`{|}~])/,\n autolink: /^<(scheme:[^\\s\\x00-\\x1f<>]*|email)>/,\n url: noopTest,\n tag:\n "^comment" +\n "|^" + // self-closing tag\n "|^<[a-zA-Z][\\\\w-]*(?:attribute)*?\\\\s*/?>" + // open tag\n "|^<\\\\?[\\\\s\\\\S]*?\\\\?>" + // processing instruction, e.g. \n "|^" + // declaration, e.g. \n "|^", // CDATA section\n link: /^!?\\[(label)\\]\\(\\s*(href)(?:\\s+(title))?\\s*\\)/,\n reflink: /^!?\\[(label)\\]\\[(ref)\\]/,\n nolink: /^!?\\[(ref)\\](?:\\[\\])?/,\n reflinkSearch: "reflink|nolink(?!\\\\()",\n emStrong: {\n lDelim: /^(?:\\*+(?:([punct_])|[^\\s*]))|^_+(?:([punct*])|([^\\s_]))/,\n // (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right.\n // () Skip orphan inside strong () Consume to delim (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a\n rDelimAst:\n /^[^_*]*?\\_\\_[^_*]*?\\*[^_*]*?(?=\\_\\_)|[^*]+(?=[^*])|[punct_](\\*+)(?=[\\s]|$)|[^punct*_\\s](\\*+)(?=[punct_\\s]|$)|[punct_\\s](\\*+)(?=[^punct*_\\s])|[\\s](\\*+)(?=[punct_])|[punct_](\\*+)(?=[punct_])|[^punct*_\\s](\\*+)(?=[^punct*_\\s])/,\n rDelimUnd:\n /^[^_*]*?\\*\\*[^_*]*?\\_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|[punct*](\\_+)(?=[\\s]|$)|[^punct*_\\s](\\_+)(?=[punct*\\s]|$)|[punct*\\s](\\_+)(?=[^punct*_\\s])|[\\s](\\_+)(?=[punct*])|[punct*](\\_+)(?=[punct*])/, // ^- Not allowed for _\n },\n code: /^(`+)([^`]|[^`][\\s\\S]*?[^`])\\1(?!`)/,\n br: /^( {2,}|\\\\)\\n(?!\\s*$)/,\n del: noopTest,\n text: /^(`+|[^`])(?:(?= {2,}\\n)|[\\s\\S]*?(?:(?=[\\\\?@\\\\[\\\\]`^{|}~"\ninline.punctuation = edit(inline.punctuation)\n .replace(/punctuation/g, inline._punctuation)\n .getRegex()\n\n// sequences em should skip over [title](link), `code`, \ninline.blockSkip = /\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>/g\ninline.escapedEmSt = /\\\\\\*|\\\\_/g\n\ninline._comment = edit(block._comment).replace("(?:-->|$)", "-->").getRegex()\n\ninline.emStrong.lDelim = edit(inline.emStrong.lDelim)\n .replace(/punct/g, inline._punctuation)\n .getRegex()\n\ninline.emStrong.rDelimAst = edit(inline.emStrong.rDelimAst, "g")\n .replace(/punct/g, inline._punctuation)\n .getRegex()\n\ninline.emStrong.rDelimUnd = edit(inline.emStrong.rDelimUnd, "g")\n .replace(/punct/g, inline._punctuation)\n .getRegex()\n\ninline._escapes = /\\\\([!"#$%&\'()*+,\\-./:;<=>?@\\[\\]\\\\^_`{|}~])/g\n\ninline._scheme = /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/\ninline._email =\n /[a-zA-Z0-9.!#$%&\'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/\ninline.autolink = edit(inline.autolink)\n .replace("scheme", inline._scheme)\n .replace("email", inline._email)\n .getRegex()\n\ninline._attribute =\n /\\s+[a-zA-Z:_][\\w.:-]*(?:\\s*=\\s*"[^"]*"|\\s*=\\s*\'[^\']*\'|\\s*=\\s*[^\\s"\'=<>`]+)?/\n\ninline.tag = edit(inline.tag)\n .replace("comment", inline._comment)\n .replace("attribute", inline._attribute)\n .getRegex()\n\ninline._label = /(?:\\[(?:\\\\.|[^\\[\\]\\\\])*\\]|\\\\.|`[^`]*`|[^\\[\\]\\\\`])*?/\ninline._href = /<(?:\\\\.|[^\\n<>\\\\])+>|[^\\s\\x00-\\x1f]*/\ninline._title = /"(?:\\\\"?|[^"\\\\])*"|\'(?:\\\\\'?|[^\'\\\\])*\'|\\((?:\\\\\\)?|[^)\\\\])*\\)/\n\ninline.link = edit(inline.link)\n .replace("label", inline._label)\n .replace("href", inline._href)\n .replace("title", inline._title)\n .getRegex()\n\ninline.reflink = edit(inline.reflink)\n .replace("label", inline._label)\n .replace("ref", block._label)\n .getRegex()\n\ninline.nolink = edit(inline.nolink).replace("ref", block._label).getRegex()\n\ninline.reflinkSearch = edit(inline.reflinkSearch, "g")\n .replace("reflink", inline.reflink)\n .replace("nolink", inline.nolink)\n .getRegex()\n\n/**\n * Normal Inline Grammar\n */\n\ninline.normal = merge({}, inline)\n\n/**\n * Pedantic Inline Grammar\n */\n\ninline.pedantic = merge({}, inline.normal, {\n strong: {\n start: /^__|\\*\\*/,\n middle: /^__(?=\\S)([\\s\\S]*?\\S)__(?!_)|^\\*\\*(?=\\S)([\\s\\S]*?\\S)\\*\\*(?!\\*)/,\n endAst: /\\*\\*(?!\\*)/g,\n endUnd: /__(?!_)/g,\n },\n em: {\n start: /^_|\\*/,\n middle: /^()\\*(?=\\S)([\\s\\S]*?\\S)\\*(?!\\*)|^_(?=\\S)([\\s\\S]*?\\S)_(?!_)/,\n endAst: /\\*(?!\\*)/g,\n endUnd: /_(?!_)/g,\n },\n link: edit(/^!?\\[(label)\\]\\((.*?)\\)/)\n .replace("label", inline._label)\n .getRegex(),\n reflink: edit(/^!?\\[(label)\\]\\s*\\[([^\\]]*)\\]/)\n .replace("label", inline._label)\n .getRegex(),\n})\n\n/**\n * GFM Inline Grammar\n */\n\ninline.gfm = merge({}, inline.normal, {\n escape: edit(inline.escape).replace("])", "~|])").getRegex(),\n _extended_email:\n /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,\n url: /^((?:ftp|https?):\\/\\/|www\\.)(?:[a-zA-Z0-9\\-]+\\.?)+[^\\s<]*|^email/,\n _backpedal:\n /(?:[^?!.,:;*_~()&]+|\\([^)]*\\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,\n del: /^(~~?)(?=[^\\s~])([\\s\\S]*?[^\\s~])\\1(?=[^~]|$)/,\n text: /^([`~]+|[^`~])(?:(?= {2,}\\n)|(?=[a-zA-Z0-9.!#$%&\'*+\\/=?_`{\\|}~-]+@)|[\\s\\S]*?(?:(?=[\\\\ 0.5) {\n ch = "x" + ch.toString(16)\n }\n out += "&#" + ch + ";"\n }\n\n return out\n}\n\n/**\n * Block Lexer\n */\nclass Lexer {\n constructor(options) {\n this.tokens = []\n this.tokens.links = Object.create(null)\n this.options = options || defaults\n this.options.tokenizer = this.options.tokenizer || new Tokenizer()\n this.tokenizer = this.options.tokenizer\n this.tokenizer.options = this.options\n this.tokenizer.lexer = this\n this.inlineQueue = []\n this.state = {\n inLink: false,\n inRawBlock: false,\n top: true,\n }\n\n const rules = {\n block: block.normal,\n inline: inline.normal,\n }\n\n if (this.options.pedantic) {\n rules.block = block.pedantic\n rules.inline = inline.pedantic\n } else if (this.options.gfm) {\n rules.block = block.gfm\n if (this.options.breaks) {\n rules.inline = inline.breaks\n } else {\n rules.inline = inline.gfm\n }\n }\n this.tokenizer.rules = rules\n }\n\n /**\n * Expose Rules\n */\n static get rules() {\n return {\n block,\n inline,\n }\n }\n\n /**\n * Static Lex Method\n */\n static lex(src, options) {\n const lexer = new Lexer(options)\n return lexer.lex(src)\n }\n\n /**\n * Static Lex Inline Method\n */\n static lexInline(src, options) {\n const lexer = new Lexer(options)\n return lexer.inlineTokens(src)\n }\n\n /**\n * Preprocessing\n */\n lex(src) {\n src = src.replace(/\\r\\n|\\r/g, "\\n")\n\n this.blockTokens(src, this.tokens)\n\n let next\n while ((next = this.inlineQueue.shift())) {\n this.inlineTokens(next.src, next.tokens)\n }\n\n return this.tokens\n }\n\n /**\n * Lexing\n */\n blockTokens(src, tokens = []) {\n if (this.options.pedantic) {\n src = src.replace(/\\t/g, " ").replace(/^ +$/gm, "")\n } else {\n src = src.replace(/^( *)(\\t+)/gm, (_, leading, tabs) => {\n return leading + " ".repeat(tabs.length)\n })\n }\n\n let token, lastToken, cutSrc, lastParagraphClipped\n\n while (src) {\n if (\n this.options.extensions &&\n this.options.extensions.block &&\n this.options.extensions.block.some(extTokenizer => {\n if ((token = extTokenizer.call({ lexer: this }, src, tokens))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n return true\n }\n return false\n })\n ) {\n continue\n }\n\n // newline\n if ((token = this.tokenizer.space(src))) {\n src = src.substring(token.raw.length)\n if (token.raw.length === 1 && tokens.length > 0) {\n // if there\'s a single \\n as a spacer, it\'s terminating the last line,\n // so move it there so that we don\'t get unecessary paragraph tags\n tokens[tokens.length - 1].raw += "\\n"\n } else {\n tokens.push(token)\n }\n continue\n }\n\n // code\n if ((token = this.tokenizer.code(src))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n // An indented code block cannot interrupt a paragraph.\n if (\n lastToken &&\n (lastToken.type === "paragraph" || lastToken.type === "text")\n ) {\n lastToken.raw += "\\n" + token.raw\n lastToken.text += "\\n" + token.text\n this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n // fences\n if ((token = this.tokenizer.fences(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // heading\n if ((token = this.tokenizer.heading(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // hr\n if ((token = this.tokenizer.hr(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // blockquote\n if ((token = this.tokenizer.blockquote(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // list\n if ((token = this.tokenizer.list(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // html\n if ((token = this.tokenizer.html(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // def\n if ((token = this.tokenizer.def(src))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n if (\n lastToken &&\n (lastToken.type === "paragraph" || lastToken.type === "text")\n ) {\n lastToken.raw += "\\n" + token.raw\n lastToken.text += "\\n" + token.raw\n this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text\n } else if (!this.tokens.links[token.tag]) {\n this.tokens.links[token.tag] = {\n href: token.href,\n title: token.title,\n }\n }\n continue\n }\n\n // table (gfm)\n if ((token = this.tokenizer.table(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // lheading\n if ((token = this.tokenizer.lheading(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // top-level paragraph\n // prevent paragraph consuming extensions by clipping \'src\' to extension start\n cutSrc = src\n if (this.options.extensions && this.options.extensions.startBlock) {\n let startIndex = Infinity\n const tempSrc = src.slice(1)\n let tempStart\n this.options.extensions.startBlock.forEach(function (getStartIndex) {\n tempStart = getStartIndex.call({ lexer: this }, tempSrc)\n if (typeof tempStart === "number" && tempStart >= 0) {\n startIndex = Math.min(startIndex, tempStart)\n }\n })\n if (startIndex < Infinity && startIndex >= 0) {\n cutSrc = src.substring(0, startIndex + 1)\n }\n }\n if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {\n lastToken = tokens[tokens.length - 1]\n if (lastParagraphClipped && lastToken.type === "paragraph") {\n lastToken.raw += "\\n" + token.raw\n lastToken.text += "\\n" + token.text\n this.inlineQueue.pop()\n this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text\n } else {\n tokens.push(token)\n }\n lastParagraphClipped = cutSrc.length !== src.length\n src = src.substring(token.raw.length)\n continue\n }\n\n // text\n if ((token = this.tokenizer.text(src))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n if (lastToken && lastToken.type === "text") {\n lastToken.raw += "\\n" + token.raw\n lastToken.text += "\\n" + token.text\n this.inlineQueue.pop()\n this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n if (src) {\n const errMsg = "Infinite loop on byte: " + src.charCodeAt(0)\n if (this.options.silent) {\n console.error(errMsg)\n break\n } else {\n throw new Error(errMsg)\n }\n }\n }\n\n this.state.top = true\n return tokens\n }\n\n inline(src, tokens) {\n this.inlineQueue.push({ src, tokens })\n }\n\n /**\n * Lexing/Compiling\n */\n inlineTokens(src, tokens = []) {\n let token, lastToken, cutSrc\n\n // String with links masked to avoid interference with em and strong\n let maskedSrc = src\n let match\n let keepPrevChar, prevChar\n\n // Mask out reflinks\n if (this.tokens.links) {\n const links = Object.keys(this.tokens.links)\n if (links.length > 0) {\n while (\n (match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) !=\n null\n ) {\n if (\n links.includes(match[0].slice(match[0].lastIndexOf("[") + 1, -1))\n ) {\n maskedSrc =\n maskedSrc.slice(0, match.index) +\n "[" +\n repeatString("a", match[0].length - 2) +\n "]" +\n maskedSrc.slice(\n this.tokenizer.rules.inline.reflinkSearch.lastIndex\n )\n }\n }\n }\n }\n // Mask out other blocks\n while (\n (match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null\n ) {\n maskedSrc =\n maskedSrc.slice(0, match.index) +\n "[" +\n repeatString("a", match[0].length - 2) +\n "]" +\n maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex)\n }\n\n // Mask out escaped em & strong delimiters\n while (\n (match = this.tokenizer.rules.inline.escapedEmSt.exec(maskedSrc)) != null\n ) {\n maskedSrc =\n maskedSrc.slice(0, match.index) +\n "++" +\n maskedSrc.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex)\n }\n\n while (src) {\n if (!keepPrevChar) {\n prevChar = ""\n }\n keepPrevChar = false\n\n // extensions\n if (\n this.options.extensions &&\n this.options.extensions.inline &&\n this.options.extensions.inline.some(extTokenizer => {\n if ((token = extTokenizer.call({ lexer: this }, src, tokens))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n return true\n }\n return false\n })\n ) {\n continue\n }\n\n // escape\n if ((token = this.tokenizer.escape(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // tag\n if ((token = this.tokenizer.tag(src))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n if (lastToken && token.type === "text" && lastToken.type === "text") {\n lastToken.raw += token.raw\n lastToken.text += token.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n // link\n if ((token = this.tokenizer.link(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // reflink, nolink\n if ((token = this.tokenizer.reflink(src, this.tokens.links))) {\n src = src.substring(token.raw.length)\n lastToken = tokens[tokens.length - 1]\n if (lastToken && token.type === "text" && lastToken.type === "text") {\n lastToken.raw += token.raw\n lastToken.text += token.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n // em & strong\n if ((token = this.tokenizer.emStrong(src, maskedSrc, prevChar))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // code\n if ((token = this.tokenizer.codespan(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // br\n if ((token = this.tokenizer.br(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // del (gfm)\n if ((token = this.tokenizer.del(src))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // autolink\n if ((token = this.tokenizer.autolink(src, mangle))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // url (gfm)\n if (!this.state.inLink && (token = this.tokenizer.url(src, mangle))) {\n src = src.substring(token.raw.length)\n tokens.push(token)\n continue\n }\n\n // text\n // prevent inlineText consuming extensions by clipping \'src\' to extension start\n cutSrc = src\n if (this.options.extensions && this.options.extensions.startInline) {\n let startIndex = Infinity\n const tempSrc = src.slice(1)\n let tempStart\n this.options.extensions.startInline.forEach(function (getStartIndex) {\n tempStart = getStartIndex.call({ lexer: this }, tempSrc)\n if (typeof tempStart === "number" && tempStart >= 0) {\n startIndex = Math.min(startIndex, tempStart)\n }\n })\n if (startIndex < Infinity && startIndex >= 0) {\n cutSrc = src.substring(0, startIndex + 1)\n }\n }\n if ((token = this.tokenizer.inlineText(cutSrc, smartypants))) {\n src = src.substring(token.raw.length)\n if (token.raw.slice(-1) !== "_") {\n // Track prevChar before string of ____ started\n prevChar = token.raw.slice(-1)\n }\n keepPrevChar = true\n lastToken = tokens[tokens.length - 1]\n if (lastToken && lastToken.type === "text") {\n lastToken.raw += token.raw\n lastToken.text += token.text\n } else {\n tokens.push(token)\n }\n continue\n }\n\n if (src) {\n const errMsg = "Infinite loop on byte: " + src.charCodeAt(0)\n if (this.options.silent) {\n console.error(errMsg)\n break\n } else {\n throw new Error(errMsg)\n }\n }\n }\n\n return tokens\n }\n}\n\n/**\n * Renderer\n */\nclass Renderer {\n constructor(options) {\n this.options = options || defaults\n }\n\n code(code, infostring, escaped) {\n const lang = (infostring || "").match(/\\S*/)[0]\n if (this.options.highlight) {\n const out = this.options.highlight(code, lang)\n if (out != null && out !== code) {\n escaped = true\n code = out\n }\n }\n\n code = code.replace(/\\n$/, "") + "\\n"\n\n if (!lang) {\n return (\n "
    " +\n        (escaped ? code : escape(code, true)) +\n        "
    \\n"\n )\n }\n\n return (\n \'
    \' +\n      (escaped ? code : escape(code, true)) +\n      "
    \\n"\n )\n }\n\n /**\n * @param {string} quote\n */\n blockquote(quote) {\n return `
    \\n${quote}
    \\n`\n }\n\n html(html) {\n return html\n }\n\n /**\n * @param {string} text\n * @param {string} level\n * @param {string} raw\n * @param {any} slugger\n */\n heading(text, level, raw, slugger) {\n if (this.options.headerIds) {\n const id = this.options.headerPrefix + slugger.slug(raw)\n return `${text}\\n`\n }\n\n // ignore IDs\n return `${text}\\n`\n }\n\n hr() {\n return this.options.xhtml ? "
    \\n" : "
    \\n"\n }\n\n list(body, ordered, start) {\n const type = ordered ? "ol" : "ul",\n startatt = ordered && start !== 1 ? \' start="\' + start + \'"\' : ""\n return "<" + type + startatt + ">\\n" + body + "\\n"\n }\n\n /**\n * @param {string} text\n */\n listitem(text) {\n return `
  • ${text}
  • \\n`\n }\n\n checkbox(checked) {\n return (\n " "\n )\n }\n\n /**\n * @param {string} text\n */\n paragraph(text) {\n return `

    ${text}

    \\n`\n }\n\n /**\n * @param {string} header\n * @param {string} body\n */\n table(header, body) {\n if (body) body = `${body}`\n\n return (\n "\\n" + "\\n" + header + "\\n" + body + "
    \\n"\n )\n }\n\n /**\n * @param {string} content\n */\n tablerow(content) {\n return `\\n${content}\\n`\n }\n\n tablecell(content, flags) {\n const type = flags.header ? "th" : "td"\n const tag = flags.align ? `<${type} align="${flags.align}">` : `<${type}>`\n return tag + content + `\\n`\n }\n\n /**\n * span level renderer\n * @param {string} text\n */\n strong(text) {\n return `${text}`\n }\n\n /**\n * @param {string} text\n */\n em(text) {\n return `${text}`\n }\n\n /**\n * @param {string} text\n */\n codespan(text) {\n return `${text}`\n }\n\n br() {\n return this.options.xhtml ? "
    " : "
    "\n }\n\n /**\n * @param {string} text\n */\n del(text) {\n return `${text}`\n }\n\n /**\n * @param {string} href\n * @param {string} title\n * @param {string} text\n */\n link(href, title, text) {\n href = cleanUrl(this.options.sanitize, this.options.baseUrl, href)\n if (href === null) {\n return text\n }\n let out = \'
    "\n return out\n }\n\n /**\n * @param {string} href\n * @param {string} title\n * @param {string} text\n */\n image(href, title, text) {\n href = cleanUrl(this.options.sanitize, this.options.baseUrl, href)\n if (href === null) {\n return text\n }\n\n let out = `${text}" : ">"\n return out\n }\n\n text(text) {\n return text\n }\n}\n\n/**\n * TextRenderer\n * returns only the textual part of the token\n */\nclass TextRenderer {\n // no need for block level renderers\n strong(text) {\n return text\n }\n\n em(text) {\n return text\n }\n\n codespan(text) {\n return text\n }\n\n del(text) {\n return text\n }\n\n html(text) {\n return text\n }\n\n text(text) {\n return text\n }\n\n link(href, title, text) {\n return "" + text\n }\n\n image(href, title, text) {\n return "" + text\n }\n\n br() {\n return ""\n }\n}\n\n/**\n * Slugger generates header id\n */\nclass Slugger {\n constructor() {\n this.seen = {}\n }\n\n /**\n * @param {string} value\n */\n serialize(value) {\n return (\n value\n .toLowerCase()\n .trim()\n // remove html tags\n .replace(/<[!\\/a-z].*?>/gi, "")\n // remove unwanted chars\n .replace(\n /[\\u2000-\\u206F\\u2E00-\\u2E7F\\\\\'!"#$%&()*+,./:;<=>?@[\\]^`{|}~]/g,\n ""\n )\n .replace(/\\s/g, "-")\n )\n }\n\n /**\n * Finds the next safe (unique) slug to use\n * @param {string} originalSlug\n * @param {boolean} isDryRun\n */\n getNextSafeSlug(originalSlug, isDryRun) {\n let slug = originalSlug\n let occurenceAccumulator = 0\n if (this.seen.hasOwnProperty(slug)) {\n occurenceAccumulator = this.seen[originalSlug]\n do {\n occurenceAccumulator++\n slug = originalSlug + "-" + occurenceAccumulator\n } while (this.seen.hasOwnProperty(slug))\n }\n if (!isDryRun) {\n this.seen[originalSlug] = occurenceAccumulator\n this.seen[slug] = 0\n }\n return slug\n }\n\n /**\n * Convert string to unique id\n * @param {object} [options]\n * @param {boolean} [options.dryrun] Generates the next unique slug without\n * updating the internal accumulator.\n */\n slug(value, options = {}) {\n const slug = this.serialize(value)\n return this.getNextSafeSlug(slug, options.dryrun)\n }\n}\n\n/**\n * Parsing & Compiling\n */\nclass Parser {\n constructor(options) {\n this.options = options || defaults\n this.options.renderer = this.options.renderer || new Renderer()\n this.renderer = this.options.renderer\n this.renderer.options = this.options\n this.textRenderer = new TextRenderer()\n this.slugger = new Slugger()\n }\n\n /**\n * Static Parse Method\n */\n static parse(tokens, options) {\n const parser = new Parser(options)\n return parser.parse(tokens)\n }\n\n /**\n * Static Parse Inline Method\n */\n static parseInline(tokens, options) {\n const parser = new Parser(options)\n return parser.parseInline(tokens)\n }\n\n /**\n * Parse Loop\n */\n parse(tokens, top = true) {\n let out = "",\n i,\n j,\n k,\n l2,\n l3,\n row,\n cell,\n header,\n body,\n token,\n ordered,\n start,\n loose,\n itemBody,\n item,\n checked,\n task,\n checkbox,\n ret\n\n const l = tokens.length\n for (i = 0; i < l; i++) {\n token = tokens[i]\n\n // Run any renderer extensions\n if (\n this.options.extensions &&\n this.options.extensions.renderers &&\n this.options.extensions.renderers[token.type]\n ) {\n ret = this.options.extensions.renderers[token.type].call(\n { parser: this },\n token\n )\n if (\n ret !== false ||\n ![\n "space",\n "hr",\n "heading",\n "code",\n "table",\n "blockquote",\n "list",\n "html",\n "paragraph",\n "text",\n ].includes(token.type)\n ) {\n out += ret || ""\n continue\n }\n }\n\n switch (token.type) {\n case "space": {\n continue\n }\n case "hr": {\n out += this.renderer.hr()\n continue\n }\n case "heading": {\n out += this.renderer.heading(\n this.parseInline(token.tokens),\n token.depth,\n unescape(this.parseInline(token.tokens, this.textRenderer)),\n this.slugger\n )\n continue\n }\n case "code": {\n out += this.renderer.code(token.text, token.lang, token.escaped)\n continue\n }\n case "table": {\n header = ""\n\n // header\n cell = ""\n l2 = token.header.length\n for (j = 0; j < l2; j++) {\n cell += this.renderer.tablecell(\n this.parseInline(token.header[j].tokens),\n { header: true, align: token.align[j] }\n )\n }\n header += this.renderer.tablerow(cell)\n\n body = ""\n l2 = token.rows.length\n for (j = 0; j < l2; j++) {\n row = token.rows[j]\n\n cell = ""\n l3 = row.length\n for (k = 0; k < l3; k++) {\n cell += this.renderer.tablecell(this.parseInline(row[k].tokens), {\n header: false,\n align: token.align[k],\n })\n }\n\n body += this.renderer.tablerow(cell)\n }\n out += this.renderer.table(header, body)\n continue\n }\n case "blockquote": {\n body = this.parse(token.tokens)\n out += this.renderer.blockquote(body)\n continue\n }\n case "list": {\n ordered = token.ordered\n start = token.start\n loose = token.loose\n l2 = token.items.length\n\n body = ""\n for (j = 0; j < l2; j++) {\n item = token.items[j]\n checked = item.checked\n task = item.task\n\n itemBody = ""\n if (item.task) {\n checkbox = this.renderer.checkbox(checked)\n if (loose) {\n if (\n item.tokens.length > 0 &&\n item.tokens[0].type === "paragraph"\n ) {\n item.tokens[0].text = checkbox + " " + item.tokens[0].text\n if (\n item.tokens[0].tokens &&\n item.tokens[0].tokens.length > 0 &&\n item.tokens[0].tokens[0].type === "text"\n ) {\n item.tokens[0].tokens[0].text =\n checkbox + " " + item.tokens[0].tokens[0].text\n }\n } else {\n item.tokens.unshift({\n type: "text",\n text: checkbox,\n })\n }\n } else {\n itemBody += checkbox\n }\n }\n\n itemBody += this.parse(item.tokens, loose)\n body += this.renderer.listitem(itemBody, task, checked)\n }\n\n out += this.renderer.list(body, ordered, start)\n continue\n }\n case "html": {\n // TODO parse inline content if parameter markdown=1\n out += this.renderer.html(token.text)\n continue\n }\n case "paragraph": {\n out += this.renderer.paragraph(this.parseInline(token.tokens))\n continue\n }\n case "text": {\n body = token.tokens ? this.parseInline(token.tokens) : token.text\n while (i + 1 < l && tokens[i + 1].type === "text") {\n token = tokens[++i]\n body +=\n "\\n" +\n (token.tokens ? this.parseInline(token.tokens) : token.text)\n }\n out += top ? this.renderer.paragraph(body) : body\n continue\n }\n\n default: {\n const errMsg = \'Token with "\' + token.type + \'" type was not found.\'\n if (this.options.silent) {\n console.error(errMsg)\n return\n } else {\n throw new Error(errMsg)\n }\n }\n }\n }\n\n return out\n }\n\n /**\n * Parse Inline Tokens\n */\n parseInline(tokens, renderer) {\n renderer = renderer || this.renderer\n let out = "",\n i,\n token,\n ret\n\n const l = tokens.length\n for (i = 0; i < l; i++) {\n token = tokens[i]\n\n // Run any renderer extensions\n if (\n this.options.extensions &&\n this.options.extensions.renderers &&\n this.options.extensions.renderers[token.type]\n ) {\n ret = this.options.extensions.renderers[token.type].call(\n { parser: this },\n token\n )\n if (\n ret !== false ||\n ![\n "escape",\n "html",\n "link",\n "image",\n "strong",\n "em",\n "codespan",\n "br",\n "del",\n "text",\n ].includes(token.type)\n ) {\n out += ret || ""\n continue\n }\n }\n\n switch (token.type) {\n case "escape": {\n out += renderer.text(token.text)\n break\n }\n case "html": {\n out += renderer.html(token.text)\n break\n }\n case "link": {\n out += renderer.link(\n token.href,\n token.title,\n this.parseInline(token.tokens, renderer)\n )\n break\n }\n case "image": {\n out += renderer.image(token.href, token.title, token.text)\n break\n }\n case "strong": {\n out += renderer.strong(this.parseInline(token.tokens, renderer))\n break\n }\n case "em": {\n out += renderer.em(this.parseInline(token.tokens, renderer))\n break\n }\n case "codespan": {\n out += renderer.codespan(token.text)\n break\n }\n case "br": {\n out += renderer.br()\n break\n }\n case "del": {\n out += renderer.del(this.parseInline(token.tokens, renderer))\n break\n }\n case "text": {\n out += renderer.text(token.text)\n break\n }\n default: {\n const errMsg = \'Token with "\' + token.type + \'" type was not found.\'\n if (this.options.silent) {\n console.error(errMsg)\n return\n } else {\n throw new Error(errMsg)\n }\n }\n }\n }\n return out\n }\n}\n\n/**\n * Marked\n */\nfunction marked(src, opt, callback) {\n // throw error in case of non string input\n if (typeof src === "undefined" || src === null) {\n throw new Error("marked(): input parameter is undefined or null")\n }\n if (typeof src !== "string") {\n throw new Error(\n "marked(): input parameter is of type " +\n Object.prototype.toString.call(src) +\n ", string expected"\n )\n }\n\n if (typeof opt === "function") {\n callback = opt\n opt = null\n }\n\n opt = merge({}, marked.defaults, opt || {})\n checkSanitizeDeprecation(opt)\n\n if (callback) {\n const highlight = opt.highlight\n let tokens\n\n try {\n tokens = Lexer.lex(src, opt)\n } catch (e) {\n return callback(e)\n }\n\n const done = function (err) {\n let out\n\n if (!err) {\n try {\n if (opt.walkTokens) {\n marked.walkTokens(tokens, opt.walkTokens)\n }\n out = Parser.parse(tokens, opt)\n } catch (e) {\n err = e\n }\n }\n\n opt.highlight = highlight\n\n return err ? callback(err) : callback(null, out)\n }\n\n if (!highlight || highlight.length < 3) {\n return done()\n }\n\n delete opt.highlight\n\n if (!tokens.length) return done()\n\n let pending = 0\n marked.walkTokens(tokens, function (token) {\n if (token.type === "code") {\n pending++\n setTimeout(() => {\n highlight(token.text, token.lang, function (err, code) {\n if (err) {\n return done(err)\n }\n if (code != null && code !== token.text) {\n token.text = code\n token.escaped = true\n }\n\n pending--\n if (pending === 0) {\n done()\n }\n })\n }, 0)\n }\n })\n\n if (pending === 0) {\n done()\n }\n\n return\n }\n\n try {\n const tokens = Lexer.lex(src, opt)\n if (opt.walkTokens) {\n marked.walkTokens(tokens, opt.walkTokens)\n }\n return Parser.parse(tokens, opt)\n } catch (e) {\n e.message += "\\nPlease report this to https://github.com/markedjs/marked."\n if (opt.silent) {\n return (\n "

    An error occurred:

    " +\n        escape(e.message + "", true) +\n        "
    "\n )\n }\n throw e\n }\n}\n\n/**\n * Options\n */\n\nmarked.options = marked.setOptions = function (opt) {\n merge(marked.defaults, opt)\n changeDefaults(marked.defaults)\n return marked\n}\n\nmarked.getDefaults = getDefaults\n\nmarked.defaults = defaults\n\n/**\n * Use Extension\n */\n\nmarked.use = function (...args) {\n const opts = merge({}, ...args)\n const extensions = marked.defaults.extensions || {\n renderers: {},\n childTokens: {},\n }\n let hasExtensions\n\n args.forEach(pack => {\n // ==-- Parse "addon" extensions --== //\n if (pack.extensions) {\n hasExtensions = true\n pack.extensions.forEach(ext => {\n if (!ext.name) {\n throw new Error("extension name required")\n }\n if (ext.renderer) {\n // Renderer extensions\n const prevRenderer = extensions.renderers\n ? extensions.renderers[ext.name]\n : null\n if (prevRenderer) {\n // Replace extension with func to run new extension but fall back if false\n extensions.renderers[ext.name] = function (...args) {\n let ret = ext.renderer.apply(this, args)\n if (ret === false) {\n ret = prevRenderer.apply(this, args)\n }\n return ret\n }\n } else {\n extensions.renderers[ext.name] = ext.renderer\n }\n }\n if (ext.tokenizer) {\n // Tokenizer Extensions\n if (!ext.level || (ext.level !== "block" && ext.level !== "inline")) {\n throw new Error("extension level must be \'block\' or \'inline\'")\n }\n if (extensions[ext.level]) {\n extensions[ext.level].unshift(ext.tokenizer)\n } else {\n extensions[ext.level] = [ext.tokenizer]\n }\n if (ext.start) {\n // Function to check for start of token\n if (ext.level === "block") {\n if (extensions.startBlock) {\n extensions.startBlock.push(ext.start)\n } else {\n extensions.startBlock = [ext.start]\n }\n } else if (ext.level === "inline") {\n if (extensions.startInline) {\n extensions.startInline.push(ext.start)\n } else {\n extensions.startInline = [ext.start]\n }\n }\n }\n }\n if (ext.childTokens) {\n // Child tokens to be visited by walkTokens\n extensions.childTokens[ext.name] = ext.childTokens\n }\n })\n }\n\n // ==-- Parse "overwrite" extensions --== //\n if (pack.renderer) {\n const renderer = marked.defaults.renderer || new Renderer()\n for (const prop in pack.renderer) {\n const prevRenderer = renderer[prop]\n // Replace renderer with func to run extension, but fall back if false\n renderer[prop] = (...args) => {\n let ret = pack.renderer[prop].apply(renderer, args)\n if (ret === false) {\n ret = prevRenderer.apply(renderer, args)\n }\n return ret\n }\n }\n opts.renderer = renderer\n }\n if (pack.tokenizer) {\n const tokenizer = marked.defaults.tokenizer || new Tokenizer()\n for (const prop in pack.tokenizer) {\n const prevTokenizer = tokenizer[prop]\n // Replace tokenizer with func to run extension, but fall back if false\n tokenizer[prop] = (...args) => {\n let ret = pack.tokenizer[prop].apply(tokenizer, args)\n if (ret === false) {\n ret = prevTokenizer.apply(tokenizer, args)\n }\n return ret\n }\n }\n opts.tokenizer = tokenizer\n }\n\n // ==-- Parse WalkTokens extensions --== //\n if (pack.walkTokens) {\n const walkTokens = marked.defaults.walkTokens\n opts.walkTokens = function (token) {\n pack.walkTokens.call(this, token)\n if (walkTokens) {\n walkTokens.call(this, token)\n }\n }\n }\n\n if (hasExtensions) {\n opts.extensions = extensions\n }\n\n marked.setOptions(opts)\n })\n}\n\n/**\n * Run callback for every token\n */\n\nmarked.walkTokens = function (tokens, callback) {\n for (const token of tokens) {\n callback.call(marked, token)\n switch (token.type) {\n case "table": {\n for (const cell of token.header) {\n marked.walkTokens(cell.tokens, callback)\n }\n for (const row of token.rows) {\n for (const cell of row) {\n marked.walkTokens(cell.tokens, callback)\n }\n }\n break\n }\n case "list": {\n marked.walkTokens(token.items, callback)\n break\n }\n default: {\n if (\n marked.defaults.extensions &&\n marked.defaults.extensions.childTokens &&\n marked.defaults.extensions.childTokens[token.type]\n ) {\n // Walk any extensions\n marked.defaults.extensions.childTokens[token.type].forEach(function (\n childTokens\n ) {\n marked.walkTokens(token[childTokens], callback)\n })\n } else if (token.tokens) {\n marked.walkTokens(token.tokens, callback)\n }\n }\n }\n }\n}\n\n/**\n * Parse Inline\n * @param {string} src\n */\nmarked.parseInline = function (src, opt) {\n // throw error in case of non string input\n if (typeof src === "undefined" || src === null) {\n throw new Error(\n "marked.parseInline(): input parameter is undefined or null"\n )\n }\n if (typeof src !== "string") {\n throw new Error(\n "marked.parseInline(): input parameter is of type " +\n Object.prototype.toString.call(src) +\n ", string expected"\n )\n }\n\n opt = merge({}, marked.defaults, opt || {})\n checkSanitizeDeprecation(opt)\n\n try {\n const tokens = Lexer.lexInline(src, opt)\n if (opt.walkTokens) {\n marked.walkTokens(tokens, opt.walkTokens)\n }\n return Parser.parseInline(tokens, opt)\n } catch (e) {\n e.message += "\\nPlease report this to https://github.com/markedjs/marked."\n if (opt.silent) {\n return (\n "

    An error occurred:

    " +\n        escape(e.message + "", true) +\n        "
    "\n )\n }\n throw e\n }\n}\n\n/**\n * Expose\n */\nmarked.Parser = Parser\nmarked.parser = Parser.parse\nmarked.Renderer = Renderer\nmarked.TextRenderer = TextRenderer\nmarked.Lexer = Lexer\nmarked.lexer = Lexer.lex\nmarked.Tokenizer = Tokenizer\nmarked.Slugger = Slugger\nmarked.parse = marked\n\nconst options = marked.options\nconst setOptions = marked.setOptions\nconst use = marked.use\nconst walkTokens = marked.walkTokens\nconst parseInline = marked.parseInline\nconst parse = marked\nconst parser = Parser.parse\nconst lexer = Lexer.lex\n\nconst email = trigger.row\nreturn marked(email.Message)' From 208464a158031be8374f684a25045448f32586fc Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:02:37 +0000 Subject: [PATCH 31/59] Fix snippet decorator regex --- .../builder/src/components/common/bindings/SnippetDrawer.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index 3badf0d8c3..0989432735 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -80,6 +80,8 @@ } return null } + + $: console.log(nameError) From 8a455781d4a63cb3ef12f62bd786c8b647dccc64 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:03:57 +0000 Subject: [PATCH 32/59] Fix regex. Wrong file before --- .../builder/src/components/common/CodeEditor/CodeEditor.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte index 1ddc3d802a..5eefe11e58 100644 --- a/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte +++ b/packages/builder/src/components/common/CodeEditor/CodeEditor.svelte @@ -109,7 +109,7 @@ // Match decoration for snippets const snippetMatchDeco = new MatchDecorator({ - regexp: /snippets.[^\s(]+/g, + regexp: /snippets\.[^\s(]+/g, decoration: () => { return Decoration.mark({ tag: "span", From c9c0384c96e261079506ef6aec32d7e79a744f73 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:05:21 +0000 Subject: [PATCH 33/59] Fix being unable to edit snippets --- .../src/components/common/bindings/SnippetDrawer.svelte | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index 0989432735..a8973c753b 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -80,8 +80,6 @@ } return null } - - $: console.log(nameError) @@ -114,7 +112,11 @@ Delete {/if} - From 886929b8bc82706e6360394db91ad219d8984853 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:06:42 +0000 Subject: [PATCH 34/59] Fix being unable to hide side panels in the binding editor again. Already fixed this but got lost in a merge --- .../builder/src/components/common/bindings/BindingPanel.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/common/bindings/BindingPanel.svelte b/packages/builder/src/components/common/bindings/BindingPanel.svelte index 01c2f5d55b..7a29e20700 100644 --- a/packages/builder/src/components/common/bindings/BindingPanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingPanel.svelte @@ -84,7 +84,7 @@ $: bindingHelpers = new BindingHelpers(getCaretPosition, insertAtPos) $: { // Ensure a valid side panel option is always selected - if (!sidePanelOptions.includes(sidePanel)) { + if (sidePanel && !sidePanelOptions.includes(sidePanel)) { sidePanel = sidePanelOptions[0] } } From 64855bbdf03d0ce3e7cfeec103d4236940d64766 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:11:09 +0000 Subject: [PATCH 35/59] Optimise cloneDeep usage in string templates --- packages/string-templates/src/helpers/javascript.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index 76ef19ef2e..26c2753295 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -54,8 +54,9 @@ module.exports.processJS = (handlebars, context) => { // Our $ context function gets a value from context. // We clone the context to avoid mutation in the binding affecting real // app context. + const clonedContext = cloneDeep(context) const sandboxContext = { - $: path => getContextValue(path, cloneDeep(context)), + $: path => getContextValue(path, clonedContext), helpers: getJsHelperList(), // Proxy to evaluate snippets when running in the browser From 861d48dbf32aa08d6e582c7951fef4d4fb45f73d Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:37:49 +0000 Subject: [PATCH 36/59] Transform snippets into a map in the browser for faster access --- packages/string-templates/src/helpers/javascript.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index 26c2753295..e38f9b5651 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -51,10 +51,16 @@ module.exports.processJS = (handlebars, context) => { // This is required to allow the final `return` statement to be valid. const js = iifeWrapper(atob(handlebars)) + // Transform snippets into an object for faster access + let snippetMap = {} + for (let snippet of context.snippets || []) { + snippetMap[snippet.name] = snippet.code + } + // Our $ context function gets a value from context. // We clone the context to avoid mutation in the binding affecting real // app context. - const clonedContext = cloneDeep(context) + const clonedContext = cloneDeep({ ...context, snippets: null }) const sandboxContext = { $: path => getContextValue(path, clonedContext), helpers: getJsHelperList(), @@ -64,9 +70,7 @@ module.exports.processJS = (handlebars, context) => { {}, { get: function (_, name) { - // This will error if the snippet doesn't exist, but that's intended - const snippet = (context.snippets || []).find(x => x.name === name) - return eval(iifeWrapper(snippet.code)) + return eval(iifeWrapper(snippetMap[name])) }, } ), From 663abde785a6af0294a5e561d979c687badcbc5c Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:48:55 +0000 Subject: [PATCH 37/59] Optimise isolated-vm snippet performance by using a map and by caching evaluated snippets --- .../jsRunner/bundles/snippets.ivm.bundle.js | 6 +++--- .../server/src/jsRunner/bundles/snippets.ts | 19 ++++++++++++------- .../server/src/jsRunner/vm/isolated-vm.ts | 8 +++++++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js index 5adb19eaf7..85fb8bbd5a 100644 --- a/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js +++ b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js @@ -1,3 +1,3 @@ -"use strict";var snippets=(()=>{var u=Object.create;var p=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var m=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var l=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports),W=(e,n)=>{for(var i in n)p(e,i,{get:n[i],enumerable:!0})},o=(e,n,i,t)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of d(n))!x.call(e,r)&&r!==i&&p(e,r,{get:()=>n[r],enumerable:!(t=c(n,r))||t.enumerable});return e};var g=(e,n,i)=>(i=e!=null?u(m(e)):{},o(n||!e||!e.__esModule?p(i,"default",{value:e,enumerable:!0}):i,e)),v=e=>o(p({},"__esModule",{value:!0}),e);var a=l((P,f)=>{f.exports.iifeWrapper=e=>`(function(){ -${e} -})();`});var y={};W(y,{default:()=>w});var s=g(a()),w=new Proxy({},{get:function(e,n){let i=(snippetDefinitions||[]).find(t=>t.name===n);return[eval][0]((0,s.iifeWrapper)(i.code))}});return v(y);})(); +"use strict";var snippets=(()=>{var a=Object.create;var r=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var l=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var C=(i,e)=>()=>(e||i((e={exports:{}}).exports,e),e.exports),y=(i,e)=>{for(var n in e)r(i,n,{get:e[n],enumerable:!0})},f=(i,e,n,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let p of h(e))!x.call(i,p)&&p!==n&&r(i,p,{get:()=>e[p],enumerable:!(t=c(e,p))||t.enumerable});return i};var W=(i,e,n)=>(n=i!=null?a(l(i)):{},f(e||!i||!i.__esModule?r(n,"default",{value:i,enumerable:!0}):n,i)),d=i=>f(r({},"__esModule",{value:!0}),i);var s=C((D,o)=>{o.exports.iifeWrapper=i=>`(function(){ +${i} +})();`});var v={};y(v,{default:()=>g});var u=W(s()),g=new Proxy({},{get:function(i,e){if(!(e in snippetCache))snippetCache[e]=[eval][0]((0,u.iifeWrapper)(snippetDefinitions[e]));else return n=>n*2;return snippetCache[e]}});return d(v);})(); diff --git a/packages/server/src/jsRunner/bundles/snippets.ts b/packages/server/src/jsRunner/bundles/snippets.ts index f473aaf7b4..258d501a27 100644 --- a/packages/server/src/jsRunner/bundles/snippets.ts +++ b/packages/server/src/jsRunner/bundles/snippets.ts @@ -6,14 +6,19 @@ export default new Proxy( {}, { get: function (_, name) { - // Snippet definitions are injected to the isolate global scope before - // this bundle is loaded, so we can access it from there. - // https://esbuild.github.io/content-types/#direct-eval for info on why - // eval is being called this way. + // Both snippetDefinitions and snippetCache are injected to the isolate + // global scope before this bundle is loaded, so we can access it from + // there. + // See https://esbuild.github.io/content-types/#direct-eval for info on + // why eval is being called this way. + // Snippets are cached and reused once they have been evaluated. // @ts-ignore - // eslint-disable-next-line no-undef - const snippet = (snippetDefinitions || []).find(x => x.name === name) - return [eval][0](iifeWrapper(snippet.code)) + if (!(name in snippetCache)) { + // @ts-ignore + snippetCache[name] = [eval][0](iifeWrapper(snippetDefinitions[name])) + } + // @ts-ignore + return snippetCache[name] }, } ) diff --git a/packages/server/src/jsRunner/vm/isolated-vm.ts b/packages/server/src/jsRunner/vm/isolated-vm.ts index 64e68c296d..e89d420ec5 100644 --- a/packages/server/src/jsRunner/vm/isolated-vm.ts +++ b/packages/server/src/jsRunner/vm/isolated-vm.ts @@ -99,9 +99,15 @@ export class IsolatedVM implements VM { } withSnippets(snippets?: Snippet[]) { + // Transform snippets into a map for faster access + let snippetMap: Record = {} + for (let snippet of snippets || []) { + snippetMap[snippet.name] = snippet.code + } const snippetsSource = loadBundle(BundleType.SNIPPETS) const script = this.isolate.compileScriptSync(` - const snippetDefinitions = ${JSON.stringify(snippets || [])}; + const snippetDefinitions = ${JSON.stringify(snippetMap)}; + const snippetCache = {}; ${snippetsSource}; snippets = snippets.default; `) From 30622a56ca0e3546d7ab79809602c33ce4a574cc Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:50:26 +0000 Subject: [PATCH 38/59] Add updated snippets IVM bundle --- packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js index 85fb8bbd5a..bad9049af0 100644 --- a/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js +++ b/packages/server/src/jsRunner/bundles/snippets.ivm.bundle.js @@ -1,3 +1,3 @@ -"use strict";var snippets=(()=>{var a=Object.create;var r=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var l=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var C=(i,e)=>()=>(e||i((e={exports:{}}).exports,e),e.exports),y=(i,e)=>{for(var n in e)r(i,n,{get:e[n],enumerable:!0})},f=(i,e,n,t)=>{if(e&&typeof e=="object"||typeof e=="function")for(let p of h(e))!x.call(i,p)&&p!==n&&r(i,p,{get:()=>e[p],enumerable:!(t=c(e,p))||t.enumerable});return i};var W=(i,e,n)=>(n=i!=null?a(l(i)):{},f(e||!i||!i.__esModule?r(n,"default",{value:i,enumerable:!0}):n,i)),d=i=>f(r({},"__esModule",{value:!0}),i);var s=C((D,o)=>{o.exports.iifeWrapper=i=>`(function(){ +"use strict";var snippets=(()=>{var u=Object.create;var n=Object.defineProperty;var a=Object.getOwnPropertyDescriptor;var h=Object.getOwnPropertyNames;var x=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var l=(i,e)=>()=>(e||i((e={exports:{}}).exports,e),e.exports),W=(i,e)=>{for(var p in e)n(i,p,{get:e[p],enumerable:!0})},f=(i,e,p,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let t of h(e))!C.call(i,t)&&t!==p&&n(i,t,{get:()=>e[t],enumerable:!(r=a(e,t))||r.enumerable});return i};var d=(i,e,p)=>(p=i!=null?u(x(i)):{},f(e||!i||!i.__esModule?n(p,"default",{value:i,enumerable:!0}):p,i)),g=i=>f(n({},"__esModule",{value:!0}),i);var s=l((D,o)=>{o.exports.iifeWrapper=i=>`(function(){ ${i} -})();`});var v={};y(v,{default:()=>g});var u=W(s()),g=new Proxy({},{get:function(i,e){if(!(e in snippetCache))snippetCache[e]=[eval][0]((0,u.iifeWrapper)(snippetDefinitions[e]));else return n=>n*2;return snippetCache[e]}});return d(v);})(); +})();`});var w={};W(w,{default:()=>v});var c=d(s()),v=new Proxy({},{get:function(i,e){return e in snippetCache||(snippetCache[e]=[eval][0]((0,c.iifeWrapper)(snippetDefinitions[e]))),snippetCache[e]}});return g(w);})(); From 95f71efdab75c8f8e6806d09a83131d3e3258d00 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 12:52:36 +0000 Subject: [PATCH 39/59] Cache snippet evaluations in the browser --- packages/string-templates/src/helpers/javascript.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index e38f9b5651..5be2619463 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -51,8 +51,10 @@ module.exports.processJS = (handlebars, context) => { // This is required to allow the final `return` statement to be valid. const js = iifeWrapper(atob(handlebars)) - // Transform snippets into an object for faster access + // Transform snippets into an object for faster access, and cache previously + // evaluated snippets let snippetMap = {} + let snippetCache = {} for (let snippet of context.snippets || []) { snippetMap[snippet.name] = snippet.code } @@ -70,7 +72,10 @@ module.exports.processJS = (handlebars, context) => { {}, { get: function (_, name) { - return eval(iifeWrapper(snippetMap[name])) + if (!(name in snippetCache)) { + snippetCache[name] = eval(iifeWrapper(snippetMap[name])) + } + return snippetCache[name] }, } ), From 5666a965e068a12c5086a92ab1d8cdce9e557172 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 13:01:44 +0000 Subject: [PATCH 40/59] Fix issue with click_outside and drawers --- packages/bbui/src/Actions/click_outside.js | 4 +- packages/bbui/src/Drawer/Drawer.svelte | 67 ++++++++++++---------- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/packages/bbui/src/Actions/click_outside.js b/packages/bbui/src/Actions/click_outside.js index 62416ae88d..12c4c4d002 100644 --- a/packages/bbui/src/Actions/click_outside.js +++ b/packages/bbui/src/Actions/click_outside.js @@ -33,8 +33,8 @@ const handleClick = event => { } // Ignore clicks for drawers, unless the handler is registered from a drawer - const sourceInDrawer = handler.anchor.closest(".drawer-container") != null - const clickInDrawer = event.target.closest(".drawer-container") != null + const sourceInDrawer = handler.anchor.closest(".drawer-wrapper") != null + const clickInDrawer = event.target.closest(".drawer-wrapper") != null if (clickInDrawer && !sourceInDrawer) { return } diff --git a/packages/bbui/src/Drawer/Drawer.svelte b/packages/bbui/src/Drawer/Drawer.svelte index 04e678c4e5..89ee92726d 100644 --- a/packages/bbui/src/Drawer/Drawer.svelte +++ b/packages/bbui/src/Drawer/Drawer.svelte @@ -172,37 +172,44 @@ {#if visible} -
    -
    0} - class:modal={$modal} - transition:drawerSlide|local - {style} - > -
    - {#if $$slots.title} - - {:else} -
    {title || "Bindings"}
    - {/if} -
    - - - {#if $resizable} - modal.set(!$modal)} - > - - + +
    +
    +
    0} + class:modal={$modal} + transition:drawerSlide|local + {style} + > +
    + {#if $$slots.title} + + {:else} +
    {title || "Bindings"}
    {/if} -
    -
    - -
    +
    + + + {#if $resizable} + modal.set(!$modal)} + > + + + {/if} +
    + + +
    +
    {/if} From 6d53b0676211b6a997325a0db1968f2ab4a65ab0 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 13:23:48 +0000 Subject: [PATCH 41/59] Fix typo in automations placeholder --- .../automation/AutomationPanel/AutomationPanel.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte index 582db950fe..ac1c4f91cb 100644 --- a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte @@ -49,7 +49,7 @@ + - - { - search = null - }} - class:searching={search} - > - - + {:else} +
    Bindings
    + + {/if}
    {/if} - {#if !selectedCategory && !search}
      {#each categoryNames as categoryName} @@ -281,18 +303,15 @@ background: var(--background); z-index: 1; } - .header :global(input) { border: none; border-radius: 0; background: none; padding: 0; } - .search-input { - flex: 1; - } - .search-input-icon.searching { - cursor: pointer; + .search-input, + .title { + flex: 1 1 auto; } ul.category-list { diff --git a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte index 592ca7cfcd..72c9b2f44c 100644 --- a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte @@ -89,11 +89,29 @@ bind:value={search} />
    - + {:else}
    Snippets
    - - + + {/if}
    @@ -108,9 +126,9 @@ editSnippet(e, snippet)} - color="var(--spectrum-global-color-gray-700)" />
    {/each} From 567cbf3ef89feaffc809d385da5229b0bcbfec7c Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 13:58:42 +0000 Subject: [PATCH 43/59] More icon updates for consistency --- packages/bbui/src/Icon/Icon.svelte | 10 +++++++++- .../common/bindings/EvaluationSidePanel.svelte | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/bbui/src/Icon/Icon.svelte b/packages/bbui/src/Icon/Icon.svelte index 275c339bf4..13452cf981 100644 --- a/packages/bbui/src/Icon/Icon.svelte +++ b/packages/bbui/src/Icon/Icon.svelte @@ -14,6 +14,7 @@ export let disabled = false export let color export let tooltip + export let newStyles = false $: rotation = getRotation(direction) @@ -28,6 +29,7 @@
    (showTooltip = true)} on:focus={() => (showTooltip = true)} on:mouseleave={() => (showTooltip = false)} @@ -60,6 +62,9 @@ display: grid; place-items: center; } + .newStyles { + color: var(--spectrum-global-color-gray-700); + } svg.hoverable { pointer-events: all; @@ -72,7 +77,10 @@ svg.hoverable:active { color: var(--spectrum-global-color-blue-400) !important; } - + .newStyles svg.hoverable:hover, + .newStyles svg.hoverable:active { + color: var(--spectrum-global-color-gray-900) !important; + } svg.disabled { color: var(--spectrum-global-color-gray-500) !important; pointer-events: none !important; diff --git a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte index 82c3f80a6f..1bd55b4e21 100644 --- a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte @@ -65,7 +65,7 @@ {/if} {#if !empty} - + {/if} {/if}
    From 138cd39c36d8e905fb9ca50fc9fc2a183c564e3c Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 13:59:14 +0000 Subject: [PATCH 44/59] Autofocus search inputs --- .../src/components/common/bindings/SnippetSidePanel.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte index 72c9b2f44c..c82eed7a23 100644 --- a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte @@ -87,6 +87,7 @@ placeholder="Search for snippets" autocomplete="off" bind:value={search} + autofocus />
    Date: Wed, 13 Mar 2024 14:29:50 +0000 Subject: [PATCH 45/59] Update packages/shared-core/src/constants/index.ts Co-authored-by: Sam Rose --- packages/shared-core/src/constants/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shared-core/src/constants/index.ts b/packages/shared-core/src/constants/index.ts index b5b651a3da..633fd36e45 100644 --- a/packages/shared-core/src/constants/index.ts +++ b/packages/shared-core/src/constants/index.ts @@ -98,7 +98,7 @@ export enum BuilderSocketEvent { export const SocketSessionTTL = 60 export const ValidQueryNameRegex = /^[^()]*$/ export const ValidColumnNameRegex = /^[_a-zA-Z0-9\s]*$/g -export const ValidSnippetNameRegex = /^[a-z-_][a-z0-9-_]*$/i +export const ValidSnippetNameRegex = /^[a-z_][a-z0-9_]*$/i export const REBOOT_CRON = "@reboot" From 47925e394d8c22baf90c09bdef3da87ddaad4025 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 16:20:18 +0000 Subject: [PATCH 46/59] Lint and remove outdated comment --- .../src/components/common/bindings/BindingSidePanel.svelte | 2 -- .../src/components/common/bindings/SnippetDrawer.svelte | 6 ------ 2 files changed, 8 deletions(-) diff --git a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte index 4c54ef2698..f364b39ba9 100644 --- a/packages/builder/src/components/common/bindings/BindingSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/BindingSidePanel.svelte @@ -3,7 +3,6 @@ import { convertToJS } from "@budibase/string-templates" import { Input, Layout, Icon, Popover } from "@budibase/bbui" import { handlebarsCompletions } from "constants/completions" - import { tick } from "svelte" export let addHelper export let addBinding @@ -154,7 +153,6 @@ -
    {#if selectedCategory} diff --git a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte index a8973c753b..d6b6f92b17 100644 --- a/packages/builder/src/components/common/bindings/SnippetDrawer.svelte +++ b/packages/builder/src/components/common/bindings/SnippetDrawer.svelte @@ -59,12 +59,6 @@ loading = false } - // Validating function names is not as easy as you think. A simple regex does - // not work, as there are a bunch of reserved words. The correct regex for - // this is about 12K characters long. - // Instead, we can run a simple regex to roughly validate it, then basically - // try executing it and see if it's valid JS. The initial regex prevents - // against any potential XSS attacks here. const validateName = (name, snippets) => { if (!name?.length) { return "Name is required" From 8b4ce703e97657e97e01a9d13c579e6da6cc49a1 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 13 Mar 2024 17:01:09 +0000 Subject: [PATCH 47/59] Try to fix tests --- packages/backend-core/src/context/mainContext.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/context/mainContext.ts b/packages/backend-core/src/context/mainContext.ts index 9d4cc9096d..6cea7efeba 100644 --- a/packages/backend-core/src/context/mainContext.ts +++ b/packages/backend-core/src/context/mainContext.ts @@ -292,7 +292,7 @@ export async function ensureSnippetContext() { // Otherwise get snippets for this app and update context let snippets: Snippet[] | undefined const db = getAppDB() - if (db) { + if (db && !env.isTest()) { const app = await db.get(DocumentType.APP_METADATA) snippets = app.snippets } From f8690a6bd95f45a76ffac597e50bb1cacd3f9a15 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 14 Mar 2024 12:08:03 +0000 Subject: [PATCH 48/59] Update comment --- packages/builder/src/components/common/bindings/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/common/bindings/utils.js b/packages/builder/src/components/common/bindings/utils.js index 4ff99aa6bb..c60374f0f7 100644 --- a/packages/builder/src/components/common/bindings/utils.js +++ b/packages/builder/src/components/common/bindings/utils.js @@ -39,7 +39,7 @@ export class BindingHelpers { } } - // Adds a JS/HBS helper to the expression + // Adds a snippet to the expression onSelectSnippet(snippet) { const pos = this.getCaretPosition() const { start, end } = pos From 0e94caafcbfd5103cd551d43fdb4d7a4c5f6621c Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 14 Mar 2024 14:10:37 +0000 Subject: [PATCH 49/59] Update snippet insertion to not insert parenthesis --- packages/builder/src/components/common/CodeEditor/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/common/CodeEditor/index.js b/packages/builder/src/components/common/CodeEditor/index.js index b93c95b944..f66c84adce 100644 --- a/packages/builder/src/components/common/CodeEditor/index.js +++ b/packages/builder/src/components/common/CodeEditor/index.js @@ -271,13 +271,12 @@ export const insertBinding = (view, from, to, text, mode) => { } export const insertSnippet = (view, from, to, text) => { - const parsedInsert = `${text}()` - let cursorPos = from + parsedInsert.length - 1 + let cursorPos = from + text.length view.dispatch({ changes: { from, to, - insert: parsedInsert, + insert: text, }, selection: { anchor: cursorPos, From 049c2b989ba8422765ee706ce8bafceecef5fb7e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 14 Mar 2024 14:29:13 +0000 Subject: [PATCH 50/59] Soft paywall snippets --- .../common/bindings/SnippetSidePanel.svelte | 150 ++++++++++++------ 1 file changed, 100 insertions(+), 50 deletions(-) diff --git a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte index c82eed7a23..4495e0d3e4 100644 --- a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte @@ -1,8 +1,19 @@ + + + + diff --git a/packages/client/src/stores/derived/snippets.js b/packages/client/src/stores/derived/snippets.js index 806ff85c4a..74b2797643 100644 --- a/packages/client/src/stores/derived/snippets.js +++ b/packages/client/src/stores/derived/snippets.js @@ -1,8 +1,8 @@ -import { derived } from "svelte/store" import { appStore } from "../app.js" import { builderStore } from "../builder.js" +import { derivedMemo } from "@budibase/frontend-core" -export const snippets = derived( +export const snippets = derivedMemo( [appStore, builderStore], ([$appStore, $builderStore]) => { return $builderStore?.snippets || $appStore?.application?.snippets || [] diff --git a/packages/client/src/utils/enrichDataBinding.js b/packages/client/src/utils/enrichDataBinding.js index 0068a3241c..3756d8789a 100644 --- a/packages/client/src/utils/enrichDataBinding.js +++ b/packages/client/src/utils/enrichDataBinding.js @@ -1,14 +1,10 @@ import { Helpers } from "@budibase/bbui" import { processObjectSync } from "@budibase/string-templates" -import { snippets } from "../stores" -import { get } from "svelte/store" /** * Recursively enriches all props in a props object and returns the new props. * Props are deeply cloned so that no mutation is done to the source object. */ export const enrichDataBindings = (props, context) => { - const totalContext = { ...context, snippets: get(snippets) } - const opts = { cache: true } - return processObjectSync(Helpers.cloneDeep(props), totalContext, opts) + return processObjectSync(Helpers.cloneDeep(props), context, { cache: true }) } From 23a91bcd2317f792fc80b1ea96290af4dd65c134 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 14 Mar 2024 16:16:37 +0000 Subject: [PATCH 56/59] Update snippet empty state --- packages/bbui/src/Drawer/DrawerContent.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/bbui/src/Drawer/DrawerContent.svelte b/packages/bbui/src/Drawer/DrawerContent.svelte index 7974e1e1bf..490dfecc31 100644 --- a/packages/bbui/src/Drawer/DrawerContent.svelte +++ b/packages/bbui/src/Drawer/DrawerContent.svelte @@ -19,7 +19,6 @@ .drawer-contents { overflow-y: auto; flex: 1 1 auto; - height: 0; } .container { height: 100%; From a0e3a8f56c1128063c00847d89e5df75a8e8d1de Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 14 Mar 2024 16:16:58 +0000 Subject: [PATCH 57/59] Update drawer styles to fix issue with filter modal --- .../common/bindings/SnippetSidePanel.svelte | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte index 1c6e443fb8..c68699fc0f 100644 --- a/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte +++ b/packages/builder/src/components/common/bindings/SnippetSidePanel.svelte @@ -1,5 +1,14 @@