text-to-map
Version:
Text To Map usiluje o lepší, strojově zpracovatelné využití částí vyhlášek s výčtem ulic a dalších lokací. Jde o rozšiřitelnou sadu konceptů a nástrojů, které zajistí hladký převod výčtu ulic a jejich rozsahů v lidsky srozumitelném jazyce do strojově zpra
399 lines • 62.8 kB
JavaScript
import { AddressPointType, createSingleLineAddress } from "czech-address";
import { SeriesType, isNegativeSeriesSpec, isRange, isSeriesSpecArray, } from "../street-markdown/types";
import { findClosestString, roundToNDecimalPlaces } from "../utils/helpers";
import jtsk2wgs84 from "../utils/jtsk2wgs84";
import { extractKeyValuesPairs, generate2DPlaceholders, getKnexDb, insertMultipleRows, isMysql, isSqlite, nonEmptyOrNull, rawQuery, } from "./db";
import { getFounderCityCode } from "./founders";
import { MunicipalityType } from "./types";
import chunk from "lodash/chunk";
const DescriptiveType = "č.p.";
const RegistrationType = "č.ev.";
const ObjectTypes = {
[DescriptiveType]: 1,
[RegistrationType]: 2,
};
export const Column = {
admCode: 0,
cityCode: 1,
cityName: 2,
districtCode: 3,
districtName: 4,
pragueDistrictCode: 5,
pragueDistrictName: 6,
municipalityPartCode: 7,
municipalityPartName: 8,
streetCode: 9,
streetName: 10,
objectType: 11,
houseNumber: 12,
orientationalNumber: 13,
orientationalNumberLetter: 14,
postalCode: 15,
yCoordinate: 16,
xCoordinate: 17,
validFrom: 18,
};
const columnNames = [
"id",
"street_code",
"object_type_id",
"descriptive_number",
"orientational_number",
"orientational_number_letter",
"city_code",
"city_district_code",
"municipality_part_code",
"prague_district_code",
"postal_code",
"jtsk_x",
"jtsk_y",
"wgs84_latitude",
"wgs84_longitude",
];
export const commitAddressPoints = async (buffer) => {
if (buffer.length === 0) {
return 0;
}
await insertCities(buffer);
await insertDistricts(buffer);
await insertMunicipalityParts(buffer);
await insertStreets(buffer);
await insertPragueDistricts(buffer);
const params = {};
buffer.forEach((data) => {
let latOrNull, lonOrNull;
latOrNull = null;
lonOrNull = null;
if (data[Column.xCoordinate] && data[Column.yCoordinate]) {
const { lat, lon } = jtsk2wgs84(parseFloat(data[Column.xCoordinate]), parseFloat(data[Column.yCoordinate]));
[latOrNull, lonOrNull] = [lat.toString(), lon.toString()];
}
params[data[Column.admCode]] = [
data[Column.admCode],
nonEmptyOrNull(data[Column.streetCode]),
ObjectTypes[data[Column.objectType]],
nonEmptyOrNull(data[Column.houseNumber]),
nonEmptyOrNull(data[Column.orientationalNumber]),
nonEmptyOrNull(data[Column.orientationalNumberLetter]),
data[Column.cityCode],
nonEmptyOrNull(data[Column.districtCode]),
nonEmptyOrNull(data[Column.municipalityPartCode]),
nonEmptyOrNull(data[Column.pragueDistrictCode]),
data[Column.postalCode],
nonEmptyOrNull(data[Column.xCoordinate]),
nonEmptyOrNull(data[Column.yCoordinate]),
latOrNull,
lonOrNull,
];
});
// find already existing rows
const existingRows = await rawQuery(`SELECT * FROM address_point WHERE id IN (${buffer
.map((data) => data[Column.admCode])
.join(",")})`);
const queries = [];
for (const row of existingRows) {
const toInsert = params[row.id];
const values = Object.values(row);
if (!toInsert.every((v, i) => valuesEqual(v, values[i]))) {
// update those whose values differ
queries.push(rawQuery(`UPDATE address_point SET ${columnNames
.map((c) => `${c} = ?`)
.join(", ")} WHERE id = ?`, [...toInsert, row.id]));
}
}
await Promise.all(queries);
const placeHolders = generate2DPlaceholders(columnNames.length, buffer.length);
const knex = getKnexDb();
await rawQuery(`INSERT ${isMysql(knex) ? "IGNORE" : ""} INTO address_point (${columnNames.join(",")}) VALUES ${placeHolders} ${!isMysql(knex) ? "ON CONFLICT (id) DO NOTHING" : ""}`, Object.values(params).flat());
return buffer.length;
};
export async function removeDeprecatedAddressPoints(allIds) {
const knex = getKnexDb();
const existingIds = await knex.pluck("id").from("address_point");
const toRemove = existingIds.filter((id) => !allIds.has(id));
const chunks = chunk(toRemove, 1000);
for (const chunk of chunks) {
await knex("address_point").whereIn("id", chunk).delete();
}
console.log(`Removed ${toRemove.length} deprecated address points.`);
}
function valuesEqual(a, b) {
return sanitizeValue(a) === sanitizeValue(b);
}
function sanitizeValue(value) {
if (value === null) {
return null;
}
value = value.toString();
if (/\d+\.\d+/.test(value)) {
return roundToNDecimalPlaces(parseFloat(value), 6).toString();
}
return value;
}
export const insertCities = async (buffer) => {
return await insertMultipleRows(extractKeyValuesPairs(buffer, Column.cityCode, [Column.cityName]), "city", ["code", "name"]);
};
export const insertDistricts = async (buffer) => {
return await insertMultipleRows(extractKeyValuesPairs(buffer, Column.districtCode, [
Column.cityCode,
Column.districtName,
]), "city_district", ["code", "city_code", "name"]);
};
export const insertMunicipalityParts = async (buffer) => {
return await insertMultipleRows(extractKeyValuesPairs(buffer, Column.municipalityPartCode, [
Column.municipalityPartName,
Column.cityCode,
]), "municipality_part", ["code", "name", "city_code"]);
};
export const insertPragueDistricts = async (buffer) => {
return await insertMultipleRows(extractKeyValuesPairs(buffer, Column.pragueDistrictCode, [
Column.pragueDistrictName,
]), "prague_district", ["code", "name"]);
};
export const insertStreets = async (buffer) => {
return await insertMultipleRows(extractKeyValuesPairs(buffer, Column.streetCode, [
Column.cityCode,
Column.streetName,
]), "street", ["code", "city_code", "name"]);
};
export const areAddressPointsSynced = async () => {
const result = await getKnexDb()
.count("*", { as: "countAll" })
.from("address_point")
.first();
return Number(result.countAll) >= 2900000; // total is almost 3 million
};
const addressPointSelect = `
SELECT a.id, s.name AS street_name, o.name AS object_type_name, a.descriptive_number,
a.orientational_number, a.orientational_number_letter, c.name AS city_name,
m.name AS municipality_part_name, p.name AS prague_district_name,
d.name AS district_name, a.postal_code, a.wgs84_latitude, a.wgs84_longitude
FROM address_point a
LEFT JOIN street s ON a.street_code = s.code
INNER JOIN object_type o ON o.id = a.object_type_id
INNER JOIN city c ON c.code = a.city_code
LEFT JOIN city_district d ON a.city_district_code = d.code
LEFT JOIN prague_district p ON a.prague_district_code = p.code
LEFT JOIN municipality_part m ON a.municipality_part_code = m.code`;
export const getAddressPointById = async (addressPointId) => {
const row = await rawQuery(`${addressPointSelect}
WHERE a.id = ?`, [addressPointId]);
if (row.length === 0) {
return null;
}
return rowToAddressPoint(row[0]);
};
let lastFounder = null;
let allStreets = [];
export const checkStreetExists = async (streetName, founder) => {
const knex = getKnexDb();
const errors = [];
// we check the whole city
const cityCode = await getFounderCityCode(founder.municipalityType, founder.municipalityCode);
const rowList = await rawQuery(`SELECT name AS street_name
FROM street
WHERE city_code = ? AND name = ? ${isSqlite(knex) ? "COLLATE NOCASE" : ""}`, [cityCode, streetName]);
if (rowList.length > 0) {
const row = rowList[0];
if (row.street_name !== streetName) {
// errors.push(
// `Street '${streetName}' has wrong case, correct case: '${row.street_name}'.`
// );
}
return { exists: true, errors };
}
if (lastFounder !== founder) {
allStreets = await getAllStreets(cityCode);
lastFounder = founder;
}
const match = allStreets.find((s) => s.toLocaleLowerCase("cs-CZ") === streetName.toLocaleLowerCase("cs-CZ"));
let exists = false;
if (match) {
exists = true;
if (streetName !== match) {
// errors.push(
// `Street '${streetName}' has wrong case, correct case: '${match}'.`
// );
}
}
else {
const closest = findClosestString(streetName, allStreets);
errors.push({
message: `Ulice '${streetName}' v této obci neexistuje, mysleli jste '${closest}'?`,
startOffset: 0,
endOffset: streetName.length + 1,
});
}
return { exists: false, errors };
};
const getAllStreets = async (cityCode) => {
const knex = getKnexDb();
return await knex.pluck("name").from("street").where("city_code", cityCode);
};
export const findAddressPoints = async (params) => {
const queryParams = getQueryParams(params);
const streetJoinCondition = getStreetJoinCondition(params);
const whereCondition = getWhereCondition(params);
const queryResult = await rawQuery(`SELECT a.id, s.name AS street_name, o.name AS object_type_name, a.descriptive_number,
a.orientational_number, a.orientational_number_letter, c.name AS city_name,
d.name AS district_name, m.name AS municipality_part_name, p.name AS prague_district_name,
a.postal_code, a.wgs84_latitude, a.wgs84_longitude
FROM address_point a
${streetJoinCondition}
INNER JOIN object_type o ON o.id = a.object_type_id
INNER JOIN city c ON c.code = a.city_code
LEFT JOIN city_district d ON a.city_district_code = d.code
LEFT JOIN municipality_part m ON a.municipality_part_code = m.code
LEFT JOIN prague_district p ON a.prague_district_code = p.code
WHERE ${whereCondition}`, queryParams);
return filterAddressPoints(queryResult.map(rowToAddressPoint), params);
};
export const filterAddressPoints = (addressPoints, params) => {
if (params.type === "wholeMunicipality" ||
params.type === "wholeMunicipalityNoStreetName" ||
params.type === "wholeMunicipalityPart") {
return addressPoints;
}
const result = [];
const numberSpec = params.smdLine.numberSpec;
if (isSeriesSpecArray(numberSpec)) {
numberSpec.forEach((seriesSpec) => {
result.push(...filterAddressPointsByRanges(addressPoints, seriesSpec));
});
// when no number specs are present, we take everything
if (numberSpec.length === 0) {
result.push(...addressPoints);
}
}
else if (isNegativeSeriesSpec(numberSpec)) {
// negative number spec (e.g. odd numbers except 13-17)
const toExclude = filterAddressPointsByRanges(addressPoints, numberSpec);
result.push(...addressPoints.filter((addressPoint) => !toExclude.includes(addressPoint)));
}
return result;
};
const filterAddressPointsByRanges = (addressPoints, seriesSpec) => {
const result = [];
seriesSpec.ranges.forEach((range) => {
result.push(...addressPoints.filter((addressPoint) => {
if (isRange(range)) {
const number = getNumberByType(seriesSpec.type, addressPoint);
return (isInRange(number, seriesSpec.type !== SeriesType.Description
? addressPoint.orientationalNumberLetter ?? null
: null, range) && fitsType(number, seriesSpec.type));
}
else {
const fullStreetNumber = range;
return equalsFullStreetNumber(fullStreetNumber, addressPoint);
}
}));
});
// when no series specs are present, we take everything that fits the type
if (seriesSpec.ranges.length === 0) {
result.push(...addressPoints.filter((addressPoint) => fitsType(getNumberByType(seriesSpec.type, addressPoint), seriesSpec.type)));
}
return result;
};
export const getQueryParams = (params) => {
if (params.type === "smdLine" && params.smdLine.type === "street") {
return [params.smdLine.street, params.municipality.code];
}
if ((params.type === "smdLine" && params.smdLine.type === "municipalityPart") ||
params.type === "wholeMunicipalityPart") {
return [params.municipalityPartCode];
}
return [params.municipality.code];
};
export const getStreetJoinCondition = (params) => {
if (params.type === "smdLine" && params.smdLine.type === "street") {
return `JOIN street s ON a.street_code = s.code AND s.name = ? ${isSqlite(getKnexDb()) ? "COLLATE NOCASE" : ""}`;
}
return "LEFT JOIN street s ON a.street_code = s.code";
};
export const getWhereCondition = (params) => {
if ((params.type === "smdLine" && params.smdLine.type === "municipalityPart") ||
params.type === "wholeMunicipalityPart") {
return "a.municipality_part_code = ?";
}
if (params.type === "wholeMunicipalityNoStreetName") {
return `${getMunicipalityWhere("a", params.municipality)} AND a.street_code IS NULL`;
}
return getMunicipalityWhere("a", params.municipality);
};
export const isInRange = (number, letter, range) => {
if (number === null) {
return !range.from && !range.to;
}
// explainer: either the lower bound is not set, or it is lower than the number
// if it's equal to the number, we also have to check the letter - if it's not set, we don't care
// if from.letter is set, we need to have a letter set and it has to be greater or equal:
// number without letter counts lower than number with letter 'a' (e.g. 23 < 23a)
const satisfiesFrom = !range.from ||
number > range.from.number ||
(number === range.from.number &&
(!range.from.letter || (!!letter && letter >= range.from.letter)));
const satisfiesTo = !range.to ||
number < range.to.number ||
(number === range.to.number &&
(!letter ||
(!!range.to.letter && !!letter && letter <= range.to.letter)));
return satisfiesFrom && satisfiesTo;
};
export const fitsType = (number, type) => {
return (type === SeriesType.Description ||
type === SeriesType.All ||
(type === SeriesType.Odd && number % 2 === 1) ||
(type === SeriesType.Even && number % 2 === 0));
};
export const equalsFullStreetNumber = (fullStreetNumber, addressPoint) => {
return (fullStreetNumber.descriptionNumber.number === addressPoint.houseNumber &&
fullStreetNumber.orientationalNumber.number ===
addressPoint.orientationalNumber &&
((!fullStreetNumber.orientationalNumber.letter &&
!addressPoint.orientationalNumberLetter) ||
fullStreetNumber.orientationalNumber.letter ===
addressPoint.orientationalNumberLetter));
};
const getNumberByType = (type, addressPoint) => {
return type === SeriesType.Description
? addressPoint.houseNumber
: addressPoint.orientationalNumber ?? null;
};
const getMunicipalityWhere = (alias, municipality) => {
return municipality.type === MunicipalityType.City
? `${alias}.city_code = ?`
: `${alias}.city_district_code = ?`;
};
const rowToAddressPoint = (row) => {
const point = {
id: row.id,
address: "",
type: row.object_type_name === DescriptiveType
? AddressPointType.Description
: AddressPointType.Registration,
houseNumber: row.descriptive_number,
city: row.city_name,
municipalityPart: row.municipality_part_name,
postalCode: row.postal_code,
lat: row.wgs84_latitude,
lng: row.wgs84_longitude,
};
if (row.street_name !== null) {
point.street = row.street_name;
}
if (row.orientational_number !== null) {
point.orientationalNumber = row.orientational_number;
}
if (row.orientational_number_letter !== null) {
point.orientationalNumberLetter = row.orientational_number_letter;
}
if (row.district_name !== null) {
point.district = row.district_name;
}
if (row.prague_district_name !== null) {
point.pragueDistrict = row.prague_district_name;
}
point.address = createSingleLineAddress(point);
return point;
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWRkcmVzcy1wb2ludHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZGIvYWRkcmVzcy1wb2ludHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGdCQUFnQixFQUFFLHVCQUF1QixFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQzFFLE9BQU8sRUFLTCxVQUFVLEVBR1Ysb0JBQW9CLEVBQ3BCLE9BQU8sRUFDUCxpQkFBaUIsR0FDbEIsTUFBTSwwQkFBMEIsQ0FBQztBQUNsQyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUM1RSxPQUFPLFVBQVUsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QyxPQUFPLEVBQ0wscUJBQXFCLEVBQ3JCLHNCQUFzQixFQUN0QixTQUFTLEVBQ1Qsa0JBQWtCLEVBQ2xCLE9BQU8sRUFDUCxRQUFRLEVBQ1IsY0FBYyxFQUNkLFFBQVEsR0FDVCxNQUFNLE1BQU0sQ0FBQztBQUNkLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUNoRCxPQUFPLEVBQXlCLGdCQUFnQixFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQ2xFLE9BQU8sS0FBSyxNQUFNLGNBQWMsQ0FBQztBQUVqQyxNQUFNLGVBQWUsR0FBRyxNQUFNLENBQUM7QUFDL0IsTUFBTSxnQkFBZ0IsR0FBRyxPQUFPLENBQUM7QUFFakMsTUFBTSxXQUFXLEdBQUc7SUFDbEIsQ0FBQyxlQUFlLENBQUMsRUFBRSxDQUFDO0lBQ3BCLENBQUMsZ0JBQWdCLENBQUMsRUFBRSxDQUFDO0NBQ3RCLENBQUM7QUFFRixNQUFNLENBQUMsTUFBTSxNQUFNLEdBQUc7SUFDcEIsT0FBTyxFQUFFLENBQUM7SUFDVixRQUFRLEVBQUUsQ0FBQztJQUNYLFFBQVEsRUFBRSxDQUFDO0lBQ1gsWUFBWSxFQUFFLENBQUM7SUFDZixZQUFZLEVBQUUsQ0FBQztJQUNmLGtCQUFrQixFQUFFLENBQUM7SUFD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YUFBYSxDQUFDO0tBQ3BDO0lBQ0QsSUFBSSxHQUFHLENBQUMsb0JBQW9CLEtBQUssSUFBSSxFQUFFO1FBQ3JDLEtBQUssQ0FBQyxjQUFjLEdBQUcsR0FBRyxDQUFDLG9CQUFvQixDQUFDO0tBQ2pEO0lBQ0QsS0FBSyxDQUFDLE9BQU8sR0FBRyx1QkFBdUIsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUMvQyxPQUFPLEtBQUssQ0FBQztBQUNmLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEFkZHJlc3NQb2ludFR5cGUsIGNyZWF0ZVNpbmdsZUxpbmVBZGRyZXNzIH0gZnJvbSBcImN6ZWNoLWFkZHJlc3NcIjtcclxuaW1wb3J0IHtcclxuICBBZGRyZXNzUG9pbnQsXHJcbiAgRnVsbFN0cmVldE51bWJlcixcclxuICBSYW5nZVNwZWMsXHJcbiAgU2VyaWVzU3BlYyxcclxuICBTZXJpZXNUeXBlLFxyXG4gIFNtZEVycm9yLFxyXG4gIFNtZExpbmUsXHJcbiAgaXNOZWdhdGl2ZVNlcmllc1NwZWMsXHJcbiAgaXNSYW5nZSxcclxuICBpc1Nlcmllc1NwZWNBcnJheSxcclxufSBmcm9tIFwiLi4vc3RyZWV0LW1hcmtkb3duL3R5cGVzXCI7XHJcbmltcG9ydCB7IGZpbmRDbG9zZXN0U3RyaW5nLCByb3VuZFRvTkRlY2ltYWxQbGFjZXMgfSBmcm9tIFwiLi4vdXRpbHMvaGVscGVyc1wiO1xyXG5pbXBvcnQganRzazJ3Z3M4NCBmcm9tIFwiLi4vdXRpbHMvanRzazJ3Z3M4NFwiO1xyXG5pbXBvcnQge1xyXG4gIGV4dHJhY3RLZXlWYWx1ZXNQYWlycyxcclxuICBnZW5lcmF0ZTJEUGxhY2Vob2xkZXJzLFxyXG4gIGdldEtuZXhEYixcclxuICBpbnNlcnRNdWx0aXBsZVJvd3MsXHJcbiAgaXNNeXNxbCxcclxuICBpc1NxbGl0ZSxcclxuICBub25FbXB0eU9yTnVsbCxcclxuICByYXdRdWVyeSxcclxufSBmcm9tIFwiLi9kYlwiO1xyXG5pbXBvcnQgeyBnZXRGb3VuZGVyQ2l0eUNvZGUgfSBmcm9tIFwiLi9mb3VuZGVyc1wiO1xyXG5pbXBvcnQgeyBGb3VuZGVyLCBNdW5pY2lwYWxpdHksIE11bmljaXBhbGl0eVR5cGUgfSBmcm9tIFwiLi90eXBlc1wiO1xyXG5pbXBvcnQgY2h1bmsgZnJvbSBcImxvZGFzaC9jaHVua1wiO1xyXG5cclxuY29uc3QgRGVzY3JpcHRpdmVUeXBlID0gXCLEjS5wLlwiO1xyXG5jb25zdCBSZWdpc3RyYXRpb25UeXBlID0gXCLEjS5ldi5cIjtcclxuXHJcbmNvbnN0IE9iamVjdFR5cGVzID0ge1xyXG4gIFtEZXNjcmlwdGl2ZVR5cGVdOiAxLFxyXG4gIFtSZWdpc3RyYXRpb25UeXBlXTogMixcclxufTtcclxuXHJcbmV4cG9ydCBjb25zdCBDb2x1bW4gPSB7XHJcbiAgYWRtQ29kZTogMCxcclxuICBjaXR5Q29kZTogMSxcclxuICBjaXR5TmFtZTogMixcclxuICBkaXN0cmljdENvZGU6IDMsXHJcbiAgZGlzdHJpY3ROYW1lOiA0LFxyXG4gIHByYWd1ZURpc3RyaWN0Q29kZTogNSxcclxuICBwcmFndWVEaXN0cmljdE5hbWU6IDYsXHJcbiAgbXVuaWNpcGFsaXR5UGFydENvZGU6IDcsXHJcbiAgbXVuaWNpcGFsaXR5UGFydE5hbWU6IDgsXHJcbiAgc3RyZWV0Q29kZTogOSxcclxuICBzdHJlZXROYW1lOiAxMCxcclxuICBvYmplY3RUeXBlOiAxMSxcclxuICBob3VzZU51bWJlcjogMTIsXHJcbiAgb3JpZW50YXRpb25hbE51bWJlcjogMTMsXHJcbiAgb3JpZW50YXRpb25hbE51bWJlckxldHRlcjogMTQsXHJcbiAgcG9zdGFsQ29kZTogMTUsXHJcbiAgeUNvb3JkaW5hdGU6IDE2LFxyXG4gIHhDb29yZGluYXRlOiAxNyxcclxuICB2YWxpZEZyb206IDE4LFxyXG59O1xyXG5cclxuY29uc3QgY29sdW1uTmFtZXMgPSBbXHJcbiAgXCJpZFwiLFxyXG4gIFwic3RyZWV0X2NvZGVcIixcclxuICBcIm9iamVjdF90eXBlX2lkXCIsXHJcbiAgXCJkZXNjcmlwdGl2ZV9udW1iZXJcIixcclxuICBcIm9yaWVudGF0aW9uYWxfbnVtYmVyXCIsXHJcbiAgXCJvcmllbnRhdGlvbmFsX251bWJlcl9sZXR0ZXJcIixcclxuICBcImNpdHlfY29kZVwiLFxyXG4gIFwiY2l0eV9kaXN0cmljdF9jb2RlXCIsXHJcbiAgXCJtdW5pY2lwYWxpdHlfcGFydF9jb2RlXCIsXHJcbiAgXCJwcmFndWVfZGlzdHJpY3RfY29kZVwiLFxyXG4gIFwicG9zdGFsX2NvZGVcIixcclxuICBcImp0c2tfeFwiLFxyXG4gIFwianRza195XCIsXHJcbiAgXCJ3Z3M4NF9sYXRpdHVkZVwiLFxyXG4gIFwid2dzODRfbG9uZ2l0dWRlXCIsXHJcbl07XHJcblxyXG5leHBvcnQgY29uc3QgY29tbWl0QWRkcmVzc1BvaW50cyA9IGFzeW5jIChcclxuICBidWZmZXI6IHN0cmluZ1tdW11cclxuKTogUHJvbWlzZTxudW1iZXI+ID0+IHtcclxuICBpZiAoYnVmZmVyLmxlbmd0aCA9PT0gMCkge1xyXG4gICAgcmV0dXJuIDA7XHJcbiAgfVxyXG4gIGF3YWl0IGluc2VydENpdGllcyhidWZmZXIpO1xyXG4gIGF3YWl0IGluc2VydERpc3RyaWN0cyhidWZmZXIpO1xyXG4gIGF3YWl0IGluc2VydE11bmljaXBhbGl0eVBhcnRzKGJ1ZmZlcik7XHJcbiAgYXdhaXQgaW5zZXJ0U3RyZWV0cyhidWZmZXIpO1xyXG4gIGF3YWl0IGluc2VydFByYWd1ZURpc3RyaWN0cyhidWZmZXIpO1xyXG5cclxuICBjb25zdCBwYXJhbXM6IFJlY29yZDxzdHJpbmcsIChzdHJpbmcgfCBudWxsKVtdPiA9IHt9O1xyXG4gIGJ1ZmZlci5mb3JFYWNoKChkYXRhKSA9PiB7XHJcbiAgICBsZXQgbGF0T3JOdWxsOiBzdHJpbmcsIGxvbk9yTnVsbDogc3RyaW5nO1xyXG4gICAgbGF0T3JOdWxsID0gbnVsbDtcclxuICAgIGxvbk9yTnVsbCA9IG51bGw7XHJcbiAgICBpZiAoZGF0YVtDb2x1bW4ueENvb3JkaW5hdGVdICYmIGRhdGFbQ29sdW1uLnlDb29yZGluYXRlXSkge1xyXG4gICAgICBjb25zdCB7IGxhdCwgbG9uIH0gPSBqdHNrMndnczg0KFxyXG4gICAgICAgIHBhcnNlRmxvYXQoZGF0YVtDb2x1bW4ueENvb3JkaW5hdGVdKSxcclxuICAgICAgICBwYXJzZUZsb2F0KGRhdGFbQ29sdW1uLnlDb29yZGluYXRlXSlcclxuICAgICAgKTtcclxuICAgICAgW2xhdE9yTnVsbCwgbG9uT3JOdWxsXSA9IFtsYXQudG9TdHJpbmcoKSwgbG9uLnRvU3RyaW5nKCldO1xyXG4gICAgfVxyXG4gICAgcGFyYW1zW2RhdGFbQ29sdW1uLmFkbUNvZGVdXSA9IFtcclxuICAgICAgZGF0YVtDb2x1bW4uYWRtQ29kZV0sXHJcbiAgICAgIG5vbkVtcHR5T3JOdWxsKGRhdGFbQ29sdW1uLnN0cmVldENvZGVdKSxcclxuICAgICAgT2JqZWN0VHlwZXNbZGF0YVtDb2x1bW4ub2JqZWN0VHlwZV1dLFxyXG4gICAgICBub25FbXB0eU9yTnVsbChkYXRhW0NvbHVtbi5ob3VzZU51bWJlcl0pLFxyXG4gICAgICBub25FbXB0eU9yTnVsbChkYXRhW0NvbHVtbi5vcmllbnRhdGlvbmFsTnVtYmVyXSksXHJcbiAgICAgIG5vbkVtcHR5T3JOdWxsKGRhdGFbQ29sdW1uLm9yaWVudGF0aW9uYWxOdW1iZXJMZXR0ZXJdKSxcclxuICAgICAgZGF0YVtDb2x1bW4uY2l0eUNvZGVdLFxyXG4gICAgICBub25FbXB0eU9yTnVsbChkYXRhW0NvbHVtbi5kaXN0cmljdENvZGVdKSxcclxuICAgICAgbm9uRW1wdHlPck51bGwoZGF0YVtDb2x1bW4ubXVuaWNpcGFsaXR5UGFydENvZGVdKSxcclxuICAgICAgbm9uRW1wdHlPck51bGwoZGF0YVtDb2x1bW4ucHJhZ3VlRGlzdHJpY3RDb2RlXSksXHJcbiAgICAgIGRhdGFbQ29sdW1uLnBvc3RhbENvZGVdLFxyXG4gICAgICBub25FbXB0eU9yTnVsbChkYXRhW0NvbHVtbi54Q29vcmRpbmF0ZV0pLFxyXG4gICAgICBub25FbXB0eU9yTnVsbChkYXRhW0NvbHVtbi55Q29vcmRpbmF0ZV0pLFxyXG4gICAgICBsYXRPck51bGwsXHJcbiAgICAgIGxvbk9yTnVsbCxcclxuICAgIF07XHJcbiAgfSk7XHJcblxyXG4gIC8vIGZpbmQgYWxyZWFkeSBleGlzdGluZyByb3dzXHJcbiAgY29uc3QgZXhpc3RpbmdSb3dzID0gYXdhaXQgcmF3UXVlcnkoXHJcbiAgICBgU0VMRUNUICogRlJPTSBhZGRyZXNzX3BvaW50IFdIRVJFIGlkIElOICgke2J1ZmZlclxyXG4gICAgICAubWFwKChkYXRhKSA9PiBkYXRhW0NvbHVtbi5hZG1Db2RlXSlcclxuICAgICAgLmpvaW4oXCIsXCIpfSlgXHJcbiAgKTtcclxuXHJcbiAgY29uc3QgcXVlcmllcyA9IFtdO1xyXG4gIGZvciAoY29uc3Qgcm93IG9mIGV4aXN0aW5nUm93cykge1xyXG4gICAgY29uc3QgdG9JbnNlcnQgPSBwYXJhbXNbcm93LmlkXTtcclxuICAgIGNvbnN0IHZhbHVlcyA9IE9iamVjdC52YWx1ZXMocm93KTtcclxuICAgIGlmICghdG9JbnNlcnQuZXZlcnkoKHYsIGkpID0+IHZhbHVlc0VxdWFsKHYsIHZhbHVlc1tpXSkpKSB7XHJcbiAgICAgIC8vIHVwZGF0ZSB0aG9zZSB3aG9zZSB2YWx1ZXMgZGlmZmVyXHJcbiAgICAgIHF1ZXJpZXMucHVzaChcclxuICAgICAgICByYXdRdWVyeShcclxuICAgICAgICAgIGBVUERBVEUgYWRkcmVzc19wb2ludCBTRVQgJHtjb2x1bW5OYW1lc1xyXG4gICAgICAgICAgICAubWFwKChjKSA9PiBgJHtjfSA9ID9gKVxyXG4gICAgICAgICAgICAuam9pbihcIiwgXCIpfSBXSEVSRSBpZCA9ID9gLFxyXG4gICAgICAgICAgWy4uLnRvSW5zZXJ0LCByb3cuaWRdXHJcbiAgICAgICAgKVxyXG4gICAgICApO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgYXdhaXQgUHJvbWlzZS5hbGwocXVlcmllcyk7XHJcblxyXG4gIGNvbnN0IHBsYWNlSG9sZGVycyA9IGdlbmVyYXRlMkRQbGFjZWhvbGRlcnMoXHJcbiAgICBjb2x1bW5OYW1lcy5sZW5ndGgsXHJcbiAgICBidWZmZXIubGVuZ3RoXHJcbiAgKTtcclxuXHJcbiAgY29uc3Qga25leCA9IGdldEtuZXhEYigpO1xyXG4gIGF3YWl0IHJhd1F1ZXJ5KFxyXG4gICAgYElOU0VSVCAke1xyXG4gICAgICBpc015c3FsKGtuZXgpID8gXCJJR05PUkVcIiA6IFwiXCJcclxuICAgIH0gSU5UTyBhZGRyZXNzX3BvaW50ICgke2NvbHVtbk5hbWVzLmpvaW4oXCIsXCIpfSkgVkFMVUVTICR7cGxhY2VIb2xkZXJzfSAke1xyXG4gICAgICAhaXNNeXNxbChrbmV4KSA/IFwiT04gQ09ORkxJQ1QgKGlkKSBETyBOT1RISU5HXCIgOiBcIlwiXHJcbiAgICB9YCxcclxuICAgIE9iamVjdC52YWx1ZXMocGFyYW1zKS5mbGF0KClcclxuICApO1xyXG5cclxuICByZXR1cm4gYnVmZmVyLmxlbmd0aDtcclxufTtcclxuXHJcbmV4cG9ydCBhc3luYyBmdW5jdGlvbiByZW1vdmVEZXByZWNhdGVkQWRkcmVzc1BvaW50cyhcclxuICBhbGxJZHM6IFNldDxudW1iZXI+XHJcbik6IFByb21pc2U8dm9pZD4ge1xyXG4gIGNvbnN0IGtuZXggPSBnZXRLbmV4RGIoKTtcclxuICBjb25zdCBleGlzdGluZ0lkcyA9IGF3YWl0IGtuZXgucGx1Y2soXCJpZFwiKS5mcm9tKFwiYWRkcmVzc19wb2ludFwiKTtcclxuICBjb25zdCB0b1JlbW92ZSA9IGV4aXN0aW5nSWRzLmZpbHRlcigoaWQpID0+ICFhbGxJZHMuaGFzKGlkKSk7XHJcbiAgY29uc3QgY2h1bmtzID0gY2h1bmsodG9SZW1vdmUsIDEwMDApO1xyXG4gIGZvciAoY29uc3QgY2h1bmsgb2YgY2h1bmtzKSB7XHJcbiAgICBhd2FpdCBrbmV4KFwiYWRkcmVzc19wb2ludFwiKS53aGVyZUluKFwiaWRcIiwgY2h1bmspLmRlbGV0ZSgpO1xyXG4gIH1cclxuICBjb25zb2xlLmxvZyhgUmVtb3ZlZCAke3RvUmVtb3ZlLmxlbmd0aH0gZGVwcmVjYXRlZCBhZGRyZXNzIHBvaW50cy5gKTtcclxufVxyXG5cclxuZnVuY3Rpb24gdmFsdWVzRXF1YWwoYTogYW55LCBiOiBhbnkpOiBib29sZWFuIHtcclxuICByZXR1cm4gc2FuaXRpemVWYWx1ZShhKSA9PT0gc2FuaXRpemVWYWx1ZShiKTtcclxufVxyXG5cclxuZnVuY3Rpb24gc2FuaXRpemVWYWx1ZSh2YWx1ZTogYW55KTogYW55IHtcclxuICBpZiAodmFsdWUgPT09IG51bGwpIHtcclxuICAgIHJldHVybiBudWxsO1xyXG4gIH1cclxuICB2YWx1ZSA9IHZhbHVlLnRvU3RyaW5nKCk7XHJcblxyXG4gIGlmICgvXFxkK1xcLlxcZCsvLnRlc3QodmFsdWUpKSB7XHJcbiAgICByZXR1cm4gcm91bmRUb05EZWNpbWFsUGxhY2VzKHBhcnNlRmxvYXQodmFsdWUpLCA2KS50b1N0cmluZygpO1xyXG4gIH1cclxuICByZXR1cm4gdmFsdWU7XHJcbn1cclxuXHJcbmV4cG9ydCBjb25zdCBpbnNlcnRDaXRpZXMgPSBhc3luYyAoYnVmZmVyOiBzdHJpbmdbXVtdKTogUHJvbWlzZTxudW1iZXI+ID0+IHtcclxuICByZXR1cm4gYXdhaXQgaW5zZXJ0TXVsdGlwbGVSb3dzKFxyXG4gICAgZXh0cmFjdEtleVZhbHVlc1BhaXJzKGJ1ZmZlciwgQ29sdW1uLmNpdHlDb2RlLCBbQ29sdW1uLmNpdHlOYW1lXSksXHJcbiAgICBcImNpdHlcIixcclxuICAgIFtcImNvZGVcIiwgXCJuYW1lXCJdXHJcbiAgKTtcclxufTtcclxuXHJcbmV4cG9ydCBjb25zdCBpbnNlcnREaXN0cmljdHMgPSBhc3luYyAoYnVmZmVyOiBzdHJpbmdbXVtdKTogUHJvbWlzZTxudW1iZXI+ID0+IHtcclxuICByZXR1cm4gYXdhaXQgaW5zZXJ0TXVsdGlwbGVSb3dzKFxyXG4gICAgZXh0cmFjdEtleVZhbHVlc1BhaXJzKGJ1ZmZlciwgQ29sdW1uLmRpc3RyaWN0Q29kZSwgW1xyXG4gICAgICBDb2x1bW4uY2l0eUNvZGUsXHJcbiAgICAgIENvbHVtbi5kaXN0cmljdE5hbWUsXHJcbiAgICBdKSxcclxuICAgIFwiY2l0eV9kaXN0cmljdFwiLFxyXG4gICAgW1wiY29kZVwiLCBcImNpdHlfY29kZVwiLCBcIm5hbWVcIl1cclxuICApO1xyXG59O1xyXG5cclxuZXhwb3J0IGNvbnN0IGluc2VydE11bmljaXBhbGl0eVBhcnRzID0gYXN5bmMgKFxyXG4gIGJ1ZmZlcjogc3RyaW5nW11bXVxyXG4pOiBQcm9taXNlPG51bWJlcj4gPT4ge1xyXG4gIHJldHVybiBhd2FpdCBpbnNlcnRNdWx0aXBsZVJvd3MoXHJcbiAgICBleHRyYWN0S2V5VmFsdWVzUGFpcnMoYnVmZmVyLCBDb2x1bW4ubXVuaWNpcGFsaXR5UGFydENvZGUsIFtcclxuICAgICAgQ29sdW1uLm11bmljaXBhbGl0eVBhcnROYW1lLFxyXG4gICAgICBDb2x1bW4uY2l0eUNvZGUsXHJcbiAgICBdKSxcclxuICAgIFwibXVuaWNpcGFsaXR5X3BhcnRcIixcclxuICAgIFtcImNvZGVcIiwgXCJuYW1lXCIsIFwiY2l0eV9jb2RlXCJdXHJcbiAgKTtcclxufTtcclxuXHJcbmV4cG9ydCBjb25zdCBpbnNlcnRQcmFndWVEaXN0cmljdHMgPSBhc3luYyAoXHJcbiAgYnVmZmVyOiBzdHJpbmdbXVtdXHJcbik6IFByb21pc2U8bnVtYmVyPiA9PiB7XHJcbiAgcmV0dXJuIGF3YWl0IGluc2VydE11bHRpcGxlUm93cyhcclxuICAgIGV4dHJhY3RLZXlWYWx1ZXNQYWlycyhidWZmZXIsIENvbHVtbi5wcmFndWVEaXN0cmljdENvZGUsIFtcclxuICAgICAgQ29sdW1uLnByYWd1ZURpc3RyaWN0TmFtZSxcclxuICAgIF0pLFxyXG4gICAgXCJwcmFndWVfZGlzdHJpY3RcIixcclxuICAgIFtcImNvZGVcIiwgXCJuYW1lXCJdXHJcbiAgKTtcclxufTtcclxuXHJcbmV4cG9ydCBjb25zdCBpbnNlcnRTdHJlZXRzID0gYXN5bmMgKGJ1ZmZlcjogc3RyaW5nW11bXSk6IFByb21pc2U8bnVtYmVyPiA9PiB7XHJcbiAgcmV0dXJuIGF3YWl0IGluc2VydE11bHRpcGxlUm93cyhcclxuICAgIGV4dHJhY3RLZXlWYWx1ZXNQYWlycyhidWZmZXIsIENvbHVtbi5zdHJlZXRDb2RlLCBbXHJcbiAgICAgIENvbHVtbi5jaXR5Q29kZSxcclxuICAgICAgQ29sdW1uLnN0cmVldE5hbWUsXHJcbiAgICBdKSxcclxuICAgIFwic3RyZWV0XCIsXHJcbiAgICBbXCJjb2RlXCIsIFwiY2l0eV9jb2RlXCIsIFwibmFtZVwiXVxyXG4gICk7XHJcbn07XHJcblxyXG5leHBvcnQgY29uc3QgYXJlQWRkcmVzc1BvaW50c1N5bmNlZCA9IGFzeW5jICgpOiBQcm9taXNlPGJvb2xlYW4+ID0+IHtcclxuICBjb25zdCByZXN1bHQgPSBhd2FpdCBnZXRLbmV4RGIoKVxyXG4gICAgLmNvdW50KFwiKlwiLCB7IGFzOiBcImNvdW50QWxsXCIgfSlcclxuICAgIC5mcm9tKFwiYWRkcmVzc19wb2ludFwiKVxyXG4gICAgLmZpcnN0KCk7XHJcbiAgcmV0dXJuIE51bWJlcihyZXN1bHQuY291bnRBbGwpID49IDI5MDAwMDA7IC8vIHRvdGFsIGlzIGFsbW9zdCAzIG1pbGxpb25cclxufTtcclxuXHJcbmNvbnN0IGFkZHJlc3NQb2ludFNlbGVjdCA9IGBcclxuICBTRUxFQ1QgYS5pZCwgcy5uYW1lIEFTIHN0cmVldF9uYW1lLCBvLm5hbWUgQVMgb2JqZWN0X3R5cGVfbmFtZSwgYS5kZXNjcmlwdGl2ZV9udW1iZXIsXHJcbiAgICAgICAgYS5vcmllbnRhdGlvbmFsX251bWJlciwgYS5vcmllbnRhdGlvbmFsX251bWJlcl9sZXR0ZXIsIGMubmFtZSBBUyBjaXR5X25hbWUsXHJcbiAgICAgICAgbS5uYW1lIEFTIG11bmljaXBhbGl0eV9wYXJ0X25hbWUsIHAubmFtZSBBUyBwcmFndWVfZGlzdHJpY3RfbmFtZSxcclxuICAgICAgICBkLm5hbWUgQVMgZGlzdHJpY3RfbmFtZSwgYS5wb3N0YWxfY29kZSwgYS53Z3M4NF9sYXRpdHVkZSwgYS53Z3M4NF9sb25naXR1ZGVcclxuICBGUk9NIGFkZHJlc3NfcG9pbnQgYVxyXG4gIExFRlQgSk9JTiBzdHJlZXQgcyBPTiBhLnN0cmVldF9jb2RlID0gcy5jb2RlXHJcbiAgSU5ORVIgSk9JTiBvYmplY3RfdHlwZSBvIE9OIG8uaWQgPSBhLm9iamVjdF90eXBlX2lkXHJcbiAgSU5ORVIgSk9JTiBjaXR5IGMgT04gYy5jb2RlID0gYS5jaXR5X2NvZGVcclxuICBMRUZUIEpPSU4gY2l0eV9kaXN0cmljdCBkIE9OIGEuY2l0eV9kaXN0cmljdF9jb2RlID0gZC5jb2RlXHJcbiAgTEVGVCBKT0lOIHByYWd1ZV9kaXN0cmljdCBwIE9OIGEucHJhZ3VlX2Rpc3RyaWN0X2NvZGUgPSBwLmNvZGVcclxuICBMRUZUIEpPSU4gbXVuaWNpcGFsaXR5X3BhcnQgbSBPTiBhLm11bmljaXBhbGl0eV9wYXJ0X2NvZGUgPSBtLmNvZGVgO1xyXG5cclxuZXhwb3J0IGNvbnN0IGdldEFkZHJlc3NQb2ludEJ5SWQgPSBhc3luYyAoXHJcbiAgYWRkcmVzc1BvaW50SWQ6IG51bWJlclxyXG4pOiBQcm9taXNlPEFkZHJlc3NQb2ludCB8IG51bGw+ID0+IHtcclxuICBjb25zdCByb3cgPSBhd2FpdCByYXdRdWVyeShcclxuICAgIGAke2FkZHJlc3NQb2ludFNlbGVjdH1cclxuICAgICAgV0hFUkUgYS5pZCA9ID9gLFxyXG4gICAgW2FkZHJlc3NQb2ludElkXVxyXG4gICk7XHJcbiAgaWYgKHJvdy5sZW5ndGggPT09IDApIHtcclxuICAgIHJldHVybiBudWxsO1xyXG4gIH1cclxuICByZXR1cm4gcm93VG9BZGRyZXNzUG9pbnQocm93WzBdKTtcclxufTtcclxuXHJcbmxldCBsYXN0Rm91bmRlcjogRm91bmRlciB8IG51bGwgPSBudWxsO1xyXG5sZXQgYWxsU3RyZWV0czogc3RyaW5nW10gPSBbXTtcclxuXHJcbmV4cG9ydCBjb25zdCBjaGVja1N0cmVldEV4aXN0cyA9IGFzeW5jIChcclxuICBzdHJlZXROYW1lOiBzdHJpbmcsXHJcbiAgZm91bmRlcjogRm91bmRlclxyXG4pOiBQcm9taXNlPHsgZXhpc3RzOiBib29sZWFuOyBlcnJvcnM6IFNtZEVycm9yW10gfT4gPT4ge1xyXG4gIGNvbnN0IGtuZXggPSBnZXRLbmV4RGIoKTtcclxuICBjb25zdCBlcnJvcnM6IFNtZEVycm9yW10gPSBbXTtcclxuXHJcbiAgLy8gd2UgY2hlY2sgdGhlIHdob2xlIGNpdHlcclxuICBjb25zdCBjaXR5Q29kZSA9IGF3YWl0IGdldEZvdW5kZXJDaXR5Q29kZShcclxuICAgIGZvdW5kZXIubXVuaWNpcGFsaXR5VHlwZSxcclxuICAgIGZvdW5kZXIubXVuaWNpcGFsaXR5Q29kZVxyXG4gICk7XHJcbiAgY29uc3Qgcm93TGlzdCA9IGF3YWl0IHJhd1F1ZXJ5KFxyXG4gICAgYFNFTEVDVCBuYW1lIEFTIHN0cmVldF9uYW1lXHJcbiAgICBGUk9NIHN0cmVldFxyXG4gICAgV0hFUkUgY2l0eV9jb2RlID0gPyBBTkQgbmFtZSA9ID8gICR7XHJcbiAgICAgIGlzU3FsaXRlKGtuZXgpID8gXCJDT0xMQVRFIE5PQ0FTRVwiIDogXCJcIlxyXG4gICAgfWAsXHJcbiAgICBbY2l0eUNvZGUsIHN0cmVldE5hbWVdXHJcbiAgKTtcclxuXHJcbiAgaWYgKHJvd0xpc3QubGVuZ3RoID4gMCkge1xyXG4gICAgY29uc3Qgcm93ID0gcm93TGlzdFswXTtcclxuICAgIGlmIChyb3cuc3RyZWV0X25hbWUgIT09IHN0cmVldE5hbWUpIHtcclxuICAgICAgLy8gZXJyb3JzLnB1c2goXHJcbiAgICAgIC8vICAgYFN0cmVldCAnJHtzdHJlZXROYW1lfScgaGFzIHdyb25nIGNhc2UsIGNvcnJlY3QgY2FzZTogJyR7cm93LnN0cmVldF9uYW1lfScuYFxyXG4gICAgICAvLyApO1xyXG4gICAgfVxyXG4gICAgcmV0dXJuIHsgZXhpc3RzOiB0cnVlLCBlcnJvcnMgfTtcclxuICB9XHJcblxyXG4gIGlmIChsYXN0Rm91bmRlciAhPT0gZm91bmRlcikge1xyXG4gICAgYWxsU3RyZWV0cyA9IGF3YWl0IGdldEFsbFN0cmVldHMoY2l0eUNvZGUpO1xyXG4gICAgbGFzdEZvdW5kZXIgPSBmb3VuZGVyO1xyXG4gIH1cclxuXHJcbiAgY29uc3QgbWF0Y2ggPSBhbGxTdHJlZXRzLmZpbmQoXHJcbiAgICAocykgPT5cclxuICAgICAgcy50b0xvY2FsZUxvd2VyQ2FzZShcImNzLUNaXCIpID09PSBzdHJlZXROYW1lLnRvTG9jYWxlTG93ZXJDYXNlKFwiY3MtQ1pcIilcclxuICApO1xyXG4gIGxldCBleGlzdHMgPSBmYWxzZTtcclxuICBpZiAobWF0Y2gpIHtcclxuICAgIGV4aXN0cyA9IHRydWU7XHJcbiAgICBpZiAoc3RyZWV0TmFtZSAhPT0gbWF0Y2gpIHtcclxuICAgICAgLy8gZXJyb3JzLnB1c2goXHJcbiAgICAgIC8vICAgYFN0cmVldCAnJHtzdHJlZXROYW1lfScgaGFzIHdyb25nIGNhc2UsIGNvcnJlY3QgY2FzZTogJyR7bWF0Y2h9Jy5gXHJcbiAgICAgIC8vICk7XHJcbiAgICB9XHJcbiAgfSBlbHNlIHtcclxuICAgIGNvbnN0IGNsb3Nlc3QgPSBmaW5kQ2xvc2VzdFN0cmluZyhzdHJlZXROYW1lLCBhbGxTdHJlZXRzKTtcclxuICAgIGVycm9ycy5wdXNoKHt