kepler.gl
Version:
kepler.gl is a webgl based application to visualize large scale location data in the browser
669 lines (637 loc) • 74.5 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.SUPPORTED_DUCKDB_DROP_EXTENSIONS = void 0;
exports.castDuckDBTypesForKepler = castDuckDBTypesForKepler;
exports.checkIsSelectQuery = checkIsSelectQuery;
exports.dropTableIfExists = void 0;
exports.getDuckDBColumnTypes = getDuckDBColumnTypes;
exports.getDuckDBColumnTypesMap = getDuckDBColumnTypesMap;
exports.isGeoArrowLineString = isGeoArrowLineString;
exports.isGeoArrowMultiLineString = isGeoArrowMultiLineString;
exports.isGeoArrowMultiPoint = isGeoArrowMultiPoint;
exports.isGeoArrowMultiPolygon = isGeoArrowMultiPolygon;
exports.isGeoArrowPoint = isGeoArrowPoint;
exports.isGeoArrowPolygon = isGeoArrowPolygon;
exports.quoteTableName = quoteTableName;
exports.removeSQLComments = removeSQLComments;
exports.restoreUnsupportedExtensions = exports.restoreArrowTable = exports.removeUnsupportedExtensions = void 0;
exports.sanitizeDuckDBTableName = sanitizeDuckDBTableName;
exports.setGeoArrowWKBExtension = setGeoArrowWKBExtension;
exports.splitSqlStatements = splitSqlStatements;
exports.tableFromFile = tableFromFile;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var arrow = _interopRequireWildcard(require("apache-arrow"));
var _type = require("apache-arrow/type");
var _duckdbWasm = require("@duckdb/duckdb-wasm");
var _constants = require("@kepler.gl/constants");
var _utils = require("@kepler.gl/utils");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
// Copied from loaders.gl/geoarrow
// TODO: Remove isGeoArrow* once Kepler.gl is upgraded to loaders.gl 4.4+
var SUPPORTED_DUCKDB_DROP_EXTENSIONS = exports.SUPPORTED_DUCKDB_DROP_EXTENSIONS = ['arrow', 'csv', 'geojson', 'json', 'parquet'];
/**
* Queries a DuckDB table for the schema description.
* @param connection An active DuckDB connection.
* @param tableName A name of DuckDB table to query.
* @returns An array of column names and DuckDB types.
*/
function getDuckDBColumnTypes(_x, _x2) {
return _getDuckDBColumnTypes.apply(this, arguments);
}
/**
* Generates a mapping of column names to their corresponding DuckDB data types.
* @param columns An array of column descriptions from DuckDB. Check getDuckDBColumnTypes.
* @returns A record where keys are column names and values are their data types.
*/
function _getDuckDBColumnTypes() {
_getDuckDBColumnTypes = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(connection, tableName) {
var quotedTableName, duckDbTypes, resInfo, numRows, columnNames, columnTypes, i, resDescribe, _numRows, _i, _resDescribe$getChild, _resDescribe$getChild2, columnName, columnType, error;
return _regenerator["default"].wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
quotedTableName = quoteTableName(tableName);
duckDbTypes = [];
_context2.prev = 2;
_context2.next = 5;
return connection.query("PRAGMA table_info(".concat(quotedTableName, ")"));
case 5:
resInfo = _context2.sent;
numRows = resInfo.numRows;
columnNames = resInfo.getChild('name');
columnTypes = resInfo.getChild('type');
for (i = 0; i < numRows; ++i) {
duckDbTypes.push({
name: columnNames === null || columnNames === void 0 ? void 0 : columnNames.get(i),
type: columnTypes === null || columnTypes === void 0 ? void 0 : columnTypes.get(i)
});
}
_context2.next = 27;
break;
case 12:
_context2.prev = 12;
_context2.t0 = _context2["catch"](2);
_context2.prev = 14;
_context2.next = 17;
return connection.query("DESCRIBE ".concat(quotedTableName));
case 17:
resDescribe = _context2.sent;
_numRows = resDescribe.numRows;
for (_i = 0; _i < _numRows; ++_i) {
columnName = (_resDescribe$getChild = resDescribe.getChildAt(0)) === null || _resDescribe$getChild === void 0 ? void 0 : _resDescribe$getChild.get(_i);
columnType = (_resDescribe$getChild2 = resDescribe.getChildAt(1)) === null || _resDescribe$getChild2 === void 0 ? void 0 : _resDescribe$getChild2.get(_i);
duckDbTypes.push({
name: columnName,
type: columnType
});
}
_context2.next = 27;
break;
case 22:
_context2.prev = 22;
_context2.t1 = _context2["catch"](14);
error = new Error("[DuckDB] Failed to load column types for ".concat(tableName, " (PRAGMA + DESCRIBE)."));
error.cause = {
primaryError: _context2.t0,
fallbackError: _context2.t1
};
throw error;
case 27:
return _context2.abrupt("return", duckDbTypes);
case 28:
case "end":
return _context2.stop();
}
}, _callee2, null, [[2, 12], [14, 22]]);
}));
return _getDuckDBColumnTypes.apply(this, arguments);
}
function getDuckDBColumnTypesMap(columns) {
return columns.reduce(function (acc, value) {
acc[value.name] = value.type;
return acc;
}, {});
}
/**
* Quotes a table name for safe SQL usage.
* Always quotes to handle all edge cases (spaces, special characters, reserved words).
* For fully qualified names (containing dots), preserves the existing structure.
* @param tableName The table name to quote.
* @returns The table name, properly quoted.
*/
function quoteTableName(tableName) {
// Return as-is if:
// 1. It's already a properly quoted simple identifier (starts and ends with quotes)
// 2. It contains both dots and quotes (assume it's a qualified name)
if (tableName.startsWith('"') && tableName.endsWith('"') || tableName.includes('.') && tableName.includes('"')) {
return tableName;
}
return "\"".concat(tableName.replace(/"/g, '""'), "\"");
}
/**
* Quotes a column name for safe SQL usage.
* Always quotes to handle all edge cases (spaces, special characters, reserved words).
* @param columnName The column name to quote.
* @returns The column name, properly quoted.
*/
function quoteColumnName(columnName) {
return "\"".concat(columnName.replace(/"/g, '""'), "\"");
}
/**
* Constructs an SQL query to select all columns from a given table,
* converting specified columns to Well-Known Binary (WKB) format using ST_AsWKB,
* and casting BIGINT columns to DOUBLE if specified.
* @param tableName The name of the table from which to select data.
* @param columns An array of column descriptors, each with a type and name.
* @param options Optional parameters to control the conversion behavior.
* @returns The constructed SQL query.
*/
function castDuckDBTypesForKepler(tableName, columns) {
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
geometryToWKB: true,
bigIntToDouble: true
};
var modifiedColumns = columns.map(function (column) {
var name = column.name,
type = column.type;
var quotedColumnName = quoteColumnName(name);
if (type === 'GEOMETRY' && options.geometryToWKB) {
return "ST_AsWKB(".concat(quotedColumnName, ") as ").concat(quotedColumnName);
} else if (options.bigIntToDouble && (type === 'BIGINT' || type === 'UBIGINT' || type === 'HUGEINT' || type === 'UHUGEINT' || type.startsWith('DECIMAL'))) {
// Cast 64-bit and larger integer types and DECIMAL to DOUBLE to avoid BigInt in JS
return "CAST(".concat(quotedColumnName, " AS DOUBLE) as ").concat(quotedColumnName);
}
return quotedColumnName;
});
var quotedTableName = quoteTableName(tableName);
return "SELECT ".concat(modifiedColumns.join(', '), " FROM ").concat(quotedTableName);
}
/**
* Sets the GeoArrow WKB extension metadata for columns of type GEOMETRY in an Arrow table.
* @param table The Apache Arrow table whose schema fields will be modified.
* @param columns An array of column descriptors from a DuckDB table.
*/
function setGeoArrowWKBExtension(table, columns) {
table.schema.fields.forEach(function (field) {
var info = columns.find(function (t) {
return t.name === field.name;
});
if ((info === null || info === void 0 ? void 0 : info.type) === 'GEOMETRY') {
field.metadata.set(_constants.GEOARROW_METADATA_KEY, _constants.GEOARROW_EXTENSIONS.WKB);
}
});
}
/**
* Creates an arrow table from an array of arrow vectors and fields.
* @param columns An array of arrow vectors.
* @param fields An array of fields per arrow vector.
* @param arrowSchema Optional arrow table schema when available.
* @returns An arrow table.
*/
var restoreArrowTable = exports.restoreArrowTable = function restoreArrowTable(columns, fields, arrowSchema) {
var creaOpts = {};
fields.map(function (field, index) {
creaOpts[field.name] = columns[index];
});
return arrowSchema ? new arrow.Table(arrowSchema, creaOpts) : new arrow.Table(creaOpts);
};
/**
* DuckDb throws when geoarrow extensions are present in metadata.
* @param table An arrow table to clear from extensions.
* @returns A map of removed per field geoarrow extensions.
*/
var removeUnsupportedExtensions = exports.removeUnsupportedExtensions = function removeUnsupportedExtensions(table) {
var removedMetadata = {};
table.schema.fields.forEach(function (field) {
var extension = field.metadata.get(_constants.GEOARROW_METADATA_KEY);
if (extension !== null && extension !== void 0 && extension.startsWith('geoarrow')) {
removedMetadata[field.name] = extension;
field.metadata["delete"](_constants.GEOARROW_METADATA_KEY);
}
});
return removedMetadata;
};
/**
* Restore removed metadata extensions after a call to removeUnsupportedExtensions.
* @param table An arrow table to restore geoarrow extensions.
* @param removedExtensions A map of per field geoarrow extensions to restore.
*/
var restoreUnsupportedExtensions = exports.restoreUnsupportedExtensions = function restoreUnsupportedExtensions(table, removedExtensions) {
table.schema.fields.forEach(function (field) {
var extension = removedExtensions[field.name];
if (extension) {
field.metadata.set(_constants.GEOARROW_METADATA_KEY, extension);
}
});
};
/** Checks whether the given Apache Arrow JS type is a Point data type */
function isGeoArrowPoint(type) {
if (_type.DataType.isFixedSizeList(type)) {
// Check list size
if (![2, 3, 4].includes(type.listSize)) {
return false;
}
// Check child of FixedSizeList is floating type
if (!_type.DataType.isFloat(type.children[0])) {
return false;
}
return true;
}
return false;
}
/** Checks whether the given Apache Arrow JS type is a Point data type */
function isGeoArrowLineString(type) {
// Check the outer type is a List
if (!_type.DataType.isList(type)) {
return false;
}
// Check the child is a point type
if (!isGeoArrowPoint(type.children[0].type)) {
return false;
}
return true;
}
/** Checks whether the given Apache Arrow JS type is a Polygon data type */
function isGeoArrowPolygon(type) {
// Check the outer vector is a List
if (!_type.DataType.isList(type)) {
return false;
}
// Check the child is a linestring vector
if (!isGeoArrowLineString(type.children[0].type)) {
return false;
}
return true;
}
/** Checks whether the given Apache Arrow JS type is a Polygon data type */
function isGeoArrowMultiPoint(type) {
// Check the outer vector is a List
if (!_type.DataType.isList(type)) {
return false;
}
// Check the child is a point vector
if (!isGeoArrowPoint(type.children[0].type)) {
return false;
}
return true;
}
/** Checks whether the given Apache Arrow JS type is a Polygon data type */
function isGeoArrowMultiLineString(type) {
// Check the outer vector is a List
if (!_type.DataType.isList(type)) {
return false;
}
// Check the child is a linestring vector
if (!isGeoArrowLineString(type.children[0].type)) {
return false;
}
return true;
}
/** Checks whether the given Apache Arrow JS type is a Polygon data type */
function isGeoArrowMultiPolygon(type) {
// Check the outer vector is a List
if (!_type.DataType.isList(type)) {
return false;
}
// Check the child is a polygon vector
if (!isGeoArrowPolygon(type.children[0].type)) {
return false;
}
return true;
}
/**
* Checks if the given SQL query is a SELECT query by using the EXPLAIN command.
* @param connection The DuckDB connection instance.
* @param query The SQL query to check.
* @returns Resolves to `true` if the query is a SELECT statement, otherwise `false`.
*/
function checkIsSelectQuery(_x3, _x4) {
return _checkIsSelectQuery.apply(this, arguments);
}
/**
* Split a string with potentially multiple SQL queries (separated as usual by ';') into an array of queries.
* This implementation:
* - Handles single and double quoted strings with proper escaping
* - Ignores semicolons in line comments (--) and block comments (slash asterisk)
* - Trims whitespace from queries
* - Handles SQL-style escaped quotes ('' inside strings)
* - Returns only non-empty queries
* @param input A string with potentially multiple SQL queries.
* @returns An array of queries.
*/
function _checkIsSelectQuery() {
_checkIsSelectQuery = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3(connection, query) {
var result;
return _regenerator["default"].wrap(function _callee3$(_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
_context3.prev = 0;
_context3.next = 3;
return connection.query("EXPLAIN (".concat(query, ")"));
case 3:
result = _context3.sent;
return _context3.abrupt("return", result.numRows > 0);
case 7:
_context3.prev = 7;
_context3.t0 = _context3["catch"](0);
return _context3.abrupt("return", false);
case 10:
case "end":
return _context3.stop();
}
}, _callee3, null, [[0, 7]]);
}));
return _checkIsSelectQuery.apply(this, arguments);
}
function splitSqlStatements(input) {
var queries = [];
var currentQuery = '';
var inSingleQuote = false;
var inDoubleQuote = false;
var inLineComment = false;
var inBlockComment = false;
for (var i = 0; i < input.length; i++) {
var _char = input[i];
if (inLineComment) {
currentQuery += _char;
if (_char === '\n') {
inLineComment = false;
}
continue;
}
if (inBlockComment) {
currentQuery += _char;
if (_char === '*' && input[i + 1] === '/') {
inBlockComment = false;
currentQuery += input[++i]; // Consume '/'
}
continue;
}
if (inSingleQuote) {
currentQuery += _char;
if (_char === "'") {
// Handle escaped single quotes in SQL
if (i + 1 < input.length && input[i + 1] === "'") {
currentQuery += input[++i];
} else {
inSingleQuote = false;
}
}
continue;
}
if (inDoubleQuote) {
currentQuery += _char;
if (_char === '"') {
// Handle escaped double quotes
if (i + 1 < input.length && input[i + 1] === '"') {
currentQuery += input[++i];
} else {
inDoubleQuote = false;
}
}
continue;
}
// Check for comment starts
if (_char === '-' && input[i + 1] === '-') {
inLineComment = true;
currentQuery += _char + input[++i];
continue;
}
if (_char === '/' && input[i + 1] === '*') {
inBlockComment = true;
currentQuery += _char + input[++i];
continue;
}
// Check for quote starts
if (_char === "'") {
inSingleQuote = true;
currentQuery += _char;
continue;
}
if (_char === '"') {
inDoubleQuote = true;
currentQuery += _char;
continue;
}
// Handle query separator
if (_char === ';') {
var _trimmed = currentQuery.trim();
if (_trimmed.length > 0) {
queries.push(_trimmed);
}
currentQuery = '';
continue;
}
currentQuery += _char;
}
// Add the final query
var trimmed = currentQuery.trim();
if (trimmed.length > 0) {
queries.push(trimmed);
}
return queries;
}
/**
* Removes SQL comments from a given SQL string.
* @param sql The SQL query string from which comments should be removed.
* @returns The cleaned SQL string without comments.
*/
function removeSQLComments(sql) {
// Remove multi-line comments (/* ... */)
sql = sql.replace(/\/\*[\s\S]*?\*\//g, '');
// Remove single-line comments (-- ...)
sql = sql.replace(/--.*$/gm, '');
return sql.trim();
}
/**
* Drops a table if it exists in the DuckDB database.
* @param connection The DuckDB connection instance.
* @param tableName The name of the table to drop.
* @returns A promise that resolves when the operation is complete.
* @throws Logs an error if the table drop operation fails.
*/
var dropTableIfExists = exports.dropTableIfExists = /*#__PURE__*/function () {
var _ref = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(connection, tableName) {
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.prev = 0;
_context.next = 3;
return connection.query("DROP TABLE IF EXISTS \"".concat(tableName, "\";"));
case 3:
_context.next = 8;
break;
case 5:
_context.prev = 5;
_context.t0 = _context["catch"](0);
console.error('Dropping table failed', tableName, _context.t0);
case 8:
case "end":
return _context.stop();
}
}, _callee, null, [[0, 5]]);
}));
return function dropTableIfExists(_x5, _x6) {
return _ref.apply(this, arguments);
};
}();
/**
* Imports a file into DuckDB as a table, supporting multiple formats from SUPPORTED_DUCKDB_DROP_EXTENSIONS.
* @param file The file to be imported.
* @returns A promise that resolves when the file has been processed into a DuckDB table.
*/
function tableFromFile(_x7) {
return _tableFromFile.apply(this, arguments);
}
/**
* Sanitizes a file name to be a valid DuckDB table name.
* @param fileName The input file name to be sanitized.
* @returns A valid DuckDB table name.
*/
function _tableFromFile() {
_tableFromFile = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4(file) {
var fileExt, db, c, error, tableName, sourceName, arrayBuffer, uint8Array, arrowTable, message;
return _regenerator["default"].wrap(function _callee4$(_context4) {
while (1) switch (_context4.prev = _context4.next) {
case 0:
if (file) {
_context4.next = 2;
break;
}
return _context4.abrupt("return", new Error('File Drag & Drop: No file'));
case 2:
fileExt = SUPPORTED_DUCKDB_DROP_EXTENSIONS.find(function (ext) {
return file.name.endsWith(ext);
});
if (fileExt) {
_context4.next = 5;
break;
}
return _context4.abrupt("return", new Error("File Drag & Drop: File extension isn't supported"));
case 5:
_context4.next = 7;
return (0, _utils.getApplicationConfig)().database;
case 7:
db = _context4.sent;
if (db) {
_context4.next = 10;
break;
}
return _context4.abrupt("return", new Error('The database is not configured properly.'));
case 10:
_context4.next = 12;
return db.connect();
case 12:
c = _context4.sent;
error = null;
_context4.prev = 14;
tableName = sanitizeDuckDBTableName(file.name);
sourceName = 'temp_file_handle';
c.query("install spatial;\n load spatial;");
if (!(fileExt === 'arrow')) {
_context4.next = 28;
break;
}
_context4.next = 21;
return file.arrayBuffer();
case 21:
arrayBuffer = _context4.sent;
uint8Array = new Uint8Array(arrayBuffer);
arrowTable = arrow.tableFromIPC(uint8Array);
_context4.next = 26;
return c.insertArrowTable(arrowTable, {
name: tableName
});
case 26:
_context4.next = 48;
break;
case 28:
_context4.next = 30;
return db.registerFileHandle(sourceName, file, _duckdbWasm.DuckDBDataProtocol.BROWSER_FILEREADER, true);
case 30:
if (!(fileExt === 'csv')) {
_context4.next = 35;
break;
}
_context4.next = 33;
return c.query("\n CREATE TABLE '".concat(tableName, "' AS\n SELECT *\n FROM read_csv('").concat(sourceName, "', header = true, auto_detect = true, sample_size = -1);\n "));
case 33:
_context4.next = 48;
break;
case 35:
if (!(fileExt === 'json')) {
_context4.next = 40;
break;
}
_context4.next = 38;
return c.query("\n CREATE TABLE '".concat(tableName, "' AS\n SELECT *\n FROM read_json_auto('").concat(sourceName, "');\n "));
case 38:
_context4.next = 48;
break;
case 40:
if (!(fileExt === 'geojson')) {
_context4.next = 45;
break;
}
_context4.next = 43;
return c.query("\n CREATE TABLE '".concat(tableName, "' AS\n SELECT *\n FROM ST_READ('").concat(sourceName, "', keep_wkb = TRUE);\n "));
case 43:
_context4.next = 48;
break;
case 45:
if (!(fileExt === 'parquet')) {
_context4.next = 48;
break;
}
_context4.next = 48;
return c.query("\n CREATE TABLE '".concat(tableName, "' AS\n SELECT *\n FROM read_parquet('").concat(sourceName, "')\n "));
case 48:
_context4.next = 54;
break;
case 50:
_context4.prev = 50;
_context4.t0 = _context4["catch"](14);
if (_context4.t0 instanceof Error) {
message = _context4.t0.message || ''; // output more readable errors for known issues
if (message.includes('Arrow Type with extension name: geoarrow')) {
error = new Error('The GeoArrow extensions are not implemented in the connected DuckDB version.');
} else if (message.includes("Geoparquet column 'geometry' does not have geometry types")) {
error = new Error("Invalid Input Error: Geoparquet column 'geometry' does not have geometry types.\nPossible reasons:\n - Old .parquet files that don't match the Parquet format specification.\n - Unsupported compression.");
}
}
if (!error) {
error = _context4.t0;
}
case 54:
_context4.next = 56;
return c.close();
case 56:
return _context4.abrupt("return", error);
case 57:
case "end":
return _context4.stop();
}
}, _callee4, null, [[14, 50]]);
}));
return _tableFromFile.apply(this, arguments);
}
function sanitizeDuckDBTableName(fileName) {
// Replace invalid characters with underscores
var name = fileName.replace(/[^a-zA-Z0-9_]/g, '_');
// Ensure it doesn't start with a digit
if (/^\d/.test(name)) {
name = "t_".concat(name);
}
return name || 'default_table';
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJhcnJvdyIsIl9pbnRlcm9wUmVxdWlyZVdpbGRjYXJkIiwicmVxdWlyZSIsIl90eXBlIiwiX2R1Y2tkYldhc20iLCJfY29uc3RhbnRzIiwiX3V0aWxzIiwiX2dldFJlcXVpcmVXaWxkY2FyZENhY2hlIiwiZSIsIldlYWtNYXAiLCJyIiwidCIsIl9fZXNNb2R1bGUiLCJfdHlwZW9mIiwiaGFzIiwiZ2V0IiwibiIsIl9fcHJvdG9fXyIsImEiLCJPYmplY3QiLCJkZWZpbmVQcm9wZXJ0eSIsImdldE93blByb3BlcnR5RGVzY3JpcHRvciIsInUiLCJoYXNPd25Qcm9wZXJ0eSIsImNhbGwiLCJpIiwic2V0IiwiU1VQUE9SVEVEX0RVQ0tEQl9EUk9QX0VYVEVOU0lPTlMiLCJleHBvcnRzIiwiZ2V0RHVja0RCQ29sdW1uVHlwZXMiLCJfeCIsIl94MiIsIl9nZXREdWNrREJDb2x1bW5UeXBlcyIsImFwcGx5IiwiYXJndW1lbnRzIiwiX2FzeW5jVG9HZW5lcmF0b3IyIiwiX3JlZ2VuZXJhdG9yIiwibWFyayIsIl9jYWxsZWUyIiwiY29ubmVjdGlvbiIsInRhYmxlTmFtZSIsInF1b3RlZFRhYmxlTmFtZSIsImR1Y2tEYlR5cGVzIiwicmVzSW5mbyIsIm51bVJvd3MiLCJjb2x1bW5OYW1lcyIsImNvbHVtblR5cGVzIiwicmVzRGVzY3JpYmUiLCJfbnVtUm93cyIsIl9pIiwiX3Jlc0Rlc2NyaWJlJGdldENoaWxkIiwiX3Jlc0Rlc2NyaWJlJGdldENoaWxkMiIsImNvbHVtbk5hbWUiLCJjb2x1bW5UeXBlIiwiZXJyb3IiLCJ3cmFwIiwiX2NhbGxlZTIkIiwiX2NvbnRleHQyIiwicHJldiIsIm5leHQiLCJxdW90ZVRhYmxlTmFtZSIsInF1ZXJ5IiwiY29uY2F0Iiwic2VudCIsImdldENoaWxkIiwicHVzaCIsIm5hbWUiLCJ0eXBlIiwidDAiLCJnZXRDaGlsZEF0IiwidDEiLCJFcnJvciIsImNhdXNlIiwicHJpbWFyeUVycm9yIiwiZmFsbGJhY2tFcnJvciIsImFicnVwdCIsInN0b3AiLCJnZXREdWNrREJDb2x1bW5UeXBlc01hcCIsImNvbHVtbnMiLCJyZWR1Y2UiLCJhY2MiLCJ2YWx1ZSIsInN0YXJ0c1dpdGgiLCJlbmRzV2l0aCIsImluY2x1ZGVzIiwicmVwbGFjZSIsInF1b3RlQ29sdW1uTmFtZSIsImNhc3REdWNrREJUeXBlc0ZvcktlcGxlciIsIm9wdGlvbnMiLCJsZW5ndGgiLCJ1bmRlZmluZWQiLCJnZW9tZXRyeVRvV0tCIiwiYmlnSW50VG9Eb3VibGUiLCJtb2RpZmllZENvbHVtbnMiLCJtYXAiLCJjb2x1bW4iLCJxdW90ZWRDb2x1bW5OYW1lIiwiam9pbiIsInNldEdlb0Fycm93V0tCRXh0ZW5zaW9uIiwidGFibGUiLCJzY2hlbWEiLCJmaWVsZHMiLCJmb3JFYWNoIiwiZmllbGQiLCJpbmZvIiwiZmluZCIsIm1ldGFkYXRhIiwiR0VPQVJST1dfTUVUQURBVEFfS0VZIiwiR0VPQVJST1dfRVhURU5TSU9OUyIsIldLQiIsInJlc3RvcmVBcnJvd1RhYmxlIiwiYXJyb3dTY2hlbWEiLCJjcmVhT3B0cyIsImluZGV4IiwiVGFibGUiLCJyZW1vdmVVbnN1cHBvcnRlZEV4dGVuc2lvbnMiLCJyZW1vdmVkTWV0YWRhdGEiLCJleHRlbnNpb24iLCJyZXN0b3JlVW5zdXBwb3J0ZWRFeHRlbnNpb25zIiwicmVtb3ZlZEV4dGVuc2lvbnMiLCJpc0dlb0Fycm93UG9pbnQiLCJEYXRhVHlwZSIsImlzRml4ZWRTaXplTGlzdCIsImxpc3RTaXplIiwiaXNGbG9hdCIsImNoaWxkcmVuIiwiaXNHZW9BcnJvd0xpbmVTdHJpbmciLCJpc0xpc3QiLCJpc0dlb0Fycm93UG9seWdvbiIsImlzR2VvQXJyb3dNdWx0aVBvaW50IiwiaXNHZW9BcnJvd011bHRpTGluZVN0cmluZyIsImlzR2VvQXJyb3dNdWx0aVBvbHlnb24iLCJjaGVja0lzU2VsZWN0UXVlcnkiLCJfeDMiLCJfeDQiLCJfY2hlY2tJc1NlbGVjdFF1ZXJ5IiwiX2NhbGxlZTMiLCJyZXN1bHQiLCJfY2FsbGVlMyQiLCJfY29udGV4dDMiLCJzcGxpdFNxbFN0YXRlbWVudHMiLCJpbnB1dCIsInF1ZXJpZXMiLCJjdXJyZW50UXVlcnkiLCJpblNpbmdsZVF1b3RlIiwiaW5Eb3VibGVRdW90ZSIsImluTGluZUNvbW1lbnQiLCJpbkJsb2NrQ29tbWVudCIsImNoYXIiLCJ0cmltbWVkIiwidHJpbSIsInJlbW92ZVNRTENvbW1lbnRzIiwic3FsIiwiZHJvcFRhYmxlSWZFeGlzdHMiLCJfcmVmIiwiX2NhbGxlZSIsIl9jYWxsZWUkIiwiX2NvbnRleHQiLCJjb25zb2xlIiwiX3g1IiwiX3g2IiwidGFibGVGcm9tRmlsZSIsIl94NyIsIl90YWJsZUZyb21GaWxlIiwiX2NhbGxlZTQiLCJmaWxlIiwiZmlsZUV4dCIsImRiIiwiYyIsInNvdXJjZU5hbWUiLCJhcnJheUJ1ZmZlciIsInVpbnQ4QXJyYXkiLCJhcnJvd1RhYmxlIiwibWVzc2FnZSIsIl9jYWxsZWU0JCIsIl9jb250ZXh0NCIsImV4dCIsImdldEFwcGxpY2F0aW9uQ29uZmlnIiwiZGF0YWJhc2UiLCJjb25uZWN0Iiwic2FuaXRpemVEdWNrREJUYWJsZU5hbWUiLCJVaW50OEFycmF5IiwidGFibGVGcm9tSVBDIiwiaW5zZXJ0QXJyb3dUYWJsZSIsInJlZ2lzdGVyRmlsZUhhbmRsZSIsIkR1Y2tEQkRhdGFQcm90b2NvbCIsIkJST1dTRVJfRklMRVJFQURFUiIsImNsb3NlIiwiZmlsZU5hbWUiLCJ0ZXN0Il0sInNvdXJjZXMiOlsiLi4vLi4vc3JjL3RhYmxlL2R1Y2tkYi10YWJsZS11dGlscy50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlUXG4vLyBDb3B5cmlnaHQgY29udHJpYnV0b3JzIHRvIHRoZSBrZXBsZXIuZ2wgcHJvamVjdFxuXG4vLyBsb2FkZXJzLmdsXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogTUlUXG4vLyBDb3B5cmlnaHQgKGMpIHZpcy5nbCBjb250cmlidXRvcnNcblxuLy8gQ29waWVkIGZyb20gbG9hZGVycy5nbC9nZW9hcnJvd1xuXG4vLyBUT0RPOiBSZW1vdmUgaXNHZW9BcnJvdyogb25jZSBLZXBsZXIuZ2wgaXMgdXBncmFkZWQgdG8gbG9hZGVycy5nbCA0LjQrXG5cbmltcG9ydCAqIGFzIGFycm93IGZyb20gJ2FwYWNoZS1hcnJvdyc7XG5pbXBvcnQge0RhdGFUeXBlfSBmcm9tICdhcGFjaGUtYXJyb3cvdHlwZSc7XG5pbXBvcnQge0R1Y2tEQkRhdGFQcm90b2NvbH0gZnJvbSAnQGR1Y2tkYi9kdWNrZGItd2FzbSc7XG5cbmltcG9ydCB7R0VPQVJST1dfRVhURU5TSU9OUywgR0VPQVJST1dfTUVUQURBVEFfS0VZfSBmcm9tICdAa2VwbGVyLmdsL2NvbnN0YW50cyc7XG5pbXBvcnQge1Byb3RvRGF0YXNldEZpZWxkfSBmcm9tICdAa2VwbGVyLmdsL3R5cGVzJztcbmltcG9ydCB7RGF0YWJhc2VDb25uZWN0aW9uLCBnZXRBcHBsaWNhdGlvbkNvbmZpZ30gZnJvbSAnQGtlcGxlci5nbC91dGlscyc7XG5cbmV4cG9ydCBjb25zdCBTVVBQT1JURURfRFVDS0RCX0RST1BfRVhURU5TSU9OUyA9IFsnYXJyb3cnLCAnY3N2JywgJ2dlb2pzb24nLCAnanNvbicsICdwYXJxdWV0J107XG5cbmV4cG9ydCB0eXBlIER1Y2tEQkNvbHVtbkRlc2MgPSB7bmFtZTogc3RyaW5nOyB0eXBlOiBzdHJpbmd9O1xuXG4vKipcbiAqIFF1ZXJpZXMgYSBEdWNrREIgdGFibGUgZm9yIHRoZSBzY2hlbWEgZGVzY3JpcHRpb24uXG4gKiBAcGFyYW0gY29ubmVjdGlvbiBBbiBhY3RpdmUgRHVja0RCIGNvbm5lY3Rpb24uXG4gKiBAcGFyYW0gdGFibGVOYW1lIEEgbmFtZSBvZiBEdWNrREIgdGFibGUgdG8gcXVlcnkuXG4gKiBAcmV0dXJucyBBbiBhcnJheSBvZiBjb2x1bW4gbmFtZXMgYW5kIER1Y2tEQiB0eXBlcy5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGdldER1Y2tEQkNvbHVtblR5cGVzKFxuICBjb25uZWN0aW9uOiBEYXRhYmFzZUNvbm5lY3Rpb24sXG4gIHRhYmxlTmFtZTogc3RyaW5nXG4pOiBQcm9taXNlPER1Y2tEQkNvbHVtbkRlc2NbXT4ge1xuICBjb25zdCBxdW90ZWRUYWJsZU5hbWUgPSBxdW90ZVRhYmxlTmFtZSh0YWJsZU5hbWUpO1xuICBjb25zdCBkdWNrRGJUeXBlczogRHVja0RCQ29sdW1uRGVzY1tdID0gW107XG4gIHRyeSB7XG4gICAgLy8gUFJBR01BIHRhYmxlX2luZm8gaXMgbGVzcyBsaWtlbHkgdG8gYmluZC9leGVjdXRlIHZpZXcgU1FMIHRoYW4gREVTQ1JJQkUsXG4gICAgLy8gc28gaXQgYXZvaWRzIHRyaWdnZXJpbmcgcmVtb3RlIGFjY2VzcyAoZS5nLiwgUzMpIGZvciB2aWV3LWJhY2tlZCBzY2hlbWFzLlxuICAgIGNvbnN0IHJlc0luZm8gPSBhd2FpdCBjb25uZWN0aW9uLnF1ZXJ5KGBQUkFHTUEgdGFibGVfaW5mbygke3F1b3RlZFRhYmxlTmFtZX0pYCk7XG4gICAgY29uc3QgbnVtUm93cyA9IHJlc0luZm8ubnVtUm93cztcbiAgICBjb25zdCBjb2x1bW5OYW1lcyA9IHJlc0luZm8uZ2V0Q2hpbGQoJ25hbWUnKTtcbiAgICBjb25zdCBjb2x1bW5UeXBlcyA9IHJlc0luZm8uZ2V0Q2hpbGQoJ3R5cGUnKTtcbiAgICBmb3IgKGxldCBpID0gMDsgaSA8IG51bVJvd3M7ICsraSkge1xuICAgICAgZHVja0RiVHlwZXMucHVzaCh7XG4gICAgICAgIG5hbWU6IGNvbHVtbk5hbWVzPy5nZXQoaSksXG4gICAgICAgIHR5cGU6IGNvbHVtblR5cGVzPy5nZXQoaSlcbiAgICAgIH0pO1xuICAgIH1cbiAgfSBjYXRjaCAocHJpbWFyeUVycm9yKSB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHJlc0Rlc2NyaWJlID0gYXdhaXQgY29ubmVjdGlvbi5xdWVyeShgREVTQ1JJQkUgJHtxdW90ZWRUYWJsZU5hbWV9YCk7XG4gICAgICBjb25zdCBudW1Sb3dzID0gcmVzRGVzY3JpYmUubnVtUm93cztcbiAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgbnVtUm93czsgKytpKSB7XG4gICAgICAgIGNvbnN0IGNvbHVtbk5hbWUgPSByZXNEZXNjcmliZS5nZXRDaGlsZEF0KDApPy5nZXQoaSk7XG4gICAgICAgIGNvbnN0IGNvbHVtblR5cGUgPSByZXNEZXNjcmliZS5nZXRDaGlsZEF0KDEpPy5nZXQoaSk7XG5cbiAgICAgICAgZHVja0RiVHlwZXMucHVzaCh7XG4gICAgICAgICAgbmFtZTogY29sdW1uTmFtZSxcbiAgICAgICAgICB0eXBlOiBjb2x1bW5UeXBlXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH0gY2F0Y2ggKGZhbGxiYWNrRXJyb3IpIHtcbiAgICAgIGNvbnN0IGVycm9yID0gbmV3IEVycm9yKFxuICAgICAgICBgW0R1Y2tEQl0gRmFpbGVkIHRvIGxvYWQgY29sdW1uIHR5cGVzIGZvciAke3RhYmxlTmFtZX0gKFBSQUdNQSArIERFU0NSSUJFKS5gXG4gICAgICApO1xuICAgICAgKGVycm9yIGFzIEVycm9yICYge2NhdXNlPzogdW5rbm93bn0pLmNhdXNlID0ge1xuICAgICAgICBwcmltYXJ5RXJyb3IsXG4gICAgICAgIGZhbGxiYWNrRXJyb3JcbiAgICAgIH07XG4gICAgICB0aHJvdyBlcnJvcjtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gZHVja0RiVHlwZXM7XG59XG5cbi8qKlxuICogR2VuZXJhdGVzIGEgbWFwcGluZyBvZiBjb2x1bW4gbmFtZXMgdG8gdGhlaXIgY29ycmVzcG9uZGluZyBEdWNrREIgZGF0YSB0eXBlcy5cbiAqIEBwYXJhbSBjb2x1bW5zIEFuIGFycmF5IG9mIGNvbHVtbiBkZXNjcmlwdGlvbnMgZnJvbSBEdWNrREIuIENoZWNrIGdldER1Y2tEQkNvbHVtblR5cGVzLlxuICogQHJldHVybnMgQSByZWNvcmQgd2hlcmUga2V5cyBhcmUgY29sdW1uIG5hbWVzIGFuZCB2YWx1ZXMgYXJlIHRoZWlyIGRhdGEgdHlwZXMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXREdWNrREJDb2x1bW5UeXBlc01hcChjb2x1bW5zOiBEdWNrREJDb2x1bW5EZXNjW10pIHtcbiAgcmV0dXJuIGNvbHVtbnMucmVkdWNlKChhY2MsIHZhbHVlKSA9PiB7XG4gICAgYWNjW3ZhbHVlLm5hbWVdID0gdmFsdWUudHlwZTtcbiAgICByZXR1cm4gYWNjO1xuICB9LCB7fSBhcyBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+KTtcbn1cblxuLyoqXG4gKiBRdW90ZXMgYSB0YWJsZSBuYW1lIGZvciBzYWZlIFNRTCB1c2FnZS5cbiAqIEFsd2F5cyBxdW90ZXMgdG8gaGFuZGxlIGFsbCBlZGdlIGNhc2VzIChzcGFjZXMsIHNwZWNpYWwgY2hhcmFjdGVycywgcmVzZXJ2ZWQgd29yZHMpLlxuICogRm9yIGZ1bGx5IHF1YWxpZmllZCBuYW1lcyAoY29udGFpbmluZyBkb3RzKSwgcHJlc2VydmVzIHRoZSBleGlzdGluZyBzdHJ1Y3R1cmUuXG4gKiBAcGFyYW0gdGFibGVOYW1lIFRoZSB0YWJsZSBuYW1lIHRvIHF1b3RlLlxuICogQHJldHVybnMgVGhlIHRhYmxlIG5hbWUsIHByb3Blcmx5IHF1b3RlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHF1b3RlVGFibGVOYW1lKHRhYmxlTmFtZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgLy8gUmV0dXJuIGFzLWlzIGlmOlxuICAvLyAxLiBJdCdzIGFscmVhZHkgYSBwcm9wZXJseSBxdW90ZWQgc2ltcGxlIGlkZW50aWZpZXIgKHN0YXJ0cyBhbmQgZW5kcyB3aXRoIHF1b3RlcylcbiAgLy8gMi4gSXQgY29udGFpbnMgYm90aCBkb3RzIGFuZCBxdW90ZXMgKGFzc3VtZSBpdCdzIGEgcXVhbGlmaWVkIG5hbWUpXG4gIGlmIChcbiAgICAodGFibGVOYW1lLnN0YXJ0c1dpdGgoJ1wiJykgJiYgdGFibGVOYW1lLmVuZHNXaXRoKCdcIicpKSB8fFxuICAgICh0YWJsZU5hbWUuaW5jbHVkZXMoJy4nKSAmJiB0YWJsZU5hbWUuaW5jbHVkZXMoJ1wiJykpXG4gICkge1xuICAgIHJldHVybiB0YWJsZU5hbWU7XG4gIH1cblxuICByZXR1cm4gYFwiJHt0YWJsZU5hbWUucmVwbGFjZSgvXCIvZywgJ1wiXCInKX1cImA7XG59XG5cbi8qKlxuICogUXVvdGVzIGEgY29sdW1uIG5hbWUgZm9yIHNhZmUgU1FMIHVzYWdlLlxuICogQWx3YXlzIHF1b3RlcyB0byBoYW5kbGUgYWxsIGVkZ2UgY2FzZXMgKHNwYWNlcywgc3BlY2lhbCBjaGFyYWN0ZXJzLCByZXNlcnZlZCB3b3JkcykuXG4gKiBAcGFyYW0gY29sdW1uTmFtZSBUaGUgY29sdW1uIG5hbWUgdG8gcXVvdGUuXG4gKiBAcmV0dXJucyBUaGUgY29sdW1uIG5hbWUsIHByb3Blcmx5IHF1b3RlZC5cbiAqL1xuZnVuY3Rpb24gcXVvdGVDb2x1bW5OYW1lKGNvbHVtbk5hbWU6IHN0cmluZyk6IHN0cmluZyB7XG4gIHJldHVybiBgXCIke2NvbHVtbk5hbWUucmVwbGFjZSgvXCIvZywgJ1wiXCInKX1cImA7XG59XG5cbi8qKlxuICogQ29uc3RydWN0cyBhbiBTUUwgcXVlcnkgdG8gc2VsZWN0IGFsbCBjb2x1bW5zIGZyb20gYSBnaXZlbiB0YWJsZSxcbiAqIGNvbnZlcnRpbmcgc3BlY2lmaWVkIGNvbHVtbnMgdG8gV2VsbC1Lbm93biBCaW5hcnkgKFdLQikgZm9ybWF0IHVzaW5nIFNUX0FzV0tCLFxuICogYW5kIGNhc3RpbmcgQklHSU5UIGNvbHVtbnMgdG8gRE9VQkxFIGlmIHNwZWNpZmllZC5cbiAqIEBwYXJhbSB0YWJsZU5hbWUgVGhlIG5hbWUgb2YgdGhlIHRhYmxlIGZyb20gd2hpY2ggdG8gc2VsZWN0IGRhdGEuXG4gKiBAcGFyYW0gY29sdW1ucyBBbiBhcnJheSBvZiBjb2x1bW4gZGVzY3JpcHRvcnMsIGVhY2ggd2l0aCBhIHR5cGUgYW5kIG5hbWUuXG4gKiBAcGFyYW0gb3B0aW9ucyBPcHRpb25hbCBwYXJhbWV0ZXJzIHRvIGNvbnRyb2wgdGhlIGNvbnZlcnNpb24gYmVoYXZpb3IuXG4gKiBAcmV0dXJucyBUaGUgY29uc3RydWN0ZWQgU1FMIHF1ZXJ5LlxuICovXG5leHBvcnQgZnVuY3Rpb24gY2FzdER1Y2tEQlR5cGVzRm9yS2VwbGVyKFxuICB0YWJsZU5hbWU6IHN0cmluZyxcbiAgY29sdW1uczogRHVja0RCQ29sdW1uRGVzY1tdLFxuICBvcHRpb25zID0ge2dlb21ldHJ5VG9XS0I6IHRydWUsIGJpZ0ludFRvRG91YmxlOiB0cnVlfVxuKTogc3RyaW5nIHtcbiAgY29uc3QgbW9kaWZpZWRDb2x1bW5zID0gY29sdW1ucy5tYXAoY29sdW1uID0+IHtcbiAgICBjb25zdCB7bmFtZSwgdHlwZX0gPSBjb2x1bW47XG4gICAgY29uc3QgcXVvdGVkQ29sdW1uTmFtZSA9IHF1b3RlQ29sdW1uTmFtZShuYW1lKTtcbiAgICBpZiAodHlwZSA9PT0gJ0dFT01FVFJZJyAmJiBvcHRpb25zLmdlb21ldHJ5VG9XS0IpIHtcbiAgICAgIHJldHVybiBgU1RfQXNXS0IoJHtxdW90ZWRDb2x1bW5OYW1lfSkgYXMgJHtxdW90ZWRDb2x1bW5OYW1lfWA7XG4gICAgfSBlbHNlIGlmIChcbiAgICAgIG9wdGlvbnMuYmlnSW50VG9Eb3VibGUgJiZcbiAgICAgICh0eXBlID09PSAnQklHSU5UJyB8fFxuICAgICAgICB0eXBlID09PSAnVUJJR0lOVCcgfHxcbiAgICAgICAgdHlwZSA9PT0gJ0hVR0VJTlQnIHx8XG4gICAgICAgIHR5cGUgPT09ICdVSFVHRUlOVCcgfHxcbiAgICAgICAgdHlwZS5zdGFydHNXaXRoKCdERUNJTUFMJykpXG4gICAgKSB7XG4gICAgICAvLyBDYXN0IDY0LWJpdCBhbmQgbGFyZ2VyIGludGVnZXIgdHlwZXMgYW5kIERFQ0lNQUwgdG8gRE9VQkxFIHRvIGF2b2lkIEJpZ0ludCBpbiBKU1xuICAgICAgcmV0dXJuIGBDQVNUKCR7cXVvdGVkQ29sdW1uTmFtZX0gQVMgRE9VQkxFKSBhcyAke3F1b3RlZENvbHVtbk5hbWV9YDtcbiAgICB9XG4gICAgcmV0dXJuIHF1b3RlZENvbHVtbk5hbWU7XG4gIH0pO1xuXG4gIGNvbnN0IHF1b3RlZFRhYmxlTmFtZSA9IHF1b3RlVGFibGVOYW1lKHRhYmxlTmFtZSk7XG4gIHJldHVybiBgU0VMRUNUICR7bW9kaWZpZWRDb2x1bW5zLmpvaW4oJywgJyl9IEZST00gJHtxdW90ZWRUYWJsZU5hbWV9YDtcbn1cblxuLyoqXG4gKiBTZXRzIHRoZSBHZW9BcnJvdyBXS0IgZXh0ZW5zaW9uIG1ldGFkYXRhIGZvciBjb2x1bW5zIG9mIHR5cGUgR0VPTUVUUlkgaW4gYW4gQXJyb3cgdGFibGUuXG4gKiBAcGFyYW0gdGFibGUgVGhlIEFwYWNoZSBBcnJvdyB0YWJsZSB3aG9zZSBzY2hlbWEgZmllbGRzIHdpbGwgYmUgbW9kaWZpZWQuXG4gKiBAcGFyYW0gY29sdW1ucyBBbiBhcnJheSBvZiBjb2x1bW4gZGVzY3JpcHRvcnMgZnJvbSBhIER1Y2tEQiB0YWJsZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNldEdlb0Fycm93V0tCRXh0ZW5zaW9uKHRhYmxlOiBhcnJvdy5UYWJsZSwgY29sdW1uczogRHVja0RCQ29sdW1uRGVzY1tdKSB7XG4gIHRhYmxlLnNjaGVtYS5maWVsZHMuZm9yRWFjaChmaWVsZCA9PiB7XG4gICAgY29uc3QgaW5mbyA9IGNvbHVtbnMuZmluZCh0ID0+IHQubmFtZSA9PT0gZmllbGQubmFtZSk7XG4gICAgaWYgKGluZm8/LnR5cGUgPT09ICdHRU9NRVRSWScpIHtcbiAgICAgIGZpZWxkLm1ldGFkYXRhLnNldChHRU9BUlJPV19NRVRBREFUQV9LRVksIEdFT0FSUk9XX0VYVEVOU0lPTlMuV0tCKTtcbiAgICB9XG4gIH0pO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYW4gYXJyb3cgdGFibGUgZnJvbSBhbiBhcnJheSBvZiBhcnJvdyB2ZWN0b3JzIGFuZCBmaWVsZHMuXG4gKiBAcGFyYW0gY29sdW1ucyBBbiBhcnJheSBvZiBhcnJvdyB2ZWN0b3JzLlxuICogQHBhcmFtIGZpZWxkcyBBbiBhcnJheSBvZiBmaWVsZHMgcGVyIGFycm93IHZlY3Rvci5cbiAqIEBwYXJhbSBhcnJvd1NjaGVtYSBPcHRpb25hbCBhcnJvdyB0YWJsZSBzY2hlbWEgd2hlbiBhdmFpbGFibGUuXG4gKiBAcmV0dXJucyBBbiBhcnJvdyB0YWJsZS5cbiAqL1xuZXhwb3J0IGNvbnN0IHJlc3RvcmVBcnJvd1RhYmxlID0gKFxuICBjb2x1bW5zOiBhcnJvdy5WZWN0b3JbXSxcbiAgZmllbGRzOiBQcm90b0RhdGFzZXRGaWVsZFtdLFxuICBhcnJvd1NjaGVtYT86IGFycm93LlNjaGVtYVxuKSA9PiB7XG4gIGNvbnN0IGNyZWFPcHRzID0ge307XG4gIGZpZWxkcy5tYXAoKGZpZWxkLCBpbmRleCkgPT4ge1xuICAgIGNyZWFPcHRzW2ZpZWxkLm5hbWVdID0gY29sdW1uc1tpbmRleF07XG4gIH0pO1xuXG4gIHJldHVybiBhcnJvd1NjaGVtYSA/IG5ldyBhcnJvdy5UYWJsZShhcnJvd1NjaGVtYSwgY3JlYU9wdHMpIDogbmV3IGFycm93LlRhYmxlKGNyZWFPcHRzKTtcbn07XG5cbi8qKlxuICogRHVja0RiIHRocm93cyB3aGVuIGdlb2Fycm93IGV4dGVuc2lvbnMgYXJlIHByZXNlbnQgaW4gbWV0YWRhdGEuXG4gKiBAcGFyYW0gdGFibGUgQW4gYXJyb3cgdGFibGUgdG8gY2xlYXIgZnJvbSBleHRlbnNpb25zLlxuICogQHJldHVybnMgQSBtYXAgb2YgcmVtb3ZlZCBwZXIgZmllbGQgZ2VvYXJyb3cgZXh0ZW5zaW9ucy5cbiAqL1xuZXhwb3J0IGNvbnN0IHJlbW92ZVVuc3VwcG9ydGVkRXh0ZW5zaW9ucyA9ICh0YWJsZTogYXJyb3cuVGFibGUpOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0+IHtcbiAgY29uc3QgcmVtb3ZlZE1ldGFkYXRhOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307XG4gIHRhYmxlLnNjaGVtYS5maWVsZHMuZm9yRWFjaChmaWVsZCA9PiB7XG4gICAgY29uc3QgZXh0ZW5zaW9uID0gZmllbGQubWV0YWRhdGEuZ2V0KEdFT0FSUk9XX01FVEFEQVRBX0tFWSk7XG4gICAgaWYgKGV4dGVuc2lvbj8uc3RhcnRzV2l0aCgnZ2VvYXJyb3cnKSkge1xuICAgICAgcmVtb3ZlZE1ldGFkYXRhW2ZpZWxkLm5hbWVdID0gZXh0ZW5zaW9uO1xuICAgICAgZmllbGQubWV0YWRhdGEuZGVsZXRlKEdFT0FSUk9XX01FVEFEQVRBX0tFWSk7XG4gICAgfVxuICB9KTtcbiAgcmV0dXJuIHJlbW92ZWRNZXRhZGF0YTtcbn07XG5cbi8qKlxuICogUmVzdG9yZSByZW1vdmVkIG1ldGFkYXRhIGV4dGVuc2lvbnMgYWZ0ZXIgYSBjYWxsIHRvIHJlbW92ZVVuc3VwcG9ydGVkRXh0ZW5zaW9ucy5cbiAqIEBwYXJhbSB0YWJsZSBBbiBhcnJvdyB0YWJsZSB0byByZXN0b3JlIGdlb2Fycm93IGV4dGVuc2lvbnMuXG4gKiBAcGFyYW0gcmVtb3ZlZEV4dGVuc2lvbnMgQSBtYXAgb2YgcGVyIGZpZWxkIGdlb2Fycm93IGV4dGVuc2lvbnMgdG8gcmVzdG9yZS5cbiAqL1xuZXhwb3J0IGNvbnN0IHJlc3RvcmVVbnN1cHBvcnRlZEV4dGVuc2lvbnMgPSAoXG4gIHRhYmxlOiBhcnJvdy5UYWJsZSxcbiAgcmVtb3ZlZEV4dGVuc2lvbnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz5cbikgPT4ge1xuICB0YWJsZS5zY2hlbWEuZmllbGRzLmZvckVhY2goZmllbGQgPT4ge1xuICAgIGNvbnN0IGV4dGVuc2lvbiA9IHJlbW92ZWRFeHRlbnNpb25zW2ZpZWxkLm5hbWVdO1xuICAgIGlmIChleHRlbnNpb24pIHtcbiAgICAgIGZpZWxkLm1ldGFkYXRhLnNldChHRU9BUlJPV19NRVRBREFUQV9LRVksIGV4dGVuc2lvbik7XG4gICAgfVxuICB9KTtcbn07XG5cbi8qKiBDaGVja3Mgd2hldGhlciB0aGUgZ2l2ZW4gQXBhY2hlIEFycm93IEpTIHR5cGUgaXMgYSBQb2ludCBkYXRhIHR5cGUgKi9cbmV4cG9ydCBmdW5jdGlvbiBpc0dlb0Fycm93UG9pbnQodHlwZTogRGF0YVR5cGUpIHtcbiAgaWYgKERhdGFUeXBlLmlzRml4ZWRTaXplTGlzdCh0eXBlKSkge1xuICAgIC8vIENoZWNrIGxpc3Qgc2l6ZVxuICAgIGlmICghWzIsIDMsIDRdLmluY2x1ZGVzKHR5cGUubGlzdFNpemUpKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgLy8gQ2hlY2sgY2hpbGQgb2YgRml4ZWRTaXplTGlzdCBpcyBmbG9hdGluZyB0eXBlXG4gICAgaWYgKCFEYXRhVHlwZS5pc0Zsb2F0KHR5cGUuY2hpbGRyZW5bMF0pKSB7XG4gICAgICByZXR1cm4gZmFsc2U7XG4gICAgfVxuXG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICByZXR1cm4gZmFsc2U7XG59XG5cbi8qKiBDaGVja3Mgd2hldGhlciB0aGUgZ2l2ZW4gQXBhY2hlIEFycm93IEpTIHR5cGUgaXMgYSBQb2ludCBkYXRhIHR5cGUgKi9cbmV4cG9ydCBmdW5jdGlvbiBpc0dlb0Fycm93TGluZVN0cmluZyh0eXBlOiBEYXRhVHlwZSkge1xuICAvLyBDaGVjayB0aGUgb3V0ZXIgdHlwZSBpcyBhIExpc3RcbiAgaWYgKCFEYXRhVHlwZS5pc0xpc3QodHlwZSkpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICAvLyBDaGVjayB0aGUgY2hpbGQgaXMgYSBwb2ludCB0eXBlXG4gIGlmICghaXNHZW9BcnJvd1BvaW50KHR5cGUuY2hpbGRyZW5bMF0udHlwZSkpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn1cblxuLyoqIENoZWNrcyB3aGV0aGVyIHRoZSBnaXZlbiBBcGFjaGUgQXJyb3cgSlMgdHlwZSBpcyBhIFBvbHlnb24gZGF0YSB0eXBlICovXG5leHBvcnQgZnVuY3Rpb24gaXNHZW9BcnJvd1BvbHlnb24odHlwZTogRGF0YVR5cGUpIHtcbiAgLy8gQ2hlY2sgdGhlIG91dGVyIHZlY3RvciBpcyBhIExpc3RcbiAgaWYgKCFEYXRhVHlwZS5pc0xpc3QodHlwZSkpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICAvLyBDaGVjayB0aGUgY2hpbGQgaXMgYSBsaW5lc3RyaW5nIHZlY3RvclxuICBpZiAoIWlzR2VvQXJyb3dMaW5lU3RyaW5nKHR5cGUuY2hpbGRyZW5bMF0udHlwZSkpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn1cblxuLyoqIENoZWNrcyB3aGV0aGVyIHRoZSBnaXZlbiBBcGFjaGUgQXJyb3cgSlMgdHlwZSBpcyBhIFBvbHlnb24gZGF0YSB0eXBlICovXG5leHBvcnQgZnVuY3Rpb24gaXNHZW9BcnJvd011bHRpUG9pbnQodHlwZTogRGF0YVR5cGUpIHtcbiAgLy8gQ2hlY2sgdGhlIG91dGVyIHZlY3RvciBpcyBhIExpc3RcbiAgaWYgKCFEYXRhVHlwZS5pc0xpc3QodHlwZSkpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICAvLyBDaGVjayB0aGUgY2hpbGQgaXMgYSBwb2ludCB2ZWN0b3JcbiAgaWYgKCFpc0dlb0Fycm93UG9pbnQodHlwZS5jaGlsZHJlblswXS50eXBlKSkge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIHJldHVybiB0cnVlO1xufVxuXG4vKiogQ2hlY2tzIHdoZXRoZXIgdGhlIGdpdmVuIEFwYWNoZSBBcnJvdyBKUyB0eXBlIGlzIGEgUG9seWdvbiBkYXRhIHR5cGUgKi9cbmV4cG9ydCBmdW5jdGlvbiBpc0dlb0Fycm93TXVsdGlMaW5lU3RyaW5nKHR5cGU6IERhdGFUeXBlKSB7XG4gIC8vIENoZWNrIHRoZSBvdXRlciB2ZWN0b3IgaXMgYSBMaXN0XG4gIGlmICghRGF0YVR5cGUuaXNMaXN0KHR5cGUpKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgLy8gQ2hlY2sgdGhlIGNoaWxkIGlzIGEgbGluZXN0cmluZyB2ZWN0b3JcbiAgaWYgKCFpc0dlb0Fycm93TGluZVN0cmluZyh0eXBlLmNoaWxkcmVuWzBdLnR5cGUpKSB7XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgcmV0dXJuIHRydWU7XG59XG5cbi8qKiBDaGVja3Mgd2hldGhlciB0aGUgZ2l2ZW4gQXBhY2hlIEFycm93IEpTIHR5cGUgaXMgYSBQb2x5Z29uIGRhdGEgdHlwZSAqL1xuZXhwb3J0IGZ1bmN0aW9uIGlzR2VvQXJyb3dNdWx0aVBvbHlnb24odHlwZTogRGF0YVR5cGUpIHtcbiAgLy8gQ2hlY2sgdGhlIG91dGVyIHZlY3RvciBpcyBhIExpc3RcbiAgaWYgKCFEYXRhVHlwZS5pc0xpc3QodHlwZSkpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICAvLyBDaGVjayB0aGUgY2hpbGQgaXMgYSBwb2x5Z29uIHZlY3RvclxuICBpZiAoIWlzR2VvQXJyb3dQb2x5Z29uKHR5cGUuY2hpbGRyZW5bMF0udHlwZSkpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cblxuICByZXR1cm4gdHJ1ZTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgdGhlIGdpdmVuIFNRTCBxdWVyeSBpcyBhIFNFTEVDVCBxdWVyeSBieSB1c2luZyB0aGUgRVhQTEFJTiBjb21tYW5kLlxuICogQHBhcmFtIGNvbm5lY3Rpb24gVGhlIER1Y2tEQiBjb25uZWN0aW9uIGluc3RhbmNlLlxuICogQHBhcmFtIHF1ZXJ5IFRoZSBTUUwgcXVlcnkgdG8gY2hlY2suXG4gKiBAcmV0dXJucyBSZXNvbHZlcyB0byBgdHJ1ZWAgaWYgdGhlIHF1ZXJ5IGlzIGEgU0VMRUNUIHN0YXRlbWVudCwgb3RoZXJ3aXNlIGBmYWxzZWAuXG4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjaGVja0lzU2VsZWN0UXVlcnkoXG4gIGNvbm5lY3Rpb246IERhdGFiYXNlQ29ubmVjdGlvbixcbiAgcXVlcnk6IHN0cmluZ1xuKTogUHJvbWlzZTxib29sZWFuPiB7XG4gIHRyeSB7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgY29ubmVjdGlvbi5xdWVyeShgRVhQTEFJTiAoJHtxdWVyeX0pYCk7XG4gICAgcmV0dXJuIHJlc3VsdC5udW1Sb3dzID4gMDtcbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICByZXR1cm4gZmFsc2U7XG4gIH1cbn1cblxuLyoqXG4gKiBTcGxpdCBhIHN0cmluZyB3aXRoIHBvdGVudGlhbGx5IG11bHRpcGxlIFNRTCBxdWVyaWVzIChzZXBhcmF0ZWQgYXMgdXN1YWwgYnkgJzsnKSBpbnRvIGFuIGFycmF5IG9mIHF1ZXJpZXMuXG4gKiBUaGlzIGltcGxlbWVudGF0aW9uOlxuICogIC0gSGFuZGxlcyBzaW5nbGUgYW5kIGRvdWJsZSBxdW90ZWQgc3RyaW5ncyB3aXRoIHByb3BlciBlc2NhcGluZ1xuICogIC0gSWdub3JlcyBzZW1pY29sb25zIGluIGxpbmUgY29tbWVudHMgKC0tKSBhbmQgYmxvY2sgY29tbWVudHMgKHNsYXNoIGFzdGVyaXNrKVxuICogIC0gVHJpbXMgd2hpdGVzcGFjZSBmcm9tIHF1ZXJpZXNcbiAqICAtIEhhbmRsZXMgU1FMLXN0eWxlIGVzY2FwZWQgcXVvdGVzICgnJyBpbnNpZGUgc3RyaW5ncylcbiAqICAtIFJldHVybnMgb25seSBub24tZW1wdHkgcXVlcmllc1xuICogQHBhcmFtIGlucHV0IEEgc3RyaW5nIHdpdGggcG90ZW50aWFsbHkgbXVsdGlwbGUgU1FMIHF1ZXJpZXMuXG4gKiBAcmV0dXJucyBBbiBhcnJheSBvZiBxdWVyaWVzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc3BsaXRTcWxTdGF0ZW1lbnRzKGlucHV0OiBzdHJpbmcpOiBzdHJpbmdbXSB7XG4gIGNvbnN0IHF1ZXJpZXM6IHN0cmluZ1tdID0gW107XG4gIGxldCBjdXJyZW50UXVlcnkgPSAnJztcbiAgbGV0IGluU2luZ2xlUXVvdGUgPSBmYWxzZTtcbiAgbGV0IGluRG91YmxlUXVvdGUgPSBmYWxzZTtcbiAgbGV0IGluTGluZUNvbW1lbnQgPSBmYWxzZTtcbiAgbGV0IGluQmxvY2tDb21tZW50ID0gZmFsc2U7XG5cbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBpbnB1dC5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IGNoYXIgPSBpbnB1dFtpXTtcblxuICAgIGlmIChpbkxpbmVDb21tZW50KSB7XG4gICAgICBjdXJyZW50UXVlcnkgKz0gY2hhcjtcbiAgICAgIGlmIChjaGFyID09PSAnXFxuJykge1xuICAgICAgICBpbkxpbmVDb21tZW50ID0gZmFsc2U7XG4gICAgICB9XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBpZiAoaW5CbG9ja0NvbW1lbnQpIHtcbiAgICAgIGN1cnJlbnRRdWVyeSArPSBjaGFyO1xuICAgICAgaWYgKGNoYXIgPT09ICcqJyAmJiBpbnB1dFtpICsgMV0gPT09ICcvJykge1xuICAgICAgICBpbkJsb2NrQ29tbWVudCA9IGZhbHNlO1xuICAgICAgICBjdXJyZW50UXVlcnkgKz0gaW5wdXRbKytpXTsgLy8gQ29uc3VtZSAnLydcbiAgICAgIH1cbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChpblNpbmdsZVF1b3RlKSB7XG4gICAgICBjdXJyZW50UXVlcnkgKz0gY2hhcjtcbiAgICAgIGlmIChjaGFyID09PSBcIidcIikge1xuICAgICAgICAvLyBIYW5kbGUgZXNjYXBlZCBzaW5nbGUgcXVvdGVzIGluIFNRTFxuICAgICAgICBpZiAoaSArIDEgPCBpbnB1dC5sZW5ndGggJiYgaW5wdXRbaSArIDFdID09PSBcIidcIikge1xuICAgICAgICAgIGN1cnJlbnRRdWVyeSArPSBpbnB1dFsrK2ldO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIGluU2luZ2xlUXVvdGUgPSBmYWxzZTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgY29udGludWU7XG4gICAgfVxuXG4gICAgaWYgKGluRG91YmxlUXVvdGUpIHtcbiAgICAgIGN1cnJlbnRRdWVyeSArPSBjaGFyO1xuICAgICAgaWYgKGNoYXIgPT09ICdcIicpIHtcbiAgICAgICAgLy8gSGFuZGxlIGVzY2FwZWQgZG91YmxlIHF1b3Rlc1xuICAgICAgICBpZiAoaSArIDEgPCBpbnB1dC5sZW5ndGggJiYgaW5wdXRbaSArIDFdID09PSAnXCInKSB7XG4gICAgICAgICAgY3VycmVudFF1ZXJ5ICs9IGlucHV0WysraV07XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgaW5Eb3VibGVRdW90ZSA9IGZhbHNlO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICAvLyBDaGVjayBmb3IgY29tbWVudCBzdGFydHNcbiAgICBpZiAoY2hhciA9PT0gJy0nICYmIGlucHV0W2kgKyAxXSA9PT0gJy0nKSB7XG4gICAgICBpbkxpbmVDb21tZW50ID0gdHJ1ZTtcbiAgICAgIGN1cnJlbnRRdWVyeSArPSBjaGFyICsgaW5wdXRbKytpXTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGlmIChjaGFyID09PSAnLycgJiYgaW5wdXRbaSArIDFdID09PSAnKicpIHtcbiAgICAgIGluQmxvY2tDb21tZW50ID0gdHJ1ZTtcbiAgICAgIGN1cnJlbnRRdWVyeSArPSBjaGFyICsgaW5wdXRbKytpXTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIC8vIENoZWNrIGZvciBxdW90ZSBzdGFydHNcbiAgICBpZiAoY2hhciA9PT0gXCInXCIpIHtcbiAgICAgIGluU2luZ2xlUXVvdGUgPSB0cnVlO1xuICAgICAgY3VycmVudFF1ZXJ5ICs9IGNoYXI7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBpZiAoY2hhciA9PT0gJ1wiJykge1xuICAgICAgaW5Eb3VibGVRdW90ZSA9IHRydWU7XG4gICAgICBjdXJyZW50UXVlcnkgKz0gY2hhcjtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIC8vIEhhbmRsZSBxdWVyeSBzZXBhcmF0b3JcbiAgICBpZiAoY2hhciA9PT0gJzsnKSB7XG4gICAgICBjb25zdCB0cmltbWVkID0gY3VycmVudFF1ZXJ5LnRyaW0oKTtcbiAgICAgIGlmICh0cmltbWVkLmxlbmd0aCA+IDApIHtcbiAgICAgICAgcXVlcmllcy5wdXNoKHRyaW1tZWQpO1xuICAgICAgfVxuICAgICAgY3VycmVudFF1ZXJ5ID0gJyc7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBjdXJyZW50UXVlcnkgKz0gY2hhcjtcbiAgfVxuXG4gIC8vIEFkZCB0aGUgZmluYWwgcXVlcnlcbiAgY29uc3QgdHJpbW1lZCA9IGN1cnJlbnRRdWVyeS50cmltKCk7XG4gIGlmICh0cmltbWVkLmxlbmd0aCA+IDApIHtcbiAgICBxdWVyaWVzLnB1c2godHJpbW1lZCk7XG4gIH1cblxuICByZXR1cm4gcXVlcmllcztcbn1cblxuLyoqXG4gKiBSZW1vdmVzIFNRTCBjb21tZW50cyBmcm9tIGEgZ2l2ZW4gU1FMIHN0cmluZy5cbiAqIEBwYXJhbSBzcWwgVGhlIFNRTCBxdWVyeSBzdHJpbmcgZnJvbSB3aGljaCBjb21tZW50cyBzaG91bGQgYmUgcmVtb3ZlZC5cbiAqIEByZXR1cm5zIFRoZSBjbGVhbmVkIFNRTCBzdHJpbmcgd2l0aG91dCBjb21tZW50cy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlbW92ZVNRTENvbW1lbnRzKHNxbDogc3RyaW5nKTogc3RyaW5nIHtcbiAgLy8gUmVtb3ZlIG11bHRpLWxpbmUgY29tbWVudHMgKC8qIC4uLiAqLylcbiAgc3FsID0gc3FsLnJlcGxhY2UoL1xcL1xcKltcXHNcXFNdKj9cXCpcXC8vZywgJycpO1xuICAvLyBSZW1vdmUgc2luZ2xlLWxpbmUgY29tbWVudHMgKC0tIC4uLilcbiAgc3FsID0gc3FsLnJlcGxhY2UoLy0tLiokL2dtLCAnJyk7XG4gIHJldHVybiBzcWwudHJpbSgpO1xufVxuXG4vKipcbiAqIERyb3BzIGEgdGFibGUgaWYgaXQgZXhpc3RzIGluIHRoZSBEdWNrREIgZGF0YWJhc2UuXG4gKiBAcGFyYW0gY29ubmVjdGlvbiBUaGUgRHVja0RCIGNvbm5lY3Rpb24gaW5zdGFuY2UuXG4gKiBAcGFyYW0gdGFibGVOYW1lIFRoZSBuYW1lIG9mIHRoZSB0YWJsZSB0byBkcm9wLlxuICogQHJldHVybnMgQSBwcm9taXNlIHRoYXQgcmVzb2x2ZXMgd2hlbiB0aGUgb3BlcmF0aW9uIGlzIGNvbXBsZXRlLlxuICogQHRocm93cyBMb2dzIGFuIGVycm9yIGlmIHRoZSB0YWJsZSBkcm9wIG9wZXJhdGlvbiBmYWlscy5cbiAqL1xuZXhwb3J0IGNvbnN0IGRyb3BUYWJsZUlmRXhpc3RzID0gYXN5bmMgKGNvbm5lY3Rpb246IERhdGFiYXNlQ29ubmVjdGlvbiwgdGFibGVOYW1lOiBzdHJpbmcpID0+IHtcbiAgdHJ5IHtcbiAgICBhd2FpdCBjb25uZWN0aW9uLnF1ZXJ5KGBEUk9QIFRBQkxFIElGIEVYSVNUUyBcIiR7dGFibGVOYW1lfVwiO2ApO1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIGNvbnNvbGUuZXJyb3IoJ0Ryb3BwaW5nIHRhYmxlIGZhaWxlZCcsIHRhYmxlTmFtZSwgZXJyb3IpO1xuICB9XG59O1xuXG4vKipcbiAqIEltcG9ydHMgYSBmaWxlIGludG8gRHVja0RCIGFzIGEgdGFibGUsIHN1cHBvcnRpbmcgbXVsdGlwbGUgZm9ybWF0cyBmcm9tIFNVUFBPUlRFRF9EVUNLREJfRFJPUF9FWFRFTlNJT05TLlxuICogQHBhcmFtIGZpbGUgVGhlIGZpbGUgdG8gYmUgaW1wb3J0ZWQuXG4gKiBAcmV0dXJucyBBIHByb21pc2UgdGhhdCByZXNvbHZlcyB3aGVuIHRoZSBmaWxlIGhhcyBiZWVuIHByb2Nlc3NlZCBpbnRvIGEgRHVja0RCIHRhYmxlLlxuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gdGFibGVGcm9tRmlsZShmaWxlOiBGaWxlIHwgbnVsbCk6IFByb21pc2U8bnVsbCB8IEVycm9yPiB7XG4gIGlmICghZmlsZSkgcmV0dXJuIG5ldyBFcnJvcignRmlsZSBEcmFnICYgRHJvcDogTm8gZmlsZScpO1xuXG4gIGNvbnN0IGZpbGVFeHQgPSBTVVBQT1JURURfRFVDS0RCX0RST1BfRVhURU5TSU9OUy5maW5kKGV4dCA9PiBmaWxlLm5hbWUuZW5kc1dpdGgoZXh0KSk7XG4gIGlmICghZmlsZUV4dCkge1xuICAgIHJldHVybiBuZXcgRXJyb3IoXCJGaWxlIERyYWcgJiBEcm9wOiBGaWxlIGV4dGVuc2lvbiBpc24ndCBzdXBwb3J0ZWRcIik7XG4gIH1cblxuICBjb25zdCBkYiA9IGF3YWl0IGdldEFwcGxpY2F0aW9uQ29uZmlnKCkuZGF0YWJhc2U7XG4gIGlmICghZGIpIHtcbiAgICByZXR1cm4gbmV3IEVycm9yKCdUaGUgZGF0YWJhc2UgaXMgbm90IGNvbmZpZ3VyZWQgcHJvcGVybHkuJyk7XG4gIH1cbiAgY29uc3QgYyA9IGF3YWl0IGRiLmNvbm5lY3QoKTtcblxuICBsZXQgZXJyb3I6IEVycm9yIHwgbnVsbCA9IG51bGw7XG5cbiAgdHJ5IHtcbiAgICBjb25zdCB0YWJsZU5hbWUgPSBzYW5pdGl6ZUR1Y2tEQlRhYmxlTmFtZShmaWxlLm5hbWUpO1xuICAgIGNvbnN0IHNvdXJjZU5hbWUgPSAndGVtcF9maWxlX2hhbmRsZSc7XG5cbiAgICBjLnF1ZXJ5KGBpbnN0YWxsIHNwYXRpYWw7XG4gICAgICBsb2FkIHNwYXRpYWw7YCk7XG5cbiAgICBpZiAoZmlsZUV4dCA9PT0gJ2Fycm93Jykge1xuICAgICAgY29uc3QgYXJyYXlCdWZmZXIgPSBhd2FpdCBmaWxlLmFycmF5QnVmZmVyKCk7XG4gICAgICBjb25zdCB1aW50OEFycmF5ID0gbmV3IFVpbnQ4QXJyYXkoYXJyYXlCdWZmZXIpO1xuICAgICAgY29uc3QgYXJyb3dUYWJsZSA9IGFycm93LnRhYmxlRnJvbUlQQyh1aW50OEFycmF5KTtcblxuICAgICAgYXdhaXQgYy5pbnNlcnRBcnJvd1RhYmxlKGFycm93VGFibGUsIHtuYW1lOiB0