UNPKG

@openfisca/json-model

Version:

Library to handle informations extracted in JSON or YAML format from OpenFisca parameters, variables, etc

790 lines (786 loc) 113 kB
import { AstVisitor } from "./visitors.js"; /// Description stating that the variable is either a boolean or a vector of booleans. var VariableType = /*#__PURE__*/function (VariableType) { VariableType["Boolean"] = "Boolean"; VariableType["Decomposition"] = "Decomposition"; VariableType["OpenFiscaParameter"] = "OpenFiscaParameter"; VariableType["OpenFiscaVariable"] = "OpenFiscaVariable"; VariableType["OpenFiscaVariableList"] = "OpenFiscaVariableList"; VariableType["String"] = "String"; VariableType["StringListOrTuple"] = "StringListOrTuple"; return VariableType; }(VariableType || {}); class FormulaExtractor extends AstVisitor { /// PythonAstList of names of OpenFisca variables that are added to generate the result of the formula. decomposition = undefined; /// Names of parameters used by formula openFiscaParametersName = new Set(); /// Names of variables used by formula openFiscaVariablesName = new Set(); /// Descriptions of local variables created by formula variableDescriptionById = {}; // `null` means unknown. constructor(entityByKey, leafParametersName) { super(); this.entityByKey = entityByKey; this.leafParametersName = leafParametersName; const singlePersonRolesAndSubroles = new Set(); for (const entity of Object.values(entityByKey)) { if (entity.is_person) { continue; } for (const role of entity.roles) { if (role.max === 1) { singlePersonRolesAndSubroles.add(role.key); } for (const subrole of role.subroles ?? []) { if (subrole.max === 1) { singlePersonRolesAndSubroles.add(subrole.key); } } } } this.singlePersonRolesAndSubroles = singlePersonRolesAndSubroles; } extractDecomposition(node) { switchNodeAstClass: switch (node.ast_class) { case "BinOp": { switch (node.op.ast_class) { case "Add": { const leftDecomposition = this.extractDecomposition(node.left); if (leftDecomposition !== null) { const rightDecomposition = this.extractDecomposition(node.right); if (rightDecomposition !== null) { return [...leftDecomposition, ...rightDecomposition]; } } break switchNodeAstClass; } case "Sub": { const leftDecomposition = this.extractDecomposition(node.left); if (leftDecomposition !== null) { const rightDecomposition = this.extractDecomposition(node.right); if (rightDecomposition !== null) { return [...leftDecomposition, ...rightDecomposition.map(decompositionReference => { decompositionReference = { ...decompositionReference }; if (decompositionReference.negate) { delete decompositionReference.negate; } else { decompositionReference.negate = true; } return decompositionReference; })]; } } break switchNodeAstClass; } case "Mult": { const leftDecomposition = this.extractDecomposition(node.left); if (leftDecomposition !== null && this.isBoolean(node.right)) { return leftDecomposition; } const rightDecomposition = this.extractDecomposition(node.right); if (rightDecomposition !== null && this.isBoolean(node.left)) { return rightDecomposition; } break switchNodeAstClass; } default: break switchNodeAstClass; } } case "Call": { const func = node.func; switch (func.ast_class) { case "Name": { // US if (func.id === "add") { // Example: add(tax_unit, period, "e00900p", "e02100p", "k1bx14p") console.assert(func.ctx.ast_class === "Load"); const variablesName = node.args.slice(2).map(arg => this.extractString(arg)); if (variablesName.every(variableName => variableName !== null)) { return variablesName.map(name => ({ name })); } // Don't break, to test for UK form of "add" below. // break switchNodeAstClass } // UK if (["add", "aggr"].includes(func.id)) { // Example: add(person, period, ["savings_allowance", "savings_starter_rate_income"]) console.assert(func.ctx.ast_class === "Load"); const variablesName = this.extractStringList(node.args[2]); if (variablesName !== null) { return variablesName.map(name => ({ name })); } console.log("TODO: Unrecognized variable call in formula:", node); console.log("node.args", node.args); break switchNodeAstClass; } else if (["around", "round", "round_"].includes(func.id)) { // Example: around(ir_plaf_qf - decote_gain_fiscal) console.assert(func.ctx.ast_class === "Load"); const decomposition = this.extractDecomposition(node.args[0]); if (decomposition !== null) { return decomposition; } break switchNodeAstClass; } break switchNodeAstClass; } default: break switchNodeAstClass; } } case "Name": { if (node.ctx.ast_class === "Load") { const variableDescription = this.variableDescriptionById[node.id]; if (variableDescription?.type === VariableType.Decomposition) { return variableDescription.decomposition; } } break switchNodeAstClass; } case "UnaryOp": { switch (node.op.ast_class) { case "USub": { const decomposition = this.extractDecomposition(node.operand); if (decomposition !== null) { return decomposition.map(decompositionReference => { decompositionReference = { ...decompositionReference }; if (decompositionReference.negate) { delete decompositionReference.negate; } else { decompositionReference.negate = true; } return decompositionReference; }); } break switchNodeAstClass; } default: break switchNodeAstClass; } } } const openFiscaVariablesName = this.extractOpenFiscaVariablesName(node); if (openFiscaVariablesName !== null && openFiscaVariablesName.length === 1) { return openFiscaVariablesName.map(name => ({ name })); } return null; } extractOpenFiscaParameterName(node) { switch (node.ast_class) { case "Attribute": return this.extractOpenFiscaParameterNameFromAttribute(node); case "Call": return this.extractOpenFiscaParameterNameFromCall(node); case "Name": { if (node.ctx.ast_class === "Load") { const variableDescription = this.variableDescriptionById[node.id]; if (variableDescription?.type === VariableType.OpenFiscaParameter) { return variableDescription.name; } } return null; } case "Subscript": return this.extractOpenFiscaParameterNameFromSubscript(node); default: return null; } } extractOpenFiscaParameterNameFromAttribute(node) { const parameterName = this.extractOpenFiscaParameterName(node.value); if (parameterName === null) { return null; } if (this.leafParametersName.has(parameterName)) { // PythonAstAttribute `node.attr` is a property or method of parameter, not a child. return parameterName; } if (node.attr === "_children") { // Parameter is not a leaf, but its children nodes are accessed using an // alternate syntax. return parameterName; } return [...parameterName.split(".").filter(Boolean), node.attr].join("."); } extractOpenFiscaParameterNameFromCall(node) { const func = node.func; if (func.ast_class === "Name" && func.id === "parameters") { return ""; } return null; } extractOpenFiscaParameterNameFromSubscript(node) { const parameterName = this.extractOpenFiscaParameterName(node.value); if (parameterName === null) { return null; } if (!this.leafParametersName.has(parameterName) && node.ctx.ast_class === "Load" && node.slice.ast_class === "Constant" && typeof node.slice.value === "string") { return [...parameterName.split(".").filter(Boolean), node.slice.value].join("."); } // PythonAstSubscript is not a child. return parameterName; } extractOpenFiscaVariablesName(node) { switch (node.ast_class) { case "Call": { return this.extractOpenFiscaVariablesNameFromCall(node); } case "Name": { if (node.ctx.ast_class === "Load") { const variableDescription = this.variableDescriptionById[node.id]; if (variableDescription?.type === VariableType.OpenFiscaVariable) { return [variableDescription.name]; } if (variableDescription?.type === VariableType.OpenFiscaVariableList) { return variableDescription.names; } } return null; } default: return null; } } extractOpenFiscaVariablesNameFromCall(node) { const func = node.func; switch (func.ast_class) { case "Attribute": { const value = func.value; const attr = func.attr; switch (value.ast_class) { case "Attribute": { const value2 = value.value; const attr2 = value.attr; switch (value2.ast_class) { case "Name": { const value2Entity = this.entityByKey[value2.id]; const attrEntity = this.entityByKey[attr]; if (value2Entity !== undefined && !value2Entity.is_person && (attr2 === "members" || this.singlePersonRolesAndSubroles.has(attr2)) && attrEntity !== undefined && !attrEntity.is_person) { // Example: // * menage.members.famille('prestations_sociales', period) // * famille.demandeur.menage('logement_conventionne', period) console.assert(value2.ctx.ast_class === "Load"); console.assert(value.ctx.ast_class === "Load"); const variableName = this.extractString(node.args[0]); if (variableName !== null) { return [variableName]; } console.log("TODO: Unrecognized variable call in formula:", node); return null; } return null; } default: return null; } return null; } case "Name": { const valueEntity = this.entityByKey[value.id]; const attrEntity = this.entityByKey[attr]; if (valueEntity !== undefined && valueEntity.is_person && attrEntity !== undefined && !attrEntity.is_person) { // Example: individu.foyer_fiscal('csg_revenus_capital', period) console.assert(value.ctx.ast_class === "Load"); const variableName = this.extractString(node.args[0]); if (variableName !== null) { return [variableName]; } console.log("TODO: Unrecognized variable call in formula:", node); return null; } if (valueEntity !== undefined && !valueEntity.is_person) { if (attr === "members" || this.singlePersonRolesAndSubroles.has(attr)) { // Example: // * menage.members('pensions_nettes', period) // * famille.demandeur('age', period) console.assert(value.ctx.ast_class === "Load"); const variableName = this.extractString(node.args[0]); if (variableName !== null) { return [variableName]; } console.log("TODO: Unrecognized variable call in formula:", node); return null; } if (attr === "sum") { console.assert(value.ctx.ast_class === "Load"); const openFiscaVariablesName = this.extractOpenFiscaVariablesName(node.args[0]); if (openFiscaVariablesName !== null) { // Example: menage.sum(menage.members('pensions_nettes', period)) console.assert(openFiscaVariablesName.length === 1); return openFiscaVariablesName; } const decomposition = this.extractDecomposition(node.args[0]); if (decomposition !== null) { // Example: // prestations_sociales_i = menage.members.famille( // "prestations_sociales", period // ) * menage.members.has_role(Famille.DEMANDEUR) // prestations_sociales = menage.sum(prestations_sociales_i) // because prestations_sociales_i is a decomposition. console.assert(decomposition.length === 1); return [decomposition[0].name]; } return null; } } return null; } default: return null; } } case "Name": { // US if (func.id === "add") { // Example: add(tax_unit, period, "e00900p", "e02100p", "k1bx14p") console.assert(func.ctx.ast_class === "Load"); const variablesName = node.args.slice(2).map(arg => this.extractString(arg)); if (variablesName.every(variableName => variableName !== null)) { return variablesName; } // Don't break, to test for UK form of "add" below. // return null } // UK if (["add", "aggr"].includes(func.id)) { // Example: add(person, period, ["savings_allowance", "savings_starter_rate_income"]) console.assert(func.ctx.ast_class === "Load"); const variablesName = this.extractStringList(node.args[2]); if (variablesName !== null) { return variablesName; } console.log("TODO: Unrecognized variable call in formula:", node); console.log("node.args", node.args); return null; } if (this.entityByKey[func.id] !== undefined) { console.assert(func.ctx.ast_class === "Load"); const variableName = this.extractString(node.args[0]); if (variableName !== null) { return [variableName]; } console.log("TODO: Unrecognized variable call in formula:", node); return null; } return null; } default: return null; } } extractString(node) { switch (node.ast_class) { case "Constant": return typeof node.value === "string" ? node.value : null; case "Name": { if (node.ctx.ast_class === "Load") { const variableDescription = this.variableDescriptionById[node.id]; if (variableDescription?.type === VariableType.String) { return variableDescription.value; } } return null; } default: return null; } } extractStringList(node) { switch (node.ast_class) { case "List": case "Tuple": return node.ctx.ast_class === "Load" && node.elts.every(element => element.ast_class === "Constant" && typeof element.value === "string") ? node.elts.map(element => element.value) : null; case "Name": { if (node.ctx.ast_class === "Load") { const variableDescription = this.variableDescriptionById[node.id]; if (variableDescription?.type === VariableType.StringListOrTuple) { return variableDescription.items; } } return null; } default: return null; } } /// Detect if node is a boolean or a boolean vector. isBoolean(node) { switch (node.ast_class) { case "Call": { const func = node.func; switch (func.ast_class) { case "Attribute": { const value = func.value; const attr = func.attr; switch (value.ast_class) { case "Attribute": { const value2 = value.value; const attr2 = value.attr; switch (value2.ast_class) { case "Name": { const value2Entity = this.entityByKey[value2.id]; if (value2Entity !== undefined && !value2Entity.is_person && attr2 === "members" && attr === "has_role") { // Example: menage.members.has_role(Famille.ENFANT) console.assert(value2.ctx.ast_class === "Load"); return true; } return false; } default: return false; } return false; } case "Name": { const valueEntity = this.entityByKey[value.id]; if (valueEntity !== undefined && valueEntity.is_person && func.attr === "has_role") { // Example: individu.has_role(FoyerFiscal.DECLARANT_PRINCIPAL) console.assert(value.ctx.ast_class === "Load"); return true; } return false; } default: return false; } } case "Name": { const entity = this.entityByKey[func.id]; if (entity !== undefined && entity.is_person && node.args.length === 2 && node.args[0].ast_class === "Constant" && typeof node.args[0].value === "string" && // UK ["is_benunit_head", "is_household_head"].includes(node.args[0].value)) { // Example: person("is_benunit_head", period) return true; } return false; } default: return false; } } case "Name": { if (node.ctx.ast_class === "Load") { return this.variableDescriptionById[node.id]?.type === VariableType.Boolean; } return false; } default: return false; } } visit_Assign(node) { const openFiscaParametersName = new Set(this.openFiscaParametersName); this.visitGeneric(node); const value = node.value; let variableDescription = null; // `null` means unknown variable description if (variableDescription === null) { const openFiscaParameterName = this.extractOpenFiscaParameterName(value); if (openFiscaParameterName !== null) { // Ignore OpenFisca parameters added to this.openFiscaParametersName by above call to // `this.visitGeneric(node)`, because they are parent of `openFiscaParameterName` and not really // used. this.openFiscaParametersName = openFiscaParametersName; variableDescription = { name: openFiscaParameterName, type: VariableType.OpenFiscaParameter }; } } if (variableDescription === null) { const openFiscaVariablesName = this.extractOpenFiscaVariablesName(value); if (openFiscaVariablesName !== null) { if (openFiscaVariablesName.length === 1) { variableDescription = { name: openFiscaVariablesName[0], type: VariableType.OpenFiscaVariable }; } else { variableDescription = { names: openFiscaVariablesName, type: VariableType.OpenFiscaVariableList }; } } else { // Note: extractDecomposition must be executed after extractOpenFiscaVariablesName. const decomposition = this.extractDecomposition(value); if (decomposition !== null) { variableDescription = { decomposition, type: VariableType.Decomposition }; } else if (this.isBoolean(value)) { variableDescription = { type: VariableType.Boolean }; } } } if (variableDescription === null) { const stringListOrTuple = this.extractStringList(value); if (stringListOrTuple !== null) { variableDescription = { items: stringListOrTuple, type: VariableType.StringListOrTuple }; } } for (const target of node.targets) { if (target.ast_class === "Name" && target.ctx.ast_class === "Store") { this.variableDescriptionById[target.id] = variableDescription; } } } visit_Attribute(node) { const openFiscaParametersName = new Set(this.openFiscaParametersName); this.visitGeneric(node); const openFiscaParameterName = this.extractOpenFiscaParameterNameFromAttribute(node); if (openFiscaParameterName !== null) { // Ignore OpenFisca parameters added to this.openFiscaParametersName by above call to // `this.visitGeneric(node)`, because they are parent of `openFiscaParameterName` and not really // used. openFiscaParametersName.add(openFiscaParameterName); this.openFiscaParametersName = openFiscaParametersName; } } visit_AugAssign(node) { this.visit({ ast_class: "Assign", targets: [node.target], value: { ast_class: "BinOp", left: { ...node.target, ctx: { ast_class: "Load" } }, op: node.op, right: node.value } }); } visit_Call(node) { // Note: this.visitGeneric(node) is called below. const { func } = node; if (func.ast_class === "Name" && ["apply_bareme", "compute_cotisation", "compute_cotisation_annuelle", "compute_cotisation_anticipee"].includes(func.id)) { const keywordByArg = Object.fromEntries(node.keywords.map(keyword => [keyword.arg, keyword])); const baremeName = keywordByArg["bareme_name"]?.value; const cotisationType = keywordByArg["cotisation_type"]?.value; if (baremeName?.ast_class === "Constant" && cotisationType !== undefined) { const baremePrefixes = cotisationType.ast_class === "Constant" ? cotisationType.value === "employeur" ? ["cotsoc.cotisations_employeur"] : cotisationType.value === "salarie" ? ["cotsoc.cotisations_salarie"] : [] : // Assume that cotisationType is a variable that can have both values "employeur" & "salarie". ["cotsoc.cotisations_employeur", "cotsoc.cotisations_salarie"]; for (const baremePrefix of baremePrefixes) { for (const categorieSalarie of ["prive_non_cadre", "prive_cadre", "public_titulaire_etat", "public_titulaire_militaire", "public_titulaire_territoriale", "public_titulaire_hospitaliere", "public_non_titulaire" // "non_pertinent", ]) { const baremeParameterName = `${baremePrefix}.${categorieSalarie}.${baremeName.value}`; if (this.leafParametersName.has(baremeParameterName)) { this.openFiscaParametersName.add(baremeParameterName); } } } // Visit all other items of node. for (const arg of node.args) { this.visit(arg); } for (const keyword of node.keywords) { if (keyword.arg === undefined || !["bareme_name", "cotisation_type"].includes(keyword.arg)) { this.visit(keyword); } } return; } } else if (func.ast_class === "Name" && func.id === "apply_bareme_for_relevant_type_sal") { const keywordByArg = Object.fromEntries(node.keywords.map(keyword => [keyword.arg, keyword])); const baremeByTypeSalName = keywordByArg["bareme_by_type_sal_name"]?.value; const baremeName = keywordByArg["bareme_name"]?.value; if (baremeByTypeSalName !== undefined && baremeName?.ast_class === "Constant") { const baremePrefix = this.extractOpenFiscaParameterName(baremeByTypeSalName); if (baremePrefix !== null) { for (const categorieSalarie of ["prive_non_cadre", "prive_cadre", "public_titulaire_etat", "public_titulaire_militaire", "public_titulaire_territoriale", "public_titulaire_hospitaliere", "public_non_titulaire" // "non_pertinent", ]) { const baremeParameterName = `${baremePrefix}.${categorieSalarie}.${baremeName.value}`; if (this.leafParametersName.has(baremeParameterName)) { this.openFiscaParametersName.add(baremeParameterName); } } // Visit all other items of node. for (const arg of node.args) { this.visit(arg); } for (const keyword of node.keywords) { if (keyword.arg === undefined || !["bareme_by_type_sal_name", "bareme_name"].includes(keyword.arg)) { this.visit(keyword); } } return; } } } this.visitGeneric(node); const openFiscaParameterName = this.extractOpenFiscaParameterNameFromCall(node); if (openFiscaParameterName !== null) { this.openFiscaParametersName.add(openFiscaParameterName); return; } const openFiscaVariablesName = this.extractOpenFiscaVariablesNameFromCall(node); if (openFiscaVariablesName !== null) { for (const openFiscaVariableName of openFiscaVariablesName) { this.openFiscaVariablesName.add(openFiscaVariableName); } } } visit_Delete(node) { this.visitGeneric(node); for (const target of node.targets) { if (target.ast_class === "Name" && target.ctx.ast_class === "Del") { delete this.variableDescriptionById[target.id]; continue; } } } visit_For(node) { const { body, iter, orelse, target } = node; const stringList = this.extractStringList(iter); if (stringList !== null && target.ast_class === "Name" && target.ctx.ast_class === "Store") { const variableDescriptionById = this.variableDescriptionById; for (const value of stringList) { this.variableDescriptionById = { ...variableDescriptionById, [target.id]: { value, type: VariableType.String } }; for (const bodyItem of body) { this.visit(bodyItem); } for (const orelseItem of orelse) { this.visit(orelseItem); } } this.variableDescriptionById = variableDescriptionById; return; } this.visitGeneric(node); } visit_GeneratorExp(node) { this.visitSequenceGenerator(node); } visit_ListComp(node) { this.visitSequenceGenerator(node); } visit_Name(node) { this.visitGeneric(node); if (node.ctx.ast_class === "Load") { const variableDescription = this.variableDescriptionById[node.id]; if (variableDescription?.type === VariableType.OpenFiscaParameter) { this.openFiscaParametersName.add(variableDescription.name); } } } visit_Return(node) { this.visitGeneric(node); if (node.value !== undefined) { const decomposition = this.extractDecomposition(node.value); if (decomposition !== null) { this.decomposition = decomposition; } } } visit_SetComp(node) { this.visitSequenceGenerator(node); } visit_Subscript(node) { const openFiscaParametersName = new Set(this.openFiscaParametersName); this.visitGeneric(node); const openFiscaParameterName = this.extractOpenFiscaParameterNameFromSubscript(node); if (openFiscaParameterName !== null) { // Ignore OpenFisca parameters added to this.openFiscaParametersName by above call to // `this.visitGeneric(node)`, because they are parent of `openFiscaParameterName` and not really // used. openFiscaParametersName.add(openFiscaParameterName); this.openFiscaParametersName = openFiscaParametersName; } } visitSequenceGenerator(node) { if (node.generators.length === 1) { const { ifs, iter, target } = node.generators[0]; const stringList = this.extractStringList(iter); if (stringList !== null && target.ast_class === "Name" && target.ctx.ast_class === "Store") { const variableDescriptionById = this.variableDescriptionById; for (const value of stringList) { this.variableDescriptionById = { ...variableDescriptionById, [target.id]: { value, type: VariableType.String } }; for (const ifNode of ifs) { this.visit(ifNode); } this.visit(node.elt); } this.variableDescriptionById = variableDescriptionById; return; } } this.visitGeneric(node); } } export function extractFromFormulaAst(formula, entityByKey, leafParametersName) { const extractor = new FormulaExtractor(entityByKey, leafParametersName); extractor.visit(formula); return extractor; } //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJBc3RWaXNpdG9yIiwiVmFyaWFibGVUeXBlIiwiRm9ybXVsYUV4dHJhY3RvciIsImRlY29tcG9zaXRpb24iLCJ1bmRlZmluZWQiLCJvcGVuRmlzY2FQYXJhbWV0ZXJzTmFtZSIsIlNldCIsIm9wZW5GaXNjYVZhcmlhYmxlc05hbWUiLCJ2YXJpYWJsZURlc2NyaXB0aW9uQnlJZCIsImNvbnN0cnVjdG9yIiwiZW50aXR5QnlLZXkiLCJsZWFmUGFyYW1ldGVyc05hbWUiLCJzaW5nbGVQZXJzb25Sb2xlc0FuZFN1YnJvbGVzIiwiZW50aXR5IiwiT2JqZWN0IiwidmFsdWVzIiwiaXNfcGVyc29uIiwicm9sZSIsInJvbGVzIiwibWF4IiwiYWRkIiwia2V5Iiwic3Vicm9sZSIsInN1YnJvbGVzIiwiZXh0cmFjdERlY29tcG9zaXRpb24iLCJub2RlIiwic3dpdGNoTm9kZUFzdENsYXNzIiwiYXN0X2NsYXNzIiwib3AiLCJsZWZ0RGVjb21wb3NpdGlvbiIsImxlZnQiLCJyaWdodERlY29tcG9zaXRpb24iLCJyaWdodCIsIm1hcCIsImRlY29tcG9zaXRpb25SZWZlcmVuY2UiLCJuZWdhdGUiLCJpc0Jvb2xlYW4iLCJmdW5jIiwiaWQiLCJjb25zb2xlIiwiYXNzZXJ0IiwiY3R4IiwidmFyaWFibGVzTmFtZSIsImFyZ3MiLCJzbGljZSIsImFyZyIsImV4dHJhY3RTdHJpbmciLCJldmVyeSIsInZhcmlhYmxlTmFtZSIsIm5hbWUiLCJpbmNsdWRlcyIsImV4dHJhY3RTdHJpbmdMaXN0IiwibG9nIiwidmFyaWFibGVEZXNjcmlwdGlvbiIsInR5cGUiLCJEZWNvbXBvc2l0aW9uIiwib3BlcmFuZCIsImV4dHJhY3RPcGVuRmlzY2FWYXJpYWJsZXNOYW1lIiwibGVuZ3RoIiwiZXh0cmFjdE9wZW5GaXNjYVBhcmFtZXRlck5hbWUiLCJleHRyYWN0T3BlbkZpc2NhUGFyYW1ldGVyTmFtZUZyb21BdHRyaWJ1dGUiLCJleHRyYWN0T3BlbkZpc2NhUGFyYW1ldGVyTmFtZUZyb21DYWxsIiwiT3BlbkZpc2NhUGFyYW1ldGVyIiwiZXh0cmFjdE9wZW5GaXNjYVBhcmFtZXRlck5hbWVGcm9tU3Vic2NyaXB0IiwicGFyYW1ldGVyTmFtZSIsInZhbHVlIiwiaGFzIiwiYXR0ciIsInNwbGl0IiwiZmlsdGVyIiwiQm9vbGVhbiIsImpvaW4iLCJleHRyYWN0T3BlbkZpc2NhVmFyaWFibGVzTmFtZUZyb21DYWxsIiwiT3BlbkZpc2NhVmFyaWFibGUiLCJPcGVuRmlzY2FWYXJpYWJsZUxpc3QiLCJuYW1lcyIsInZhbHVlMiIsImF0dHIyIiwidmFsdWUyRW50aXR5IiwiYXR0ckVudGl0eSIsInZhbHVlRW50aXR5IiwiU3RyaW5nIiwiZWx0cyIsImVsZW1lbnQiLCJTdHJpbmdMaXN0T3JUdXBsZSIsIml0ZW1zIiwidmlzaXRfQXNzaWduIiwidmlzaXRHZW5lcmljIiwib3BlbkZpc2NhUGFyYW1ldGVyTmFtZSIsInN0cmluZ0xpc3RPclR1cGxlIiwidGFyZ2V0IiwidGFyZ2V0cyIsInZpc2l0X0F0dHJpYnV0ZSIsInZpc2l0X0F1Z0Fzc2lnbiIsInZpc2l0IiwidmlzaXRfQ2FsbCIsImtleXdvcmRCeUFyZyIsImZyb21FbnRyaWVzIiwia2V5d29yZHMiLCJrZXl3b3JkIiwiYmFyZW1lTmFtZSIsImNvdGlzYXRpb25UeXBlIiwiYmFyZW1lUHJlZml4ZXMiLCJiYXJlbWVQcmVmaXgiLCJjYXRlZ29yaWVTYWxhcmllIiwiYmFyZW1lUGFyYW1ldGVyTmFtZSIsImJhcmVtZUJ5VHlwZVNhbE5hbWUiLCJvcGVuRmlzY2FWYXJpYWJsZU5hbWUiLCJ2aXNpdF9EZWxldGUiLCJ2aXNpdF9Gb3IiLCJib2R5IiwiaXRlciIsIm9yZWxzZSIsInN0cmluZ0xpc3QiLCJib2R5SXRlbSIsIm9yZWxzZUl0ZW0iLCJ2aXNpdF9HZW5lcmF0b3JFeHAiLCJ2aXNpdFNlcXVlbmNlR2VuZXJhdG9yIiwidmlzaXRfTGlzdENvbXAiLCJ2aXNpdF9OYW1lIiwidmlzaXRfUmV0dXJuIiwidmlzaXRfU2V0Q29tcCIsInZpc2l0X1N1YnNjcmlwdCIsImdlbmVyYXRvcnMiLCJpZnMiLCJpZk5vZGUiLCJlbHQiLCJleHRyYWN0RnJvbUZvcm11bGFBc3QiLCJmb3JtdWxhIiwiZXh0cmFjdG9yIl0sInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FzdC9leHRyYWN0b3JzLnRzIl0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgRGVjb21wb3NpdGlvblJlZmVyZW5jZSB9IGZyb20gXCIuLi9kZWNvbXBvc2l0aW9uc1wiXG5pbXBvcnQgdHlwZSB7IEVudGl0eUJ5S2V5IH0gZnJvbSBcIi4uL2VudGl0aWVzXCJcblxuaW1wb3J0IHtcbiAgdHlwZSBQeXRob25Bc3RBc3NpZ24sXG4gIHR5cGUgUHl0aG9uQXN0Tm9kZSxcbiAgdHlwZSBQeXRob25Bc3RBdHRyaWJ1dGUsXG4gIHR5cGUgUHl0aG9uQXN0QXVnQXNzaWduLFxuICB0eXBlIFB5dGhvbkFzdEJpbmFyeU9wZXJhdG9yLFxuICB0eXBlIFB5dGhvbkFzdENhbGwsXG4gIHR5cGUgUHl0aG9uQXN0Q29uc3RhbnQsXG4gIHR5cGUgUHl0aG9uQXN0RGVsZXRlLFxuICB0eXBlIFB5dGhvbkFzdEZvcixcbiAgdHlwZSBQeXRob25Bc3RGdW5jdGlvbkRlZixcbiAgdHlwZSBQeXRob25Bc3RHZW5lcmF0b3JFeHAsXG4gIHR5cGUgUHl0aG9uQXN0TGlzdENvbXAsXG4gIHR5cGUgUHl0aG9uQXN0TmFtZSxcbiAgdHlwZSBQeXRob25Bc3RSZXR1cm4sXG4gIHR5cGUgUHl0aG9uQXN0U2V0Q29tcCxcbiAgdHlwZSBQeXRob25Bc3RTdWJzY3JpcHQsXG59IGZyb20gXCIuL25vZGVzXCJcbmltcG9ydCB7IEFzdFZpc2l0b3IgfSBmcm9tIFwiLi92aXNpdG9yc1wiXG5cbnR5cGUgVmFyaWFibGVEZXNjcmlwdGlvbiA9XG4gIHwgVmFyaWFibGVEZXNjcmlwdGlvbkJvb2xlYW5cbiAgfCBWYXJpYWJsZURlc2NyaXB0aW9uRGVjb21wb3NpdGlvblxuICB8IFZhcmlhYmxlRGVzY3JpcHRpb25PcGVuRmlzY2FQYXJhbWV0ZXJcbiAgfCBWYXJpYWJsZURlc2NyaXB0aW9uT3BlbkZpc2NhVmFyaWFibGVcbiAgfCBWYXJpYWJsZURlc2NyaXB0aW9uT3BlbkZpc2NhVmFyaWFibGVMaXN0XG4gIHwgVmFyaWFibGVEZXNjcmlwdGlvblN0cmluZ1xuICB8IFZhcmlhYmxlRGVzY3JpcHRpb25TdHJpbmdMaXN0T3JUdXBsZVxuXG5pbnRlcmZhY2UgVmFyaWFibGVEZXNjcmlwdGlvbkJhc2Uge1xuICB0eXBlOiBWYXJpYWJsZVR5cGVcbn1cblxuLy8vIERlc2NyaXB0aW9uIHN0YXRpbmcgdGhhdCB0aGUgdmFyaWFibGUgaXMgZWl0aGVyIGEgYm9vbGVhbiBvciBhIHZlY3RvciBvZiBib29sZWFucy5cbmludGVyZmFjZSBWYXJpYWJsZURlc2NyaXB0aW9uQm9vbGVhbiBleHRlbmRzIFZhcmlhYmxlRGVzY3JpcHRpb25CYXNlIHtcbiAgdHlwZTogVmFyaWFibGVUeXBlLkJvb2xlYW5cbn1cblxuaW50ZXJmYWNlIFZhcmlhYmxlRGVzY3JpcHRpb25EZWNvbXBvc2l0aW9uIGV4dGVuZHMgVmFyaWFibGVEZXNjcmlwdGlvbkJhc2Uge1xuICAvLy8gQSBsaXN0IG9mIG5hbWVzIG9mIE9wZW5GaXNjYSB2YXJpYWJsZXMgdGhhdCBhcmUgYWRkZWQgdG8gZ2VuZXJhdGUgdGhlIGRlY29tcG9zaXRpb24uXG4gIC8vLyBOb3RlOiBUaGVyZSBpcyBubyBzdWNoIHRoaW5nIGFzIGEgZGVjb21wb3NpdGlvbiB3aXRoIG9ubHkgb25lIHZhcmlhYmxlIG5hbWUsIGJlY2F1c2VcbiAgLy8vIGl0IGlzIGFuIE9wZW5GaXNjYSB2YXJpYWJsZSwgbm90IGEgZGVjb21wb3NpdGlvbi5cbiAgZGVjb21wb3NpdGlvbjogRGVjb21wb3NpdGlvblJlZmVyZW5jZVtdXG4gIHR5cGU6IFZhcmlhYmxlVHlwZS5EZWNvbXBvc2l0aW9uXG59XG5cbmludGVyZmFjZSBWYXJpYWJsZURlc2NyaXB0aW9uT3BlbkZpc2NhUGFyYW1ldGVyXG4gIGV4dGVuZHMgVmFyaWFibGVEZXNjcmlwdGlvbkJhc2Uge1xuICAvLy8gUHl0aG9uQXN0TmFtZSBvZiBPcGVuRmlzY2EgcGFyYW1ldGVyXG4gIG5hbWU6IHN0cmluZ1xuICB0eXBlOiBWYXJpYWJsZVR5cGUuT3BlbkZpc2NhUGFyYW1ldGVyXG59XG5cbmludGVyZmFjZSBWYXJpYWJsZURlc2NyaXB0aW9uT3BlbkZpc2NhVmFyaWFibGUgZXh0ZW5kcyBWYXJpYWJsZURlc2NyaXB0aW9uQmFzZSB7XG4gIC8vLyBQeXRob25Bc3ROYW1lIG9mIE9wZW5GaXNjYSB2YXJpYWJsZVxuICBuYW1lOiBzdHJpbmdcbiAgdHlwZTogVmFyaWFibGVUeXBlLk9wZW5GaXNjYVZhcmlhYmxlXG59XG5cbmludGVyZmFjZSBWYXJpYWJsZURlc2NyaXB0aW9uT3BlbkZpc2NhVmFyaWFibGVMaXN0XG4gIGV4dGVuZHMgVmFyaWFibGVEZXNjcmlwdGlvbkJhc2Uge1xuICAvLy8gTmFtZXMgb2YgT3BlbkZpc2NhIHZhcmlhYmxlc1xuICBuYW1lczogc3RyaW5nW11cbiAgdHlwZTogVmFyaWFibGVUeXBlLk9wZW5GaXNjYVZhcmlhYmxlTGlzdFxufVxuXG5pbnRlcmZhY2UgVmFyaWFibGVEZXNjcmlwdGlvblN0cmluZyBleHRlbmRzIFZhcmlhYmxlRGVzY3JpcHRpb25CYXNlIHtcbiAgdmFsdWU6IHN0cmluZ1xuICB0eXBlOiBWYXJpYWJsZVR5cGUuU3RyaW5nXG59XG5cbmludGVyZmFjZSBWYXJpYWJsZURlc2NyaXB0aW9uU3RyaW5nTGlzdE9yVHVwbGUgZXh0ZW5kcyBWYXJpYWJsZURlc2NyaXB0aW9uQmFzZSB7XG4gIGl0ZW1zOiBzdHJpbmdbXVxuICB0eXBlOiBWYXJpYWJsZVR5cGUuU3RyaW5nTGlzdE9yVHVwbGVcbn1cblxuZW51bSBWYXJpYWJsZVR5cGUge1xuICBCb29sZWFuID0gXCJCb29sZWFuXCIsXG4gIERlY29tcG9zaXRpb24gPSBcIkRlY29tcG9zaXRpb25cIixcbiAgT3BlbkZpc2NhUGFyYW1ldGVyID0gXCJPcGVuRmlzY2FQYXJhbWV0ZXJcIixcbiAgT3BlbkZpc2NhVmFyaWFibGUgPSBcIk9wZW5GaXNjYVZhcmlhYmxlXCIsXG4gIE9wZW5GaXNjYVZhcmlhYmxlTGlzdCA9IFwiT3BlbkZpc2NhVmFyaWFibGVMaXN0XCIsXG4gIFN0cmluZyA9IFwiU3RyaW5nXCIsXG4gIFN0cmluZ0xpc3RPclR1cGxlID0gXCJTdHJpbmdMaXN0T3JUdXBsZVwiLFxufVxuXG5jbGFzcyBGb3JtdWxhRXh0cmFjdG9yIGV4dGVuZHMgQXN0VmlzaXRvcjxQeXRob25Bc3ROb2RlPiB7XG4gIC8vLyBQeXRob25Bc3RMaXN0IG9mIG5hbWVzIG9mIE9wZW5GaXNjYSB2YXJpYWJsZXMgdGhhdCBhcmUgYWRkZWQgdG8gZ2VuZXJhdGUgdGhlIHJlc3VsdCBvZiB0aGUgZm9ybXVsYS5cbiAgZGVjb21wb3NpdGlvbjogRGVjb21wb3NpdGlvblJlZmVyZW5jZVtdIHwgdW5kZWZpbmVkID0gdW5kZWZpbmVkXG4gIC8vLyBOYW1lcyBvZiBwYXJhbWV0ZXJzIHVzZWQgYnkgZm9ybXVsYVxuICBvcGVuRmlzY2FQYXJhbWV0ZXJzTmFtZTogU2V0PHN0cmluZz4gPSBuZXcgU2V0KClcbiAgLy8vIE5hbWVzIG9mIHZhcmlhYmxlcyB1c2VkIGJ5IGZvcm11bGFcbiAgb3BlbkZpc2NhVmFyaWFibGVzTmFtZTogU2V0PHN0cmluZz4gPSBuZXcgU2V0KClcbiAgc2luZ2xlUGVyc29uUm9sZXNBbmRTdWJyb2xlczogU2V0PHN0cmluZz5cbiAgLy8vIERlc2NyaXB0aW9ucyBvZiBsb2NhbCB2YXJpYWJsZXMgY3JlYXRlZCBieSBmb3JtdWxhXG4gIHZhcmlhYmxlRGVzY3JpcHRpb25CeUlkOiB7IFtpZDogc3RyaW5nXTogVmFyaWFibGVEZXNjcmlwdGlvbiB8IG51bGwgfSA9IHt9IC8vIGBudWxsYCBtZWFucyB1bmtub3duLlxuXG4gIGNvbnN0cnVjdG9yKFxuICAgIHB1YmxpYyByZWFkb25seSBlbnRpdHlCeUtleTogRW50aXR5QnlLZXksXG4gICAgcHVibGljIHJlYWRvbmx5IGxlYWZQYXJhbWV0ZXJzTmFtZTogU2V0PHN0cmluZz4sXG4gICkge1xuICAgIHN1cGVyKClcblxuICAgIGNvbnN0IHNpbmdsZVBlcnNvblJvbGVzQW5kU3Vicm9sZXMgPSBuZXcgU2V0PHN0cmluZz4oKVxuICAgIGZvciAoY29uc3QgZW50aXR5IG9mIE9iamVjdC52YWx1ZXMoZW50aXR5QnlLZXkpKSB7XG4gICAgICBpZiAoZW50aXR5LmlzX3BlcnNvbikge1xuICAgICAgICBjb250aW51ZVxuICAgICAgfVxuICAgICAgZm9yIChjb25zdCByb2xlIG9mIGVudGl0eS5yb2xlcykge1xuICAgICAgICBpZiAocm9sZS5tYXggPT09IDEpIHtcbiAgICAgICAgICBzaW5nbGVQZXJzb25Sb2xlc0FuZFN1YnJvbGVzLmFkZChyb2xlLmtleSlcbiAgICAgICAgfVxuICAgICAgICBmb3IgKGNvbnN0IHN1YnJvbGUgb2Ygcm9sZS5zdWJyb2xlcyA/PyBbXSkge1xuICAgICAgICAgIGlmIChzdWJyb2xlLm1heCA9PT0gMSkge1xuICAgICAgICAgICAgc2luZ2xlUGVyc29uUm9sZXNBbmRTdWJyb2xlcy5hZGQoc3Vicm9sZS5rZXkpXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHRoaXMuc2luZ2xlUGVyc29uUm9sZXNBbmRTdWJyb2xlcyA9IHNpbmdsZVBlcnNvblJvbGVzQW5kU3Vicm9sZXNcbiAgfVxuXG4gIGV4dHJhY3REZWNvbXBvc2l0aW9uKG5vZGU6IFB5dGhvbkFzdE5vZGUpOiBEZWNvbXBvc2l0aW9uUmVmZXJlbmNlW10gfCBudWxsIHtcbiAgICBzd2l0Y2hOb2RlQXN0Q2xhc3M6IHN3aXRjaCAobm9kZS5hc3RfY2xhc3MpIHtcbiAgICAgIGNhc2UgXCJCaW5PcFwiOiB7XG4gICAgICAgIHN3aXRjaCAobm9kZS5vcC5hc3RfY2xhc3MpIHtcbiAgICAgICAgICBjYXNlIFwiQWRkXCI6IHtcbiAgICAgICAgICAgIGNvbnN0IGxlZnREZWNvbXBvc2l0aW9uID0gdGhpcy5leHRyYWN0RGVjb21wb3NpdGlvbihub2RlLmxlZnQpXG4gICAgICAgICAgICBpZiAobGVmdERlY29tcG9zaXRpb24gIT09IG51bGwpIHtcbiAgICAgICAgICAgICAgY29uc3QgcmlnaHREZWNvbXBvc2l0aW9uID0gdGhpcy5leHRyYWN0RGVjb21wb3NpdGlvbihub2RlLnJpZ2h0KVxuICAgICAgICAgICAgICBpZiAocmlnaHREZWNvbXBvc2l0aW9uICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIFsuLi5sZWZ0RGVjb21wb3NpdGlvbiwgLi4ucmlnaHREZWNvbXBvc2l0aW9uXVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBicmVhayBzd2l0Y2hOb2RlQXN0Q2xhc3NcbiAgICAgICAgICB9XG4gICAgICAgICAgY2FzZSBcIlN1YlwiOiB7XG4gICAgICAgICAgICBjb25zdCBsZWZ0RGVjb21wb3NpdGlvbiA9IHRoaXMuZXh0cmFjdERlY29tcG9zaXRpb24obm9kZS5sZWZ0KVxuICAgICAgICAgICAgaWYgKGxlZnREZWNvbXBvc2l0aW9uICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgIGNvbnN0IHJpZ2h0RGVjb21wb3NpdGlvbiA9IHRoaXMuZXh0cmFjdERlY29tcG9zaXRpb24obm9kZS5yaWdodClcbiAgICAgICAgICAgICAgaWYgKHJpZ2h0RGVjb21wb3NpdGlvbiAhPT0gbnVsbCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBbXG4gICAgICAgICAgICAgICAgICAuLi5sZWZ0RGVjb21wb3NpdGlvbixcbiAgICAgICAgICAgICAgICAgIC4uLnJpZ2h0RGVjb21wb3NpdGlvbi5tYXAoKGRlY29tcG9zaXRpb25SZWZlcmVuY2UpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgZGVjb21wb3NpdGlvblJlZmVyZW5jZSA9IHsgLi4uZGVjb21wb3NpdGlvblJlZmVyZW5jZSB9XG4gICAgICAgICAgICAgICAgICAgIGlmIChkZWNvbXBvc2l0aW9uUmVmZXJlbmNlLm5lZ2F0ZSkge1xuICAgICAgICAgICAgICAgICAgICAgIGRlbGV0ZSBkZWNvbXBvc2l0aW9uUmVmZXJlbmNlLm5lZ2F0ZVxuICAgICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgIGRlY29tcG9zaXRpb25SZWZlcmVuY2UubmVnYXRlID0gdHJ1ZVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBkZWNvbXBvc2l0aW9uUmVmZXJlbmNlXG4gICAgICAgICAgICAgICAgICB9KSxcbiAgICAgICAgICAgICAgICBdXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGJyZWFrIHN3aXRjaE5vZGVBc3RDbGFzc1xuICAgICAgICAgIH1cbiAgICAgICAgICBjYXNlIFwiTXVsdFwiOiB7XG4gICAgICAgICAgICBjb25zdCBsZWZ0RGVjb21wb3NpdGlvbiA9IHRoaXMuZXh0cmFjdERlY29tcG9zaXRpb24obm9kZS5sZWZ0KVxuICAgICAgICAgICAgaWYgKGxlZnREZWNvbXBvc2l0aW9uICE9PSBudWxsICYmIHRoaXMuaXNCb29sZWFuKG5vZGUucmlnaHQpKSB7XG4gICAgICAgICAgICAgIHJldHVybiBsZWZ0RGVjb21wb3NpdGlvblxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgY29uc3QgcmlnaHREZWNvbXBvc2l0aW9uID0gdGhpcy5leHRyYWN0RGVjb21wb3NpdGlvbihub2RlLnJpZ2h0KVxuICAgICAgICAgICAgaWYgKHJpZ2h0RGVjb21wb3NpdGlvbiAhPT0gbnVsbCAmJiB0aGlzLmlzQm9vbGVhbihub2RlLmxlZnQpKSB7XG4gICAgICAgICAgICAgIHJldHVybiByaWdodERlY29tcG9zaXRpb25cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGJyZWFrIHN3aXRjaE5vZGVBc3RDbGFzc1xuICAgICAgICAgIH1cbiAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgYnJlYWsgc3dpdGNoTm9kZUFzdENsYXNzXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGNhc2UgXCJDYWxsXCI6IHtcbiAgICAgICAgY29uc3QgZnVuYyA9IG5vZGUuZnVuY1xuICAgICAgICBzd2l0Y2ggKGZ1bmMuYXN0X2NsYXNzKSB7XG4gICAgICAgICAgY2FzZSBcIk5hbWVcIjoge1xuICAgICAgICAgICAgLy8gVVNcbiAgICAgICAgICAgIGlmIChmdW5jLmlkID09PSBcImFkZFwiKSB7XG4gICAgICAgICAgICAgIC8vIEV4YW1wbGU6IGFkZCh0YXhfdW5pdCwgcGVyaW9kLCBcImUwMDkwMHBcIiwgXCJlMDIxMDBwXCIsIFwiazFieDE0cFwiKVxuICAgICAgICAgICAgICBjb25zb2xlLmFzc2VydChmdW5jLmN0eC5hc3RfY2xhc3MgPT09IFwiTG9hZFwiKVxuICAgICAgICAgICAgICBjb25zdCB2YXJpYWJsZXNOYW1lID0gbm9kZS5hcmdzXG4gICAgICAgICAgICAgICAgLnNsaWNlKDIpXG4gICAgICAgICAgICAgICAgLm1hcCgoYXJnKSA9PiB0aGlzLmV4dHJhY3RTdHJpbmcoYXJnIGFzIFB5dGhvbkFzdE5vZGUpKVxuICAgICAgICAgICAgICBpZiAoXG4gICAgICAgICAgICAgICAgdmFyaWFibGVzTmFtZS5ldmVyeSgodmFyaWFibGVOYW1lKSA9PiB2YXJpYWJsZU5hbWUgIT09IG51bGwpXG4gICAgICAgICAgICAgICkge1xuICAgICAgICAgICAgICAgIHJldHVybiAodmFyaWFibGVzTmFtZSBhcyBzdHJpbmdbXSkubWFwKChuYW1lKSA9PiAoeyBuYW1lIH0pKVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIC8vIERvbid0IGJyZWFrLCB0byB0ZXN0IGZvciBVSyBmb3JtIG9mIFwiYWRkXCIgYmVsb3cuXG4gICAgICAgICAgICAgIC8vIGJyZWFrIHN3aXRjaE5vZGVBc3RDbGFzc1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gVUtcbiAgICAgICAgICAgIGlmIChbXCJhZGRcIiwgXCJhZ2dyXCJdLmluY2x1ZGVzKGZ1bmMuaWQpKSB7XG4gICAgICAgICAgICAgIC8vIEV4YW1wbGU6IGFkZChwZXJzb24sIHBlcmlvZCwgW1wic2F2aW5nc19hbGxvd2FuY2VcIiwgXCJzYXZpbmdzX3N0YXJ0ZXJfcmF0ZV9pbmNvbWVcIl0pXG4gICAgICAgICAgICAgIGNvbnNvbGUuYXNzZXJ0KGZ1bmMuY3R4LmFzdF9jbGFzcyA9PT0gXCJMb2FkXCIpXG4gICAgICAgICAgICAgIGNvbnN0IHZhcmlhYmxlc05hbWUgPSB0aGlzLmV4dHJhY3RTdHJpbmdMaXN0KFxuICAgICAgICAgICAgICAgIG5vZGUuYXJnc1syXSBhcyBQeXRob25Bc3ROb2RlLFxuICAgICAgICAgICAgICApXG4gICAgICAgICAgICAgIGlmICh2YXJpYWJsZXNOYW1lICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHZhcmlhYmxlc05hbWUubWFwKChuYW1lKSA9PiAoeyBuYW1lIH0pKVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGNvbnNvbGUubG9nKFwiVE9ETzogVW5yZWNvZ25pemVkIHZhcmlhYmxlIGNhbGwgaW4gZm9ybXVsYTpcIiwgbm9kZSlcbiAgICAgICAgICAgICAgY29uc29sZS5sb2coXCJub2RlLmFyZ3NcIiwgbm9kZS5hcmdzKVxuICAgICAgICAgICAgICBicmVhayBzd2l0Y2hOb2RlQXN0Q2xhc3NcbiAgICAgICAgICAgIH0gZWxzZSBpZiAoW1wiYXJvdW5kXCIsIFwicm91bmRcIiwgXCJyb3VuZF9cIl0uaW5jbHVkZXMoZnVuYy5pZCkpIHtcbiAgICAgICAgICAgICAgLy8gRXhhbXBsZTogYXJvdW5kKGlyX3BsYWZfcWYgLSBkZWNvdGVfZ2Fpbl9maXNjYWwpXG4gICAgICAgICAgICAgIGNvbnNvbGUuYXNzZXJ0KGZ1bmMuY3R4LmFzdF9jbGFzcyA9PT0gXCJMb2FkXCIpXG4gICAgICAgICAgICAgIGNvbnN0IGRlY29tcG9zaXRpb24gPSB0aGlzLmV4dHJhY3REZWNvbXBvc2l0aW9uKFxuICAgICAgICAgICAgICAgIG5vZGUuYXJnc1swXSBhcyBQeXRob25Bc3ROb2RlLFxuICAgICAgICAgICAgICApXG4gICAgICAgICAgICAgIGlmIChkZWNvbXBvc2l0aW9uICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIGRlY29tcG9zaXRpb25cbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBicmVhayBzd2l0Y2hOb2RlQXN0Q2xhc3NcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGJyZWFrIHN3aXRjaE5vZGVBc3RDbGFzc1xuICAgICAgICAgIH1cbiAgICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgICAgYnJlYWsgc3dpdGNoTm9kZUFzdENsYXNzXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGNhc2UgXCJOYW1lXCI6IHtcbiAgICAgICAgaWYgKG5vZGUuY3R4LmFzdF9jbGFzcyA9PT0gXCJMb2FkXCIpIHtcbiAgICAgICAgICBjb25zdCB2YXJpYWJsZURlc2NyaXB0aW9uID0gdGhpcy52YXJpYWJsZURlc2NyaXB0aW9uQnlJZFtub2RlLmlkXVxuICAgICAgICAgIGlmICh2YXJpYWJsZURlc2NyaXB0aW9uPy50eXBlID09PSBWYXJpYWJsZVR5cGUuRGVjb21wb3NpdGlvbikge1xuICAgICAgICAgICAgcmV0dXJuIHZhcmlhYmxlRGVzY3JpcHRpb24uZGVjb21wb3NpdGlvblxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBicmVhayBzd2l0Y2hOb2RlQXN0Q2xhc3NcbiAgICAgIH1cbiAgICAgIGNhc2UgXCJVbmFyeU9wXCI6IHtcbiAgICAgICAgc3dpdGNoIChub2RlLm9wLmFzdF9jbGFzcykge1xuICAgICAgICAgIGNhc2UgXCJVU3ViXCI6IHtcbiAgICAgICAgICAgIGNvbnN0IGRlY29tcG9zaXRpb24gPSB0aGlzLmV4dHJhY3REZWNvbXBvc2l0aW9uKG5vZGUub3BlcmFuZClcbiAgICAgICAgICAgIGlmIChkZWNvbXBvc2l0aW9uICE9PSBudWxsKSB7XG4gICAgICAgICAgICAgIHJldHVybiBkZWNvbXBvc2l0aW9uLm1hcCgoZGVjb21wb3NpdGlvblJlZmVyZW5jZSkgPT4ge1xuICAgICAgICAgICAgICAgIGRlY29tcG9zaXRpb25SZWZlcmVuY2UgPSB7IC4uLmRlY29tcG9zaXRpb25SZWZlcmVuY2UgfVxuICAgICAgICAgICAgICAgIGlmIChkZWNvbXBvc2l0aW9uUmVmZXJlbmNlLm5lZ2F0ZSkge1xuICAgICAgICAgICAgICAgICAgZGVsZXRlIGRlY29tcG9zaXRpb25SZWZlcmVuY2UubmVnYXRlXG4gICAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICAgIGRlY29tcG9zaXRpb25SZWZlcmVuY2UubmVnYXRlID0gdHJ1ZVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICByZXR1cm4gZGVjb21wb3NpdGlvblJlZmVyZW5jZVxuICAgICAgICAgICAgICB9KVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgYnJlYWsgc3dpdGNoTm9kZUFzdENsYXNzXG4gICAgICAgICAgfVxuICAgICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICBicmVhayBzd2l0Y2hOb2RlQXN0Q2xhc3NcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IG9wZW5GaXNjYVZhcmlhYmxlc05hbWUgPSB0aGlzLmV4dHJhY3RPcGVuRmlzY2FWYXJpYWJsZXNOYW1lKG5vZGUpXG4gICAgaWYgKFxuICAgICAgb3BlbkZpc2NhVmFyaWFibGVzTmFtZSAhPT0gbnVsbCAmJlxuICAgICAgb3BlbkZpc2NhVmFyaWFibGVzTmFtZS5sZW5ndGggPT09IDFcbiAgICApIHtcbiAgICAgIHJldHVybiBvcGVuRmlzY2FWYXJpYWJsZXNOYW1lLm1hcCgobmFtZSkgPT4gKHsgbmFtZSB9KSlcbiAgICB9XG5cbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgZXh0cmFjdE9wZW5GaXNjYVBhcmFtZXRlck5hbWUobm9kZTogUHl0aG9uQXN0Tm9kZSk6IHN0cmluZyB8IG51bGwge1xuICAgIHN3aXRjaCAobm9kZS5hc3RfY2xhc3MpIHtcbiAgICAgIGNhc2UgXCJBdHRyaWJ1dGVcIjpcbiAgICAgICAgcmV0dXJuIHRoaXMuZXh0cmFjdE9wZW5GaXNjYVBhcmFtZXRlck5hbWVGcm9tQXR0cmlidXRlKG5vZGUpXG4gICAgICBjYXNlIFwiQ2FsbFwiOlxuICAgICAgICByZXR1cm4gdGhpcy5leHRyYWN0T3BlbkZpc2NhUGFyYW1ldGVyTmFtZUZyb21DYWxsKG5vZGUpXG4gICAgICBjYXNlIFwiTmFtZVwiOiB7XG4gICAgICAgIGlmIChub2RlLmN0eC5hc3RfY2xhc3MgPT09IFwiTG9hZFwiKSB7XG4gICAgICAgICAgY29uc3QgdmFyaWFibGVEZXNjcmlwdGlvbiA9IHRoaXMudmFyaWFibGVEZXNjcmlwdGlvbkJ5SWRbbm9kZS5pZF1cbiAgICAgICAgICBpZiAodmFyaWFibGVEZXNjcmlwdGlvbj8udHlwZSA9PT0gVmFyaWFibGVUeXBlLk9wZW5GaXNjYVBhcmFtZXRlcikge1xuICAgICAgICAgICAgcmV0dXJuIHZhcmlhYmxlRGVzY3JpcHRpb24ubmFtZVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gbnVsbFxuICAgICAgfVxuICAgICAgY2FzZSBcIlN1YnNjcmlwdFwiOlxuICAgICAgICByZXR1cm4gdGhpcy5leHRyYWN0T3BlbkZpc2NhUGFyYW1ldGVyTmFtZUZyb21TdWJzY3JpcHQobm9kZSlcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHJldHVybiBudWxsXG4gICAgfVxuICB9XG5cbiAgZXh0cmFjdE9wZW5GaXNjYVBhcmFtZXRlck5hbWVGcm9tQXR0cmlidXRlKFxuICAgIG5vZGU6IFB5dGhvbkFzdEF0dHJpYnV0ZSxcbiAgKTogc3RyaW5nIHwgbnVsbCB7XG4gICAgY29uc3QgcGFyYW1ldGVyTmFtZSA9IHRoaXMuZXh0cmFjdE9wZW5GaXNjYVBhcmFtZXRlck5hbWUoXG4gICAgICBub2RlLnZhbHVlIGFzIFB5dGhvbkFzdE5vZGUsXG4gICAgKVxuICAgIGlmIChwYXJhbWV0ZXJOYW1lID09PSBudWxsKSB7XG4gICAgICByZXR1cm4gbnVsbFxuICAgIH1cbiAgICBpZiAodGhpcy5sZWFmUGFyYW1ldGVyc05hbWUuaGFzKHBhcmFtZXRlck5hbWUpKSB7XG4gICAgICAvLyBQeXRob25Bc3RBdHRyaWJ1dGUgYG5vZGUuYXR0cmAgaXMgYSBwcm9wZXJ0eSBvciBtZXRob2Qgb2YgcGFyYW1ldGVyLCBub3QgYSBjaGlsZC5cbiAgICAgIHJldHVybiBwYXJhbWV0ZXJOYW1lXG4gICAgfVxuICAgIGlmIChub2RlLmF0dHIgPT09IFwiX2NoaWxkcmVuXCIpIHtcbiAgICAgIC8vIFBhcmFtZXRlciBpcyBub3QgYSBsZWFmLCBidXQgaXRzIGNoaWxkcmVuIG5vZGVzIGFyZSBhY2Nlc3NlZCB1c2luZyBhblxuICAgICAgLy8gYWx0ZXJuYXRlIHN5bnRheC5cbiAgICAgIHJldHVybiBwYXJhbWV0ZXJOYW1lXG4gICAgfVxuICAgIHJldHVybiBbLi4ucGFyYW1ldGVyTmFtZS5zcGxpdChcIi5cIikuZmlsdGVyKEJvb2xlYW4pLCBub2RlLmF0dHJdLmpvaW4oXCIuXCIpXG4gIH1cblxuICBleHRyYWN0T3BlbkZpc2NhUGFyYW1ldGVyTmFtZUZyb21DYWxsKG5vZGU6IFB5dGhvbkFzdENhbGwpOiBzdHJpbmcgfCBudWxsIHtcbiAgICBjb25zdCBmdW5jID0gbm9kZS5mdW5jXG4gICAgaWYgKGZ1bmMuYXN0X2NsYXNzID09PSBcIk5hbWVcIiAmJiBmdW5jLmlkID09PSBcInBhcmFtZXRlcnNcIikge1xuICAgICAgcmV0dXJuIFwiXCJcbiAgICB9XG4gICAgcmV0dXJuIG51bGxcbiAgfVxuXG4gIGV4dHJhY3RPcGVuRmlzY2FQYXJhbWV0ZXJOYW1lRnJvbVN1YnNjcmlwdChcbiAgICBub2RlOiBQeXRob25Bc3RTdWJzY3JpcHQsXG4gICk6IHN0cmluZyB8IG51bGwge1xuICAgIGNvbnN0IHBhcmFtZXRlck5hbWUgPSB0aGlzLmV4dHJhY3RPcGVuRmlzY2FQYXJhbWV0ZXJOYW1lKFxuICAgICAgbm9kZS52YWx1ZSBhcyBQeXRob25Bc3ROb2RlLFxuICAgIClcbiAgICBpZiAocGFyYW1ldGVyTmFtZSA9PT0gbnVsbCkge1xuICAgICAgcmV0dXJuIG51bGxcbiAgICB9XG4gICAgaWYgKFxuICAgICAgIXRoaXMubGVhZlBhcmFtZXRlcnNOYW1lLmhhcyhwYXJhbWV0ZXJOYW1lKSAmJlxuICAgICAgbm9kZS5jdHguYXN0X2NsYXNzID09PSBcIkxvYWRcIiAmJlxuICAgICAgbm9kZS5zbGljZS5hc3RfY2xhc3MgPT09IFwiQ29uc3RhbnRcIiAmJlxuICAgICAgdHlwZW9mIG5vZGUuc2xpY2UudmFsdWUgPT09IFwic3RyaW5nXCJcbiAgICApIHtcbiAgICAgIHJldHVybiBbXG4gICAgICAgIC4uLnBhcmFtZXRlck5hbWUuc3BsaXQoXCIuXCIpLmZpbHRlcihCb29sZWFuKSxcbiAgICAgICAgbm9kZS5zbGljZS52YWx1ZSxcbiAgICAgIF0uam9pbihcIi5cIilcbiAgICB9XG4gICAgLy8gUHl0aG9uQXN0U3Vic2NyaXB0IGlzIG5vdCBhIGNoaWxkLlxuICAgIHJldHVybiBwYXJhbWV0ZXJOYW1lXG4gIH1cblxuICBleHRyYWN0T3BlbkZpc2NhVmFyaWF