@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
JavaScript
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