UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

549 lines (528 loc) 70.3 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.Processors = exports.PARSE_FIELD_VALUE_FROM_STRING = exports.DATASET_HANDLERS = exports.CSV_NULLS = void 0; exports.arrowSchemaToFields = arrowSchemaToFields; exports.getGeoArrowMetadataFromSchema = getGeoArrowMetadataFromSchema; exports.parseCsvRowsByFieldType = parseCsvRowsByFieldType; exports.parseRowsByFields = parseRowsByFields; exports.processArrowBatches = processArrowBatches; exports.processArrowTable = processArrowTable; exports.processCsvData = processCsvData; exports.processGeojson = processGeojson; exports.processKeplerglDataset = processKeplerglDataset; exports.processKeplerglJSON = processKeplerglJSON; exports.processRowObject = processRowObject; var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var arrow = _interopRequireWildcard(require("apache-arrow")); var _d3Dsv = require("d3-dsv"); var _typeAnalyzer = require("type-analyzer"); var _geojsonNormalize = _interopRequireDefault(require("@mapbox/geojson-normalize")); var _core = require("@loaders.gl/core"); var _wkt = require("@loaders.gl/wkt"); var _constants = require("@kepler.gl/constants"); var _utils = require("@kepler.gl/utils"); var _commonUtils = require("@kepler.gl/common-utils"); var _schemas = require("@kepler.gl/schemas"); 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; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project // if any of these value occurs in csv, parse it to null; // const CSV_NULLS = ['', 'null', 'NULL', 'Null', 'NaN', '/N']; // matches empty string var CSV_NULLS = exports.CSV_NULLS = /^(null|NULL|Null|NaN|\/N||)$/; function tryParseJsonString(str) { try { return JSON.parse(str); } catch (e) { return null; } } var PARSE_FIELD_VALUE_FROM_STRING = exports.PARSE_FIELD_VALUE_FROM_STRING = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants.ALL_FIELD_TYPES["boolean"], { valid: function valid(d) { return typeof d === 'boolean'; }, parse: function parse(d) { return d === 'true' || d === 'True' || d === 'TRUE' || d === '1'; } }), _constants.ALL_FIELD_TYPES.integer, { // @ts-ignore valid: function valid(d) { return parseInt(d, 10) === d; }, // @ts-ignore parse: function parse(d) { return parseInt(d, 10); } }), _constants.ALL_FIELD_TYPES.timestamp, { valid: function valid(d, field) { return ['x', 'X'].includes(field.format) ? typeof d === 'number' : typeof d === 'string'; }, parse: function parse(d, field) { return ['x', 'X'].includes(field.format) ? Number(d) : d; } }), _constants.ALL_FIELD_TYPES.real, { // @ts-ignore valid: function valid(d) { return parseFloat(d) === d; }, // Note this will result in NaN for some string parse: parseFloat }), _constants.ALL_FIELD_TYPES.object, { valid: _utils.isPlainObject, parse: tryParseJsonString }), _constants.ALL_FIELD_TYPES.array, { valid: Array.isArray, parse: tryParseJsonString }), _constants.ALL_FIELD_TYPES.h3, { valid: function valid(d) { return (0, _commonUtils.h3IsValid)(d); }, parse: function parse(d) { return d; } }); /** * Process csv data, output a data object with `{fields: [], rows: []}`. * The data object can be wrapped in a `dataset` and pass to [`addDataToMap`](../actions/actions.md#adddatatomap) * @param rawData raw csv string * @returns data object `{fields: [], rows: []}` can be passed to addDataToMaps * @public * @example * import {processCsvData} from '@kepler.gl/processors'; * * const testData = `gps_data.utc_timestamp,gps_data.lat,gps_data.lng,gps_data.types,epoch,has_result,id,time,begintrip_ts_utc,begintrip_ts_local,date * 2016-09-17 00:09:55,29.9900937,31.2590542,driver_analytics,1472688000000,False,1,2016-09-23T00:00:00.000Z,2016-10-01 09:41:39+00:00,2016-10-01 09:41:39+00:00,2016-09-23 * 2016-09-17 00:10:56,29.9927699,31.2461142,driver_analytics,1472688000000,False,2,2016-09-23T00:00:00.000Z,2016-10-01 09:46:37+00:00,2016-10-01 16:46:37+00:00,2016-09-23 * 2016-09-17 00:11:56,29.9907261,31.2312742,driver_analytics,1472688000000,False,3,2016-09-23T00:00:00.000Z,,,2016-09-23 * 2016-09-17 00:12:58,29.9870074,31.2175827,driver_analytics,1472688000000,False,4,2016-09-23T00:00:00.000Z,,,2016-09-23` * * const dataset = { * info: {id: 'test_data', label: 'My Csv'}, * data: processCsvData(testData) * }; * * dispatch(addDataToMap({ * datasets: [dataset], * options: {centerMap: true, readOnly: true} * })); */ function processCsvData(rawData, header) { var rows; var headerRow; if (typeof rawData === 'string') { var _parsedRows = (0, _d3Dsv.csvParseRows)(rawData); if (!Array.isArray(_parsedRows) || _parsedRows.length < 2) { // looks like an empty file, throw error to be catch throw new Error('process Csv Data Failed: CSV is empty'); } headerRow = _parsedRows[0]; rows = _parsedRows.slice(1); } else if (Array.isArray(rawData) && rawData.length) { rows = rawData; headerRow = header; if (!Array.isArray(headerRow)) { // if data is passed in as array of rows and missing header // assume first row is header // @ts-ignore headerRow = rawData[0]; rows = rawData.slice(1); } } if (!rows || !headerRow) { throw new Error('invalid input passed to processCsvData'); } // here we assume the csv file that people uploaded will have first row // as name of the column cleanUpFalsyCsvValue(rows); // No need to run type detection on every data point // here we get a list of none null values to run analyze on var sample = (0, _commonUtils.getSampleForTypeAnalyze)({ fields: headerRow, rows: rows }); var fields = (0, _commonUtils.getFieldsFromData)(sample, headerRow); var parsedRows = parseRowsByFields(rows, fields); return { fields: fields, rows: parsedRows }; } /** * Parse rows of csv by analyzed field types. So that `'1'` -> `1`, `'True'` -> `true` * @param rows * @param fields */ function parseRowsByFields(rows, fields) { // Edit rows in place var geojsonFieldIdx = fields.findIndex(function (f) { return f.name === '_geojson'; }); fields.forEach(parseCsvRowsByFieldType.bind(null, rows, geojsonFieldIdx)); return rows; } /** * Convert falsy value in csv including `'', 'null', 'NULL', 'Null', 'NaN'` to `null`, * so that type-analyzer won't detect it as string * * @param rows */ function cleanUpFalsyCsvValue(rows) { var re = new RegExp(CSV_NULLS, 'g'); for (var i = 0; i < rows.length; i++) { for (var j = 0; j < rows[i].length; j++) { // analyzer will set any fields to 'string' if there are empty values // which will be parsed as '' by d3.csv // here we parse empty data as null // TODO: create warning when deltect `CSV_NULLS` in the data if (typeof rows[i][j] === 'string' && rows[i][j].match(re)) { rows[i][j] = null; } } } } /** * Process uploaded csv file to parse value by field type * * @param rows * @param geoFieldIdx field index * @param field * @param i */ function parseCsvRowsByFieldType(rows, geoFieldIdx, field, i) { var parser = PARSE_FIELD_VALUE_FROM_STRING[field.type]; if (parser) { // check first not null value of it's already parsed var first = rows.find(function (r) { return (0, _commonUtils.notNullorUndefined)(r[i]); }); if (!first || parser.valid(first[i], field)) { return; } rows.forEach(function (row) { // parse string value based on field type if (row[i] !== null) { row[i] = parser.parse(row[i], field); if (geoFieldIdx > -1 && (0, _utils.isPlainObject)(row[geoFieldIdx]) && // @ts-ignore (0, _utils.hasOwnProperty)(row[geoFieldIdx], 'properties')) { // @ts-ignore row[geoFieldIdx].properties[field.name] = row[i]; } } }); } } /* eslint-enable complexity */ /** * Process data where each row is an object, output can be passed to [`addDataToMap`](../actions/actions.md#adddatatomap) * NOTE: This function may mutate input. * @param rawData an array of row object, each object should have the same number of keys * @returns dataset containing `fields` and `rows` * @public * @example * import {addDataToMap} from '@kepler.gl/actions'; * import {processRowObject} from '@kepler.gl/processors'; * * const data = [ * {lat: 31.27, lng: 127.56, value: 3}, * {lat: 31.22, lng: 126.26, value: 1} * ]; * * dispatch(addDataToMap({ * datasets: { * info: {label: 'My Data', id: 'my_data'}, * data: processRowObject(data) * } * })); */ function processRowObject(rawData) { if (!Array.isArray(rawData)) { return null; } else if (!rawData.length) { // data is empty return { fields: [], rows: [] }; } var firstRow = rawData[0]; var keys = Object.keys(firstRow); // [lat, lng, value] var rows = rawData.map(function (d) { return keys.map(function (key) { return d[key]; }); }); // [[31.27, 127.56, 3]] // row object can still contain values like `Null` or `N/A` cleanUpFalsyCsvValue(rows); return processCsvData(rows, keys); } /** * Process GeoJSON [`FeatureCollection`](http://wiki.geojson.org/GeoJSON_draft_version_6#FeatureCollection), * output a data object with `{fields: [], rows: []}`. * The data object can be wrapped in a `dataset` and passed to [`addDataToMap`](../actions/actions.md#adddatatomap) * NOTE: This function may mutate input. * * @param rawData raw geojson feature collection * @returns dataset containing `fields` and `rows` * @public * @example * import {addDataToMap} from '@kepler.gl/actions'; * import {processGeojson} from '@kepler.gl/processors'; * * const geojson = { * "type" : "FeatureCollection", * "features" : [{ * "type" : "Feature", * "properties" : { * "capacity" : "10", * "type" : "U-Rack" * }, * "geometry" : { * "type" : "Point", * "coordinates" : [ -71.073283, 42.417500 ] * } * }] * }; * * dispatch(addDataToMap({ * datasets: { * info: { * label: 'Sample Taxi Trips in New York City', * id: 'test_trip_data' * }, * data: processGeojson(geojson) * } * })); */ function processGeojson(rawData) { var normalizedGeojson = (0, _geojsonNormalize["default"])(rawData); if (!normalizedGeojson || !Array.isArray(normalizedGeojson.features)) { throw new Error("Read File Failed: File is not a valid GeoJSON. Read more about [supported file format](".concat(_constants.GUIDES_FILE_FORMAT_DOC, ")")); } // getting all feature fields var allDataRows = []; for (var i = 0; i < normalizedGeojson.features.length; i++) { var f = normalizedGeojson.features[i]; if (f.geometry) { allDataRows.push(_objectSpread({ // add feature to _geojson field _geojson: f }, f.properties || {})); } } // get all the field var fields = allDataRows.reduce(function (accu, curr) { Object.keys(curr).forEach(function (key) { if (!accu.includes(key)) { accu.push(key); } }); return accu; }, []); // make sure each feature has exact same fields allDataRows.forEach(function (d) { fields.forEach(function (f) { if (!(f in d)) { d[f] = null; if (d._geojson.properties) { d._geojson.properties[f] = null; } } }); }); return processRowObject(allDataRows); } /** * Process saved kepler.gl json to be pass to [`addDataToMap`](../actions/actions.md#adddatatomap). * The json object should contain `datasets` and `config`. * @param rawData * @param schema * @returns datasets and config `{datasets: {}, config: {}}` * @public * @example * import {addDataToMap} from '@kepler.gl/actions'; * import {processKeplerglJSON} from '@kepler.gl/processors'; * * dispatch(addDataToMap(processKeplerglJSON(keplerGlJson))); */ function processKeplerglJSON(rawData) { var schema = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _schemas.KeplerGlSchema; return rawData ? schema.load(rawData.datasets, rawData.config) : null; } /** * Parse a single or an array of datasets saved using kepler.gl schema * @param rawData * @param schema */ function processKeplerglDataset(rawData) { var schema = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _schemas.KeplerGlSchema; if (!rawData) { return null; } var results = schema.parseSavedData((0, _commonUtils.toArray)(rawData)); if (!results) { return null; } return Array.isArray(rawData) ? results : results[0]; } /** * Parse arrow table and return a dataset * * @param arrowTable ArrowTable to parse, see loaders.gl/schema * @returns dataset containing `fields` and `rows` or null */ function processArrowTable(arrowTable) { // @ts-ignore - Unknown data type causing build failures return processArrowBatches(arrowTable.data.batches); } /** * Extracts GeoArrow metadata from an Apache Arrow table schema. * For geoparquet files geoarrow metadata isn't present in fields, so extract extra info from schema. * @param table The Apache Arrow table to extract metadata from. * @returns An object mapping column names to their GeoArrow encoding type. * @throws Logs an error message if parsing of metadata fails. */ function getGeoArrowMetadataFromSchema(table) { var geoArrowMetadata = {}; try { var _table$schema$metadat; var geoString = (_table$schema$metadat = table.schema.metadata) === null || _table$schema$metadat === void 0 ? void 0 : _table$schema$metadat.get('geo'); if (geoString) { var parsedGeoString = JSON.parse(geoString); if (parsedGeoString.columns) { Object.keys(parsedGeoString.columns).forEach(function (columnName) { var columnData = parsedGeoString.columns[columnName]; if ((columnData === null || columnData === void 0 ? void 0 : columnData.encoding) === 'WKB') { geoArrowMetadata[columnName] = _constants.GEOARROW_EXTENSIONS.WKB; } // TODO potentially there are other types but no datasets to test }); } } } catch (error) { console.error('An error during arrow table schema metadata parsing'); } return geoArrowMetadata; } /** * Converts an Apache Arrow table schema into an array of Kepler.gl field objects. * @param table The Apache Arrow table whose schema needs to be converted. * @param fieldTypeSuggestions Optional mapping of field names to suggested field types. * @returns An array of field objects suitable for Kepler.gl. */ function arrowSchemaToFields(table) { var fieldTypeSuggestions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var headerRow = table.schema.fields.map(function (f) { return f.name; }); var sample = (0, _commonUtils.getSampleForTypeAnalyzeArrow)(table, headerRow); var keplerFields = (0, _commonUtils.getFieldsFromData)(sample, headerRow); var geoArrowMetadata = getGeoArrowMetadataFromSchema(table); return table.schema.fields.map(function (field, fieldIndex) { var _field$metadata$get; var type = (0, _utils.arrowDataTypeToFieldType)(field.type); var analyzerType = (0, _utils.arrowDataTypeToAnalyzerDataType)(field.type); var format = ''; // geometry fields produced by DuckDB's st_asgeojson() if (fieldTypeSuggestions[field.name] === 'JSON') { type = _constants.ALL_FIELD_TYPES.geojson; analyzerType = _typeAnalyzer.DATA_TYPES.GEOMETRY_FROM_STRING; } else if (fieldTypeSuggestions[field.name] === 'GEOMETRY' || (_field$metadata$get = field.metadata.get(_constants.GEOARROW_METADATA_KEY)) !== null && _field$metadata$get !== void 0 && _field$metadata$get.startsWith('geoarrow')) { type = _constants.ALL_FIELD_TYPES.geoarrow; analyzerType = _typeAnalyzer.DATA_TYPES.GEOMETRY; } else if (geoArrowMetadata[field.name]) { var _field$metadata; type = _constants.ALL_FIELD_TYPES.geoarrow; analyzerType = _typeAnalyzer.DATA_TYPES.GEOMETRY; (_field$metadata = field.metadata) === null || _field$metadata === void 0 || _field$metadata.set(_constants.GEOARROW_METADATA_KEY, geoArrowMetadata[field.name]); } else if (fieldTypeSuggestions[field.name] === 'BLOB') { // When arrow wkb column saved to DuckDB as BLOB without any metadata, then queried back try { var _table$getChildAt; var data = (_table$getChildAt = table.getChildAt(fieldIndex)) === null || _table$getChildAt === void 0 ? void 0 : _table$getChildAt.get(0); if (data) { var binaryGeo = (0, _core.parseSync)(data, _wkt.WKBLoader); if (binaryGeo) { var _field$metadata2; type = _constants.ALL_FIELD_TYPES.geoarrow; analyzerType = _typeAnalyzer.DATA_TYPES.GEOMETRY; (_field$metadata2 = field.metadata) === null || _field$metadata2 === void 0 || _field$metadata2.set(_constants.GEOARROW_METADATA_KEY, _constants.GEOARROW_EXTENSIONS.WKB); } } } catch (error) { // ignore, not WKB } } else { // TODO should we use Kepler getFieldsFromData instead // of arrowDataTypeToFieldType for all fields? var keplerField = keplerFields[fieldIndex]; if (keplerField.type === _constants.ALL_FIELD_TYPES.timestamp) { type = keplerField.type; analyzerType = keplerField.analyzerType; format = keplerField.format; } } return _objectSpread(_objectSpread({}, field), {}, { name: field.name, id: field.name, displayName: field.name, format: format, fieldIdx: fieldIndex, type: type, analyzerType: analyzerType, valueAccessor: function valueAccessor(dc) { return function (d) { return dc.valueAt(d.index, fieldIndex); }; }, metadata: field.metadata }); }); } /** * Parse arrow batches returned from parseInBatches() * * @param arrowTable the arrow table to parse * @returns dataset containing `fields` and `rows` or null */ function processArrowBatches(arrowBatches) { if (arrowBatches.length === 0) { return null; } var arrowTable = new arrow.Table(arrowBatches); var fields = arrowSchemaToFields(arrowTable); var cols = (0, _toConsumableArray2["default"])(Array(arrowTable.numCols).keys()).map(function (i) { return arrowTable.getChildAt(i); }); // return empty rows and use raw arrow table to construct column-wise data container return { fields: fields, rows: [], cols: cols, metadata: arrowTable.schema.metadata, // Save original arrow schema, for better ingestion into DuckDB. // TODO consider returning arrowTable in cols, not an array of Vectors from arrowTable. arrowSchema: arrowTable.schema }; } var DATASET_HANDLERS = exports.DATASET_HANDLERS = (0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])((0, _defineProperty2["default"])({}, _constants.DATASET_FORMATS.row, processRowObject), _constants.DATASET_FORMATS.geojson, processGeojson), _constants.DATASET_FORMATS.csv, processCsvData), _constants.DATASET_FORMATS.arrow, processArrowTable), _constants.DATASET_FORMATS.keplergl, processKeplerglDataset); var Processors = exports.Processors = { processGeojson: processGeojson, processCsvData: processCsvData, processArrowTable: processArrowTable, processArrowBatches: processArrowBatches, processRowObject: processRowObject, processKeplerglJSON: processKeplerglJSON, processKeplerglDataset: processKeplerglDataset, analyzerTypeToFieldType: _commonUtils.analyzerTypeToFieldType, getFieldsFromData: _commonUtils.getFieldsFromData, parseCsvRowsByFieldType: parseCsvRowsByFieldType }; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJhcnJvdyIsIl9pbnRlcm9wUmVxdWlyZVdpbGRjYXJkIiwicmVxdWlyZSIsIl9kM0RzdiIsIl90eXBlQW5hbHl6ZXIiLCJfZ2VvanNvbk5vcm1hbGl6ZSIsIl9pbnRlcm9wUmVxdWlyZURlZmF1bHQiLCJfY29yZSIsIl93a3QiLCJfY29uc3RhbnRzIiwiX3V0aWxzIiwiX2NvbW1vblV0aWxzIiwiX3NjaGVtYXMiLCJfZ2V0UmVxdWlyZVdpbGRjYXJkQ2FjaGUiLCJlIiwiV2Vha01hcCIsInIiLCJ0IiwiX19lc01vZHVsZSIsIl90eXBlb2YiLCJoYXMiLCJnZXQiLCJuIiwiX19wcm90b19fIiwiYSIsIk9iamVjdCIsImRlZmluZVByb3BlcnR5IiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9yIiwidSIsImhhc093blByb3BlcnR5IiwiY2FsbCIsImkiLCJzZXQiLCJvd25LZXlzIiwia2V5cyIsImdldE93blByb3BlcnR5U3ltYm9scyIsIm8iLCJmaWx0ZXIiLCJlbnVtZXJhYmxlIiwicHVzaCIsImFwcGx5IiwiX29iamVjdFNwcmVhZCIsImFyZ3VtZW50cyIsImxlbmd0aCIsImZvckVhY2giLCJfZGVmaW5lUHJvcGVydHkyIiwiZ2V0T3duUHJvcGVydHlEZXNjcmlwdG9ycyIsImRlZmluZVByb3BlcnRpZXMiLCJDU1ZfTlVMTFMiLCJleHBvcnRzIiwidHJ5UGFyc2VKc29uU3RyaW5nIiwic3RyIiwiSlNPTiIsInBhcnNlIiwiUEFSU0VfRklFTERfVkFMVUVfRlJPTV9TVFJJTkciLCJBTExfRklFTERfVFlQRVMiLCJ2YWxpZCIsImQiLCJpbnRlZ2VyIiwicGFyc2VJbnQiLCJ0aW1lc3RhbXAiLCJmaWVsZCIsImluY2x1ZGVzIiwiZm9ybWF0IiwiTnVtYmVyIiwicmVhbCIsInBhcnNlRmxvYXQiLCJvYmplY3QiLCJpc1BsYWluT2JqZWN0IiwiYXJyYXkiLCJBcnJheSIsImlzQXJyYXkiLCJoMyIsImgzSXNWYWxpZCIsInByb2Nlc3NDc3ZEYXRhIiwicmF3RGF0YSIsImhlYWRlciIsInJvd3MiLCJoZWFkZXJSb3ciLCJwYXJzZWRSb3dzIiwiY3N2UGFyc2VSb3dzIiwiRXJyb3IiLCJzbGljZSIsImNsZWFuVXBGYWxzeUNzdlZhbHVlIiwic2FtcGxlIiwiZ2V0U2FtcGxlRm9yVHlwZUFuYWx5emUiLCJmaWVsZHMiLCJnZXRGaWVsZHNGcm9tRGF0YSIsInBhcnNlUm93c0J5RmllbGRzIiwiZ2VvanNvbkZpZWxkSWR4IiwiZmluZEluZGV4IiwiZiIsIm5hbWUiLCJwYXJzZUNzdlJvd3NCeUZpZWxkVHlwZSIsImJpbmQiLCJyZSIsIlJlZ0V4cCIsImoiLCJtYXRjaCIsImdlb0ZpZWxkSWR4IiwicGFyc2VyIiwidHlwZSIsImZpcnN0IiwiZmluZCIsIm5vdE51bGxvclVuZGVmaW5lZCIsInJvdyIsInByb3BlcnRpZXMiLCJwcm9jZXNzUm93T2JqZWN0IiwiZmlyc3RSb3ciLCJtYXAiLCJrZXkiLCJwcm9jZXNzR2VvanNvbiIsIm5vcm1hbGl6ZWRHZW9qc29uIiwibm9ybWFsaXplIiwiZmVhdHVyZXMiLCJjb25jYXQiLCJHVUlERVNfRklMRV9GT1JNQVRfRE9DIiwiYWxsRGF0YVJvd3MiLCJnZW9tZXRyeSIsIl9nZW9qc29uIiwicmVkdWNlIiwiYWNjdSIsImN1cnIiLCJwcm9jZXNzS2VwbGVyZ2xKU09OIiwic2NoZW1hIiwidW5kZWZpbmVkIiwiS2VwbGVyR2xTY2hlbWEiLCJsb2FkIiwiZGF0YXNldHMiLCJjb25maWciLCJwcm9jZXNzS2VwbGVyZ2xEYXRhc2V0IiwicmVzdWx0cyIsInBhcnNlU2F2ZWREYXRhIiwidG9BcnJheSIsInByb2Nlc3NBcnJvd1RhYmxlIiwiYXJyb3dUYWJsZSIsInByb2Nlc3NBcnJvd0JhdGNoZXMiLCJkYXRhIiwiYmF0Y2hlcyIsImdldEdlb0Fycm93TWV0YWRhdGFGcm9tU2NoZW1hIiwidGFibGUiLCJnZW9BcnJvd01ldGFkYXRhIiwiX3RhYmxlJHNjaGVtYSRtZXRhZGF0IiwiZ2VvU3RyaW5nIiwibWV0YWRhdGEiLCJwYXJzZWRHZW9TdHJpbmciLCJjb2x1bW5zIiwiY29sdW1uTmFtZSIsImNvbHVtbkRhdGEiLCJlbmNvZGluZyIsIkdFT0FSUk9XX0VYVEVOU0lPTlMiLCJXS0IiLCJlcnJvciIsImNvbnNvbGUiLCJhcnJvd1NjaGVtYVRvRmllbGRzIiwiZmllbGRUeXBlU3VnZ2VzdGlvbnMiLCJnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZUFycm93Iiwia2VwbGVyRmllbGRzIiwiZmllbGRJbmRleCIsIl9maWVsZCRtZXRhZGF0YSRnZXQiLCJhcnJvd0RhdGFUeXBlVG9GaWVsZFR5cGUiLCJhbmFseXplclR5cGUiLCJhcnJvd0RhdGFUeXBlVG9BbmFseXplckRhdGFUeXBlIiwiZ2VvanNvbiIsIkFuYWx5emVyREFUQV9UWVBFUyIsIkdFT01FVFJZX0ZST01fU1RSSU5HIiwiR0VPQVJST1dfTUVUQURBVEFfS0VZIiwic3RhcnRzV2l0aCIsImdlb2Fycm93IiwiR0VPTUVUUlkiLCJfZmllbGQkbWV0YWRhdGEiLCJfdGFibGUkZ2V0Q2hpbGRBdCIsImdldENoaWxkQXQiLCJiaW5hcnlHZW8iLCJwYXJzZVN5bmMiLCJXS0JMb2FkZXIiLCJfZmllbGQkbWV0YWRhdGEyIiwia2VwbGVyRmllbGQiLCJpZCIsImRpc3BsYXlOYW1lIiwiZmllbGRJZHgiLCJ2YWx1ZUFjY2Vzc29yIiwiZGMiLCJ2YWx1ZUF0IiwiaW5kZXgiLCJhcnJvd0JhdGNoZXMiLCJUYWJsZSIsImNvbHMiLCJfdG9Db25zdW1hYmxlQXJyYXkyIiwibnVtQ29scyIsImFycm93U2NoZW1hIiwiREFUQVNFVF9IQU5ETEVSUyIsIkRBVEFTRVRfRk9STUFUUyIsImNzdiIsImtlcGxlcmdsIiwiUHJvY2Vzc29ycyIsImFuYWx5emVyVHlwZVRvRmllbGRUeXBlIl0sInNvdXJjZXMiOlsiLi4vc3JjL2RhdGEtcHJvY2Vzc29yLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBNSVRcbi8vIENvcHlyaWdodCBjb250cmlidXRvcnMgdG8gdGhlIGtlcGxlci5nbCBwcm9qZWN0XG5cbmltcG9ydCAqIGFzIGFycm93IGZyb20gJ2FwYWNoZS1hcnJvdyc7XG5pbXBvcnQge2NzdlBhcnNlUm93c30gZnJvbSAnZDMtZHN2JztcbmltcG9ydCB7REFUQV9UWVBFUyBhcyBBbmFseXplckRBVEFfVFlQRVN9IGZyb20gJ3R5cGUtYW5hbHl6ZXInO1xuaW1wb3J0IG5vcm1hbGl6ZSBmcm9tICdAbWFwYm94L2dlb2pzb24tbm9ybWFsaXplJztcbmltcG9ydCB7cGFyc2VTeW5jfSBmcm9tICdAbG9hZGVycy5nbC9jb3JlJztcbmltcG9ydCB7QXJyb3dUYWJsZX0gZnJvbSAnQGxvYWRlcnMuZ2wvc2NoZW1hJztcbmltcG9ydCB7V0tCTG9hZGVyfSBmcm9tICdAbG9hZGVycy5nbC93a3QnO1xuXG5pbXBvcnQge1xuICBBTExfRklFTERfVFlQRVMsXG4gIERBVEFTRVRfRk9STUFUUyxcbiAgR0VPQVJST1dfRVhURU5TSU9OUyxcbiAgR0VPQVJST1dfTUVUQURBVEFfS0VZLFxuICBHVUlERVNfRklMRV9GT1JNQVRfRE9DXG59IGZyb20gJ0BrZXBsZXIuZ2wvY29uc3RhbnRzJztcbmltcG9ydCB7UHJvY2Vzc29yUmVzdWx0LCBGaWVsZH0gZnJvbSAnQGtlcGxlci5nbC90eXBlcyc7XG5pbXBvcnQge1xuICBhcnJvd0RhdGFUeXBlVG9BbmFseXplckRhdGFUeXBlLFxuICBhcnJvd0RhdGFUeXBlVG9GaWVsZFR5cGUsXG4gIGhhc093blByb3BlcnR5LFxuICBpc1BsYWluT2JqZWN0XG59IGZyb20gJ0BrZXBsZXIuZ2wvdXRpbHMnO1xuaW1wb3J0IHtcbiAgYW5hbHl6ZXJUeXBlVG9GaWVsZFR5cGUsXG4gIGdldFNhbXBsZUZvclR5cGVBbmFseXplLFxuICBnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZUFycm93LFxuICBnZXRGaWVsZHNGcm9tRGF0YSxcbiAgaDNJc1ZhbGlkLFxuICBub3ROdWxsb3JVbmRlZmluZWQsXG4gIHRvQXJyYXlcbn0gZnJvbSAnQGtlcGxlci5nbC9jb21tb24tdXRpbHMnO1xuaW1wb3J0IHtLZXBsZXJHbFNjaGVtYSwgUGFyc2VkRGF0YXNldCwgU2F2ZWRNYXAsIExvYWRlZE1hcH0gZnJvbSAnQGtlcGxlci5nbC9zY2hlbWFzJztcbmltcG9ydCB7RmVhdHVyZX0gZnJvbSAnQG5lYnVsYS5nbC9lZGl0LW1vZGVzJztcblxuLy8gaWYgYW55IG9mIHRoZXNlIHZhbHVlIG9jY3VycyBpbiBjc3YsIHBhcnNlIGl0IHRvIG51bGw7XG4vLyBjb25zdCBDU1ZfTlVMTFMgPSBbJycsICdudWxsJywgJ05VTEwnLCAnTnVsbCcsICdOYU4nLCAnL04nXTtcbi8vIG1hdGNoZXMgZW1wdHkgc3RyaW5nXG5leHBvcnQgY29uc3QgQ1NWX05VTExTID0gL14obnVsbHxOVUxMfE51bGx8TmFOfFxcL058fCkkLztcblxuZnVuY3Rpb24gdHJ5UGFyc2VKc29uU3RyaW5nKHN0cikge1xuICB0cnkge1xuICAgIHJldHVybiBKU09OLnBhcnNlKHN0cik7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxufVxuXG5leHBvcnQgY29uc3QgUEFSU0VfRklFTERfVkFMVUVfRlJPTV9TVFJJTkcgPSB7XG4gIFtBTExfRklFTERfVFlQRVMuYm9vbGVhbl06IHtcbiAgICB2YWxpZDogKGQ6IHVua25vd24pOiBib29sZWFuID0+IHR5cGVvZiBkID09PSAnYm9vbGVhbicsXG4gICAgcGFyc2U6IChkOiB1bmtub3duKTogYm9vbGVhbiA9PiBkID09PSAndHJ1ZScgfHwgZCA9PT0gJ1RydWUnIHx8IGQgPT09ICdUUlVFJyB8fCBkID09PSAnMSdcbiAgfSxcbiAgW0FMTF9GSUVMRF9UWVBFUy5pbnRlZ2VyXToge1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB2YWxpZDogKGQ6IHVua25vd24pOiBib29sZWFuID0+IHBhcnNlSW50KGQsIDEwKSA9PT0gZCxcbiAgICAvLyBAdHMtaWdub3JlXG4gICAgcGFyc2U6IChkOiB1bmtub3duKTogbnVtYmVyID0+IHBhcnNlSW50KGQsIDEwKVxuICB9LFxuICBbQUxMX0ZJRUxEX1RZUEVTLnRpbWVzdGFtcF06IHtcbiAgICB2YWxpZDogKGQ6IHVua25vd24sIGZpZWxkOiBGaWVsZCk6IGJvb2xlYW4gPT5cbiAgICAgIFsneCcsICdYJ10uaW5jbHVkZXMoZmllbGQuZm9ybWF0KSA/IHR5cGVvZiBkID09PSAnbnVtYmVyJyA6IHR5cGVvZiBkID09PSAnc3RyaW5nJyxcbiAgICBwYXJzZTogKGQ6IGFueSwgZmllbGQ6IEZpZWxkKSA9PiAoWyd4JywgJ1gnXS5pbmNsdWRlcyhmaWVsZC5mb3JtYXQpID8gTnVtYmVyKGQpIDogZClcbiAgfSxcbiAgW0FMTF9GSUVMRF9UWVBFUy5yZWFsXToge1xuICAgIC8vIEB0cy1pZ25vcmVcbiAgICB2YWxpZDogKGQ6IHVua25vd24pOiBib29sZWFuID0+IHBhcnNlRmxvYXQoZCkgPT09IGQsXG4gICAgLy8gTm90ZSB0aGlzIHdpbGwgcmVzdWx0IGluIE5hTiBmb3Igc29tZSBzdHJpbmdcbiAgICBwYXJzZTogcGFyc2VGbG9hdFxuICB9LFxuICBbQUxMX0ZJRUxEX1RZUEVTLm9iamVjdF06IHtcbiAgICB2YWxpZDogaXNQbGFpbk9iamVjdCxcbiAgICBwYXJzZTogdHJ5UGFyc2VKc29uU3RyaW5nXG4gIH0sXG5cbiAgW0FMTF9GSUVMRF9UWVBFUy5hcnJheV06IHtcbiAgICB2YWxpZDogQXJyYXkuaXNBcnJheSxcbiAgICBwYXJzZTogdHJ5UGFyc2VKc29uU3RyaW5nXG4gIH0sXG5cbiAgW0FMTF9GSUVMRF9UWVBFUy5oM106IHtcbiAgICB2YWxpZDogZCA9PiBoM0lzVmFsaWQoZCksXG4gICAgcGFyc2U6IGQgPT4gZFxuICB9XG59O1xuXG4vKipcbiAqIFByb2Nlc3MgY3N2IGRhdGEsIG91dHB1dCBhIGRhdGEgb2JqZWN0IHdpdGggYHtmaWVsZHM6IFtdLCByb3dzOiBbXX1gLlxuICogVGhlIGRhdGEgb2JqZWN0IGNhbiBiZSB3cmFwcGVkIGluIGEgYGRhdGFzZXRgIGFuZCBwYXNzIHRvIFtgYWRkRGF0YVRvTWFwYF0oLi4vYWN0aW9ucy9hY3Rpb25zLm1kI2FkZGRhdGF0b21hcClcbiAqIEBwYXJhbSByYXdEYXRhIHJhdyBjc3Ygc3RyaW5nXG4gKiBAcmV0dXJucyBkYXRhIG9iamVjdCBge2ZpZWxkczogW10sIHJvd3M6IFtdfWAgY2FuIGJlIHBhc3NlZCB0byBhZGREYXRhVG9NYXBzXG4gKiBAcHVibGljXG4gKiBAZXhhbXBsZVxuICogaW1wb3J0IHtwcm9jZXNzQ3N2RGF0YX0gZnJvbSAnQGtlcGxlci5nbC9wcm9jZXNzb3JzJztcbiAqXG4gKiBjb25zdCB0ZXN0RGF0YSA9IGBncHNfZGF0YS51dGNfdGltZXN0YW1wLGdwc19kYXRhLmxhdCxncHNfZGF0YS5sbmcsZ3BzX2RhdGEudHlwZXMsZXBvY2gsaGFzX3Jlc3VsdCxpZCx0aW1lLGJlZ2ludHJpcF90c191dGMsYmVnaW50cmlwX3RzX2xvY2FsLGRhdGVcbiAqIDIwMTYtMDktMTcgMDA6MDk6NTUsMjkuOTkwMDkzNywzMS4yNTkwNTQyLGRyaXZlcl9hbmFseXRpY3MsMTQ3MjY4ODAwMDAwMCxGYWxzZSwxLDIwMTYtMDktMjNUMDA6MDA6MDAuMDAwWiwyMDE2LTEwLTAxIDA5OjQxOjM5KzAwOjAwLDIwMTYtMTAtMDEgMDk6NDE6MzkrMDA6MDAsMjAxNi0wOS0yM1xuICogMjAxNi0wOS0xNyAwMDoxMDo1NiwyOS45OTI3Njk5LDMxLjI0NjExNDIsZHJpdmVyX2FuYWx5dGljcywxNDcyNjg4MDAwMDAwLEZhbHNlLDIsMjAxNi0wOS0yM1QwMDowMDowMC4wMDBaLDIwMTYtMTAtMDEgMDk6NDY6MzcrMDA6MDAsMjAxNi0xMC0wMSAxNjo0NjozNyswMDowMCwyMDE2LTA5LTIzXG4gKiAyMDE2LTA5LTE3IDAwOjExOjU2LDI5Ljk5MDcyNjEsMzEuMjMxMjc0Mixkcml2ZXJfYW5hbHl0aWNzLDE0NzI2ODgwMDAwMDAsRmFsc2UsMywyMDE2LTA5LTIzVDAwOjAwOjAwLjAwMFosLCwyMDE2LTA5LTIzXG4gKiAyMDE2LTA5LTE3IDAwOjEyOjU4LDI5Ljk4NzAwNzQsMzEuMjE3NTgyNyxkcml2ZXJfYW5hbHl0aWNzLDE0NzI2ODgwMDAwMDAsRmFsc2UsNCwyMDE2LTA5LTIzVDAwOjAwOjAwLjAwMFosLCwyMDE2LTA5LTIzYFxuICpcbiAqIGNvbnN0IGRhdGFzZXQgPSB7XG4gKiAgaW5mbzoge2lkOiAndGVzdF9kYXRhJywgbGFiZWw6ICdNeSBDc3YnfSxcbiAqICBkYXRhOiBwcm9jZXNzQ3N2RGF0YSh0ZXN0RGF0YSlcbiAqIH07XG4gKlxuICogZGlzcGF0Y2goYWRkRGF0YVRvTWFwKHtcbiAqICBkYXRhc2V0czogW2RhdGFzZXRdLFxuICogIG9wdGlvbnM6IHtjZW50ZXJNYXA6IHRydWUsIHJlYWRPbmx5OiB0cnVlfVxuICogfSkpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0NzdkRhdGEocmF3RGF0YTogdW5rbm93bltdW10gfCBzdHJpbmcsIGhlYWRlcj86IHN0cmluZ1tdKTogUHJvY2Vzc29yUmVzdWx0IHtcbiAgbGV0IHJvd3M6IHVua25vd25bXVtdIHwgdW5kZWZpbmVkO1xuICBsZXQgaGVhZGVyUm93OiBzdHJpbmdbXSB8IHVuZGVmaW5lZDtcblxuICBpZiAodHlwZW9mIHJhd0RhdGEgPT09ICdzdHJpbmcnKSB7XG4gICAgY29uc3QgcGFyc2VkUm93czogc3RyaW5nW11bXSA9IGNzdlBhcnNlUm93cyhyYXdEYXRhKTtcblxuICAgIGlmICghQXJyYXkuaXNBcnJheShwYXJzZWRSb3dzKSB8fCBwYXJzZWRSb3dzLmxlbmd0aCA8IDIpIHtcbiAgICAgIC8vIGxvb2tzIGxpa2UgYW4gZW1wdHkgZmlsZSwgdGhyb3cgZXJyb3IgdG8gYmUgY2F0Y2hcbiAgICAgIHRocm93IG5ldyBFcnJvcigncHJvY2VzcyBDc3YgRGF0YSBGYWlsZWQ6IENTViBpcyBlbXB0eScpO1xuICAgIH1cbiAgICBoZWFkZXJSb3cgPSBwYXJzZWRSb3dzWzBdO1xuICAgIHJvd3MgPSBwYXJzZWRSb3dzLnNsaWNlKDEpO1xuICB9IGVsc2UgaWYgKEFycmF5LmlzQXJyYXkocmF3RGF0YSkgJiYgcmF3RGF0YS5sZW5ndGgpIHtcbiAgICByb3dzID0gcmF3RGF0YTtcbiAgICBoZWFkZXJSb3cgPSBoZWFkZXI7XG5cbiAgICBpZiAoIUFycmF5LmlzQXJyYXkoaGVhZGVyUm93KSkge1xuICAgICAgLy8gaWYgZGF0YSBpcyBwYXNzZWQgaW4gYXMgYXJyYXkgb2Ygcm93cyBhbmQgbWlzc2luZyBoZWFkZXJcbiAgICAgIC8vIGFzc3VtZSBmaXJzdCByb3cgaXMgaGVhZGVyXG4gICAgICAvLyBAdHMtaWdub3JlXG4gICAgICBoZWFkZXJSb3cgPSByYXdEYXRhWzBdO1xuICAgICAgcm93cyA9IHJhd0RhdGEuc2xpY2UoMSk7XG4gICAgfVxuICB9XG5cbiAgaWYgKCFyb3dzIHx8ICFoZWFkZXJSb3cpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoJ2ludmFsaWQgaW5wdXQgcGFzc2VkIHRvIHByb2Nlc3NDc3ZEYXRhJyk7XG4gIH1cblxuICAvLyBoZXJlIHdlIGFzc3VtZSB0aGUgY3N2IGZpbGUgdGhhdCBwZW9wbGUgdXBsb2FkZWQgd2lsbCBoYXZlIGZpcnN0IHJvd1xuICAvLyBhcyBuYW1lIG9mIHRoZSBjb2x1bW5cblxuICBjbGVhblVwRmFsc3lDc3ZWYWx1ZShyb3dzKTtcbiAgLy8gTm8gbmVlZCB0byBydW4gdHlwZSBkZXRlY3Rpb24gb24gZXZlcnkgZGF0YSBwb2ludFxuICAvLyBoZXJlIHdlIGdldCBhIGxpc3Qgb2Ygbm9uZSBudWxsIHZhbHVlcyB0byBydW4gYW5hbHl6ZSBvblxuICBjb25zdCBzYW1wbGUgPSBnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZSh7ZmllbGRzOiBoZWFkZXJSb3csIHJvd3N9KTtcbiAgY29uc3QgZmllbGRzID0gZ2V0RmllbGRzRnJvbURhdGEoc2FtcGxlLCBoZWFkZXJSb3cpO1xuICBjb25zdCBwYXJzZWRSb3dzID0gcGFyc2VSb3dzQnlGaWVsZHMocm93cywgZmllbGRzKTtcblxuICByZXR1cm4ge2ZpZWxkcywgcm93czogcGFyc2VkUm93c307XG59XG5cbi8qKlxuICogUGFyc2Ugcm93cyBvZiBjc3YgYnkgYW5hbHl6ZWQgZmllbGQgdHlwZXMuIFNvIHRoYXQgYCcxJ2AgLT4gYDFgLCBgJ1RydWUnYCAtPiBgdHJ1ZWBcbiAqIEBwYXJhbSByb3dzXG4gKiBAcGFyYW0gZmllbGRzXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwYXJzZVJvd3NCeUZpZWxkcyhyb3dzOiBhbnlbXVtdLCBmaWVsZHM6IEZpZWxkW10pIHtcbiAgLy8gRWRpdCByb3dzIGluIHBsYWNlXG4gIGNvbnN0IGdlb2pzb25GaWVsZElkeCA9IGZpZWxkcy5maW5kSW5kZXgoZiA9PiBmLm5hbWUgPT09ICdfZ2VvanNvbicpO1xuICBmaWVsZHMuZm9yRWFjaChwYXJzZUNzdlJvd3NCeUZpZWxkVHlwZS5iaW5kKG51bGwsIHJvd3MsIGdlb2pzb25GaWVsZElkeCkpO1xuXG4gIHJldHVybiByb3dzO1xufVxuXG4vKipcbiAqIENvbnZlcnQgZmFsc3kgdmFsdWUgaW4gY3N2IGluY2x1ZGluZyBgJycsICdudWxsJywgJ05VTEwnLCAnTnVsbCcsICdOYU4nYCB0byBgbnVsbGAsXG4gKiBzbyB0aGF0IHR5cGUtYW5hbHl6ZXIgd29uJ3QgZGV0ZWN0IGl0IGFzIHN0cmluZ1xuICpcbiAqIEBwYXJhbSByb3dzXG4gKi9cbmZ1bmN0aW9uIGNsZWFuVXBGYWxzeUNzdlZhbHVlKHJvd3M6IHVua25vd25bXVtdKTogdm9pZCB7XG4gIGNvbnN0IHJlID0gbmV3IFJlZ0V4cChDU1ZfTlVMTFMsICdnJyk7XG4gIGZvciAobGV0IGkgPSAwOyBpIDwgcm93cy5sZW5ndGg7IGkrKykge1xuICAgIGZvciAobGV0IGogPSAwOyBqIDwgcm93c1tpXS5sZW5ndGg7IGorKykge1xuICAgICAgLy8gYW5hbHl6ZXIgd2lsbCBzZXQgYW55IGZpZWxkcyB0byAnc3RyaW5nJyBpZiB0aGVyZSBhcmUgZW1wdHkgdmFsdWVzXG4gICAgICAvLyB3aGljaCB3aWxsIGJlIHBhcnNlZCBhcyAnJyBieSBkMy5jc3ZcbiAgICAgIC8vIGhlcmUgd2UgcGFyc2UgZW1wdHkgZGF0YSBhcyBudWxsXG4gICAgICAvLyBUT0RPOiBjcmVhdGUgd2FybmluZyB3aGVuIGRlbHRlY3QgYENTVl9OVUxMU2AgaW4gdGhlIGRhdGFcbiAgICAgIGlmICh0eXBlb2Ygcm93c1tpXVtqXSA9PT0gJ3N0cmluZycgJiYgKHJvd3NbaV1bal0gYXMgc3RyaW5nKS5tYXRjaChyZSkpIHtcbiAgICAgICAgcm93c1tpXVtqXSA9IG51bGw7XG4gICAgICB9XG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogUHJvY2VzcyB1cGxvYWRlZCBjc3YgZmlsZSB0byBwYXJzZSB2YWx1ZSBieSBmaWVsZCB0eXBlXG4gKlxuICogQHBhcmFtIHJvd3NcbiAqIEBwYXJhbSBnZW9GaWVsZElkeCBmaWVsZCBpbmRleFxuICogQHBhcmFtIGZpZWxkXG4gKiBAcGFyYW0gaVxuICovXG5leHBvcnQgZnVuY3Rpb24gcGFyc2VDc3ZSb3dzQnlGaWVsZFR5cGUoXG4gIHJvd3M6IHVua25vd25bXVtdLFxuICBnZW9GaWVsZElkeDogbnVtYmVyLFxuICBmaWVsZDogRmllbGQsXG4gIGk6IG51bWJlclxuKTogdm9pZCB7XG4gIGNvbnN0IHBhcnNlciA9IFBBUlNFX0ZJRUxEX1ZBTFVFX0ZST01fU1RSSU5HW2ZpZWxkLnR5cGVdO1xuICBpZiAocGFyc2VyKSB7XG4gICAgLy8gY2hlY2sgZmlyc3Qgbm90IG51bGwgdmFsdWUgb2YgaXQncyBhbHJlYWR5IHBhcnNlZFxuICAgIGNvbnN0IGZpcnN0ID0gcm93cy5maW5kKHIgPT4gbm90TnVsbG9yVW5kZWZpbmVkKHJbaV0pKTtcbiAgICBpZiAoIWZpcnN0IHx8IHBhcnNlci52YWxpZChmaXJzdFtpXSwgZmllbGQpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHJvd3MuZm9yRWFjaChyb3cgPT4ge1xuICAgICAgLy8gcGFyc2Ugc3RyaW5nIHZhbHVlIGJhc2VkIG9uIGZpZWxkIHR5cGVcbiAgICAgIGlmIChyb3dbaV0gIT09IG51bGwpIHtcbiAgICAgICAgcm93W2ldID0gcGFyc2VyLnBhcnNlKHJvd1tpXSwgZmllbGQpO1xuICAgICAgICBpZiAoXG4gICAgICAgICAgZ2VvRmllbGRJZHggPiAtMSAmJlxuICAgICAgICAgIGlzUGxhaW5PYmplY3Qocm93W2dlb0ZpZWxkSWR4XSkgJiZcbiAgICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgICAgaGFzT3duUHJvcGVydHkocm93W2dlb0ZpZWxkSWR4XSwgJ3Byb3BlcnRpZXMnKVxuICAgICAgICApIHtcbiAgICAgICAgICAvLyBAdHMtaWdub3JlXG4gICAgICAgICAgcm93W2dlb0ZpZWxkSWR4XS5wcm9wZXJ0aWVzW2ZpZWxkLm5hbWVdID0gcm93W2ldO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfSk7XG4gIH1cbn1cblxuLyogZXNsaW50LWVuYWJsZSBjb21wbGV4aXR5ICovXG5cbi8qKlxuICogUHJvY2VzcyBkYXRhIHdoZXJlIGVhY2ggcm93IGlzIGFuIG9iamVjdCwgb3V0cHV0IGNhbiBiZSBwYXNzZWQgdG8gW2BhZGREYXRhVG9NYXBgXSguLi9hY3Rpb25zL2FjdGlvbnMubWQjYWRkZGF0YXRvbWFwKVxuICogTk9URTogVGhpcyBmdW5jdGlvbiBtYXkgbXV0YXRlIGlucHV0LlxuICogQHBhcmFtIHJhd0RhdGEgYW4gYXJyYXkgb2Ygcm93IG9iamVjdCwgZWFjaCBvYmplY3Qgc2hvdWxkIGhhdmUgdGhlIHNhbWUgbnVtYmVyIG9mIGtleXNcbiAqIEByZXR1cm5zIGRhdGFzZXQgY29udGFpbmluZyBgZmllbGRzYCBhbmQgYHJvd3NgXG4gKiBAcHVibGljXG4gKiBAZXhhbXBsZVxuICogaW1wb3J0IHthZGREYXRhVG9NYXB9IGZyb20gJ0BrZXBsZXIuZ2wvYWN0aW9ucyc7XG4gKiBpbXBvcnQge3Byb2Nlc3NSb3dPYmplY3R9IGZyb20gJ0BrZXBsZXIuZ2wvcHJvY2Vzc29ycyc7XG4gKlxuICogY29uc3QgZGF0YSA9IFtcbiAqICB7bGF0OiAzMS4yNywgbG5nOiAxMjcuNTYsIHZhbHVlOiAzfSxcbiAqICB7bGF0OiAzMS4yMiwgbG5nOiAxMjYuMjYsIHZhbHVlOiAxfVxuICogXTtcbiAqXG4gKiBkaXNwYXRjaChhZGREYXRhVG9NYXAoe1xuICogIGRhdGFzZXRzOiB7XG4gKiAgICBpbmZvOiB7bGFiZWw6ICdNeSBEYXRhJywgaWQ6ICdteV9kYXRhJ30sXG4gKiAgICBkYXRhOiBwcm9jZXNzUm93T2JqZWN0KGRhdGEpXG4gKiAgfVxuICogfSkpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc1Jvd09iamVjdChyYXdEYXRhOiB1bmtub3duW10pOiBQcm9jZXNzb3JSZXN1bHQge1xuICBpZiAoIUFycmF5LmlzQXJyYXkocmF3RGF0YSkpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfSBlbHNlIGlmICghcmF3RGF0YS5sZW5ndGgpIHtcbiAgICAvLyBkYXRhIGlzIGVtcHR5XG4gICAgcmV0dXJuIHtcbiAgICAgIGZpZWxkczogW10sXG4gICAgICByb3dzOiBbXVxuICAgIH07XG4gIH1cblxuICBjb25zdCBmaXJzdFJvdyA9IHJhd0RhdGFbMF0gYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj47XG4gIGNvbnN0IGtleXMgPSBPYmplY3Qua2V5cyhmaXJzdFJvdyk7IC8vIFtsYXQsIGxuZywgdmFsdWVdXG4gIGNvbnN0IHJvd3MgPSByYXdEYXRhLm1hcChkID0+IGtleXMubWFwKGtleSA9PiAoZCBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPilba2V5XSkpOyAvLyBbWzMxLjI3LCAxMjcuNTYsIDNdXVxuXG4gIC8vIHJvdyBvYmplY3QgY2FuIHN0aWxsIGNvbnRhaW4gdmFsdWVzIGxpa2UgYE51bGxgIG9yIGBOL0FgXG4gIGNsZWFuVXBGYWxzeUNzdlZhbHVlKHJvd3MpO1xuXG4gIHJldHVybiBwcm9jZXNzQ3N2RGF0YShyb3dzLCBrZXlzKTtcbn1cblxuLyoqXG4gKiBQcm9jZXNzIEdlb0pTT04gW2BGZWF0dXJlQ29sbGVjdGlvbmBdKGh0dHA6Ly93aWtpLmdlb2pzb24ub3JnL0dlb0pTT05fZHJhZnRfdmVyc2lvbl82I0ZlYXR1cmVDb2xsZWN0aW9uKSxcbiAqIG91dHB1dCBhIGRhdGEgb2JqZWN0IHdpdGggYHtmaWVsZHM6IFtdLCByb3dzOiBbXX1gLlxuICogVGhlIGRhdGEgb2JqZWN0IGNhbiBiZSB3cmFwcGVkIGluIGEgYGRhdGFzZXRgIGFuZCBwYXNzZWQgdG8gW2BhZGREYXRhVG9NYXBgXSguLi9hY3Rpb25zL2FjdGlvbnMubWQjYWRkZGF0YXRvbWFwKVxuICogTk9URTogVGhpcyBmdW5jdGlvbiBtYXkgbXV0YXRlIGlucHV0LlxuICpcbiAqIEBwYXJhbSByYXdEYXRhIHJhdyBnZW9qc29uIGZlYXR1cmUgY29sbGVjdGlvblxuICogQHJldHVybnMgZGF0YXNldCBjb250YWluaW5nIGBmaWVsZHNgIGFuZCBgcm93c2BcbiAqIEBwdWJsaWNcbiAqIEBleGFtcGxlXG4gKiBpbXBvcnQge2FkZERhdGFUb01hcH0gZnJvbSAnQGtlcGxlci5nbC9hY3Rpb25zJztcbiAqIGltcG9ydCB7cHJvY2Vzc0dlb2pzb259IGZyb20gJ0BrZXBsZXIuZ2wvcHJvY2Vzc29ycyc7XG4gKlxuICogY29uc3QgZ2VvanNvbiA9IHtcbiAqIFx0XCJ0eXBlXCIgOiBcIkZlYXR1cmVDb2xsZWN0aW9uXCIsXG4gKiBcdFwiZmVhdHVyZXNcIiA6IFt7XG4gKiBcdFx0XCJ0eXBlXCIgOiBcIkZlYXR1cmVcIixcbiAqIFx0XHRcInByb3BlcnRpZXNcIiA6IHtcbiAqIFx0XHRcdFwiY2FwYWNpdHlcIiA6IFwiMTBcIixcbiAqIFx0XHRcdFwidHlwZVwiIDogXCJVLVJhY2tcIlxuICogXHRcdH0sXG4gKiBcdFx0XCJnZW9tZXRyeVwiIDoge1xuICogXHRcdFx0XCJ0eXBlXCIgOiBcIlBvaW50XCIsXG4gKiBcdFx0XHRcImNvb3JkaW5hdGVzXCIgOiBbIC03MS4wNzMyODMsIDQyLjQxNzUwMCBdXG4gKiBcdFx0fVxuICogXHR9XVxuICogfTtcbiAqXG4gKiBkaXNwYXRjaChhZGREYXRhVG9NYXAoe1xuICogIGRhdGFzZXRzOiB7XG4gKiAgICBpbmZvOiB7XG4gKiAgICAgIGxhYmVsOiAnU2FtcGxlIFRheGkgVHJpcHMgaW4gTmV3IFlvcmsgQ2l0eScsXG4gKiAgICAgIGlkOiAndGVzdF90cmlwX2RhdGEnXG4gKiAgICB9LFxuICogICAgZGF0YTogcHJvY2Vzc0dlb2pzb24oZ2VvanNvbilcbiAqICB9XG4gKiB9KSk7XG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm9jZXNzR2VvanNvbihyYXdEYXRhOiB1bmtub3duKTogUHJvY2Vzc29yUmVzdWx0IHtcbiAgY29uc3Qgbm9ybWFsaXplZEdlb2pzb24gPSBub3JtYWxpemUocmF3RGF0YSk7XG5cbiAgaWYgKCFub3JtYWxpemVkR2VvanNvbiB8fCAhQXJyYXkuaXNBcnJheShub3JtYWxpemVkR2VvanNvbi5mZWF0dXJlcykpIHtcbiAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICBgUmVhZCBGaWxlIEZhaWxlZDogRmlsZSBpcyBub3QgYSB2YWxpZCBHZW9KU09OLiBSZWFkIG1vcmUgYWJvdXQgW3N1cHBvcnRlZCBmaWxlIGZvcm1hdF0oJHtHVUlERVNfRklMRV9GT1JNQVRfRE9DfSlgXG4gICAgKTtcbiAgfVxuXG4gIC8vIGdldHRpbmcgYWxsIGZlYXR1cmUgZmllbGRzXG4gIGNvbnN0IGFsbERhdGFSb3dzOiBBcnJheTx7X2dlb2pzb246IEZlYXR1cmV9ICYga2V5b2YgRmVhdHVyZT4gPSBbXTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBub3JtYWxpemVkR2VvanNvbi5mZWF0dXJlcy5sZW5ndGg7IGkrKykge1xuICAgIGNvbnN0IGYgPSBub3JtYWxpemVkR2VvanNvbi5mZWF0dXJlc1tpXTtcbiAgICBpZiAoZi5nZW9tZXRyeSkge1xuICAgICAgYWxsRGF0YVJvd3MucHVzaCh7XG4gICAgICAgIC8vIGFkZCBmZWF0dXJlIHRvIF9nZW9qc29uIGZpZWxkXG4gICAgICAgIF9nZW9qc29uOiBmLFxuICAgICAgICAuLi4oZi5wcm9wZXJ0aWVzIHx8IHt9KVxuICAgICAgfSk7XG4gICAgfVxuICB9XG4gIC8vIGdldCBhbGwgdGhlIGZpZWxkXG4gIGNvbnN0IGZpZWxkcyA9IGFsbERhdGFSb3dzLnJlZHVjZTxzdHJpbmdbXT4oKGFjY3UsIGN1cnIpID0+IHtcbiAgICBPYmplY3Qua2V5cyhjdXJyKS5mb3JFYWNoKGtleSA9PiB7XG4gICAgICBpZiAoIWFjY3UuaW5jbHVkZXMoa2V5KSkge1xuICAgICAgICBhY2N1LnB1c2goa2V5KTtcbiAgICAgIH1cbiAgICB9KTtcbiAgICByZXR1cm4gYWNjdTtcbiAgfSwgW10pO1xuXG4gIC8vIG1ha2Ugc3VyZSBlYWNoIGZlYXR1cmUgaGFzIGV4YWN0IHNhbWUgZmllbGRzXG4gIGFsbERhdGFSb3dzLmZvckVhY2goZCA9PiB7XG4gICAgZmllbGRzLmZvckVhY2goZiA9PiB7XG4gICAgICBpZiAoIShmIGluIGQpKSB7XG4gICAgICAgIGRbZl0gPSBudWxsO1xuICAgICAgICBpZiAoZC5fZ2VvanNvbi5wcm9wZXJ0aWVzKSB7XG4gICAgICAgICAgZC5fZ2VvanNvbi5wcm9wZXJ0aWVzW2ZdID0gbnVsbDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0pO1xuICB9KTtcblxuICByZXR1cm4gcHJvY2Vzc1Jvd09iamVjdChhbGxEYXRhUm93cyk7XG59XG5cbi8qKlxuICogUHJvY2VzcyBzYXZlZCBrZXBsZXIuZ2wganNvbiB0byBiZSBwYXNzIHRvIFtgYWRkRGF0YVRvTWFwYF0oLi4vYWN0aW9ucy9hY3Rpb25zLm1kI2FkZGRhdGF0b21hcCkuXG4gKiBUaGUganNvbiBvYmplY3Qgc2hvdWxkIGNvbnRhaW4gYGRhdGFzZXRzYCBhbmQgYGNvbmZpZ2AuXG4gKiBAcGFyYW0gcmF3RGF0YVxuICogQHBhcmFtIHNjaGVtYVxuICogQHJldHVybnMgZGF0YXNldHMgYW5kIGNvbmZpZyBge2RhdGFzZXRzOiB7fSwgY29uZmlnOiB7fX1gXG4gKiBAcHVibGljXG4gKiBAZXhhbXBsZVxuICogaW1wb3J0IHthZGREYXRhVG9NYXB9IGZyb20gJ0BrZXBsZXIuZ2wvYWN0aW9ucyc7XG4gKiBpbXBvcnQge3Byb2Nlc3NLZXBsZXJnbEpTT059IGZyb20gJ0BrZXBsZXIuZ2wvcHJvY2Vzc29ycyc7XG4gKlxuICogZGlzcGF0Y2goYWRkRGF0YVRvTWFwKHByb2Nlc3NLZXBsZXJnbEpTT04oa2VwbGVyR2xKc29uKSkpO1xuICovXG5leHBvcnQgZnVuY3Rpb24gcHJvY2Vzc0tlcGxlcmdsSlNPTihyYXdEYXRhOiBTYXZlZE1hcCwgc2NoZW1hID0gS2VwbGVyR2xTY2hlbWEpOiBMb2FkZWRNYXAgfCBudWxsIHtcbiAgcmV0dXJuIHJhd0RhdGEgPyBzY2hlbWEubG9hZChyYXdEYXRhLmRhdGFzZXRzLCByYXdEYXRhLmNvbmZpZykgOiBudWxsO1xufVxuXG4vKipcbiAqIFBhcnNlIGEgc2luZ2xlIG9yIGFuIGFycmF5IG9mIGRhdGFzZXRzIHNhdmVkIHVzaW5nIGtlcGxlci5nbCBzY2hlbWFcbiAqIEBwYXJhbSByYXdEYXRhXG4gKiBAcGFyYW0gc2NoZW1hXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm9jZXNzS2VwbGVyZ2xEYXRhc2V0KFxuICByYXdEYXRhOiBvYmplY3QgfCBvYmplY3RbXSxcbiAgc2NoZW1hID0gS2VwbGVyR2xTY2hlbWFcbik6IFBhcnNlZERhdGFzZXQgfCBQYXJzZWREYXRhc2V0W10gfCBudWxsIHtcbiAgaWYgKCFyYXdEYXRhKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cblxuICBjb25zdCByZXN1bHRzID0gc2NoZW1hLnBhcnNlU2F2ZWREYXRhKHRvQXJyYXkocmF3RGF0YSkpO1xuICBpZiAoIXJlc3VsdHMpIHtcbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuICByZXR1cm4gQXJyYXkuaXNBcnJheShyYXdEYXRhKSA/IHJlc3VsdHMgOiByZXN1bHRzWzBdO1xufVxuXG4vKipcbiAqIFBhcnNlIGFycm93IHRhYmxlIGFuZCByZXR1cm4gYSBkYXRhc2V0XG4gKlxuICogQHBhcmFtIGFycm93VGFibGUgQXJyb3dUYWJsZSB0byBwYXJzZSwgc2VlIGxvYWRlcnMuZ2wvc2NoZW1hXG4gKiBAcmV0dXJucyBkYXRhc2V0IGNvbnRhaW5pbmcgYGZpZWxkc2AgYW5kIGByb3dzYCBvciBudWxsXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm9jZXNzQXJyb3dUYWJsZShhcnJvd1RhYmxlOiBBcnJvd1RhYmxlKTogUHJvY2Vzc29yUmVzdWx0IHwgbnVsbCB7XG4gIC8vIEB0cy1pZ25vcmUgLSBVbmtub3duIGRhdGEgdHlwZSBjYXVzaW5nIGJ1aWxkIGZhaWx1cmVzXG4gIHJldHVybiBwcm9jZXNzQXJyb3dCYXRjaGVzKGFycm93VGFibGUuZGF0YS5iYXRjaGVzKTtcbn1cblxuLyoqXG4gKiBFeHRyYWN0cyBHZW9BcnJvdyBtZXRhZGF0YSBmcm9tIGFuIEFwYWNoZSBBcnJvdyB0YWJsZSBzY2hlbWEuXG4gKiBGb3IgZ2VvcGFycXVldCBmaWxlcyBnZW9hcnJvdyBtZXRhZGF0YSBpc24ndCBwcmVzZW50IGluIGZpZWxkcywgc28gZXh0cmFjdCBleHRyYSBpbmZvIGZyb20gc2NoZW1hLlxuICogQHBhcmFtIHRhYmxlIFRoZSBBcGFjaGUgQXJyb3cgdGFibGUgdG8gZXh0cmFjdCBtZXRhZGF0YSBmcm9tLlxuICogQHJldHVybnMgQW4gb2JqZWN0IG1hcHBpbmcgY29sdW1uIG5hbWVzIHRvIHRoZWlyIEdlb0Fycm93IGVuY29kaW5nIHR5cGUuXG4gKiBAdGhyb3dzIExvZ3MgYW4gZXJyb3IgbWVzc2FnZSBpZiBwYXJzaW5nIG9mIG1ldGFkYXRhIGZhaWxzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0R2VvQXJyb3dNZXRhZGF0YUZyb21TY2hlbWEodGFibGU6IGFycm93LlRhYmxlKTogUmVjb3JkPHN0cmluZywgc3RyaW5nPiB7XG4gIGNvbnN0IGdlb0Fycm93TWV0YWRhdGE6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7fTtcbiAgdHJ5IHtcbiAgICBjb25zdCBnZW9TdHJpbmcgPSB0YWJsZS5zY2hlbWEubWV0YWRhdGE/LmdldCgnZ2VvJyk7XG4gICAgaWYgKGdlb1N0cmluZykge1xuICAgICAgY29uc3QgcGFyc2VkR2VvU3RyaW5nID0gSlNPTi5wYXJzZShnZW9TdHJpbmcpO1xuICAgICAgaWYgKHBhcnNlZEdlb1N0cmluZy5jb2x1bW5zKSB7XG4gICAgICAgIE9iamVjdC5rZXlzKHBhcnNlZEdlb1N0cmluZy5jb2x1bW5zKS5mb3JFYWNoKGNvbHVtbk5hbWUgPT4ge1xuICAgICAgICAgIGNvbnN0IGNvbHVtbkRhdGEgPSBwYXJzZWRHZW9TdHJpbmcuY29sdW1uc1tjb2x1bW5OYW1lXTtcbiAgICAgICAgICBpZiAoY29sdW1uRGF0YT8uZW5jb2RpbmcgPT09ICdXS0InKSB7XG4gICAgICAgICAgICBnZW9BcnJvd01ldGFkYXRhW2NvbHVtbk5hbWVdID0gR0VPQVJST1dfRVhURU5TSU9OUy5XS0I7XG4gICAgICAgICAgfVxuICAgICAgICAgIC8vIFRPRE8gcG90ZW50aWFsbHkgdGhlcmUgYXJlIG90aGVyIHR5cGVzIGJ1dCBubyBkYXRhc2V0cyB0byB0ZXN0XG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH1cbiAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICBjb25zb2xlLmVycm9yKCdBbiBlcnJvciBkdXJpbmcgYXJyb3cgdGFibGUgc2NoZW1hIG1ldGFkYXRhIHBhcnNpbmcnKTtcbiAgfVxuICByZXR1cm4gZ2VvQXJyb3dNZXRhZGF0YTtcbn1cblxuLyoqXG4gKiBDb252ZXJ0cyBhbiBBcGFjaGUgQXJyb3cgdGFibGUgc2NoZW1hIGludG8gYW4gYXJyYXkgb2YgS2VwbGVyLmdsIGZpZWxkIG9iamVjdHMuXG4gKiBAcGFyYW0gdGFibGUgVGhlIEFwYWNoZSBBcnJvdyB0YWJsZSB3aG9zZSBzY2hlbWEgbmVlZHMgdG8gYmUgY29udmVydGVkLlxuICogQHBhcmFtIGZpZWxkVHlwZVN1Z2dlc3Rpb25zIE9wdGlvbmFsIG1hcHBpbmcgb2YgZmllbGQgbmFtZXMgdG8gc3VnZ2VzdGVkIGZpZWxkIHR5cGVzLlxuICogQHJldHVybnMgQW4gYXJyYXkgb2YgZmllbGQgb2JqZWN0cyBzdWl0YWJsZSBmb3IgS2VwbGVyLmdsLlxuICovXG5leHBvcnQgZnVuY3Rpb24gYXJyb3dTY2hlbWFUb0ZpZWxkcyhcbiAgdGFibGU6IGFycm93LlRhYmxlLFxuICBmaWVsZFR5cGVTdWdnZXN0aW9uczogUmVjb3JkPHN0cmluZywgc3RyaW5nPiA9IHt9XG4pOiBGaWVsZFtdIHtcbiAgY29uc3QgaGVhZGVyUm93ID0gdGFibGUuc2NoZW1hLmZpZWxkcy5tYXAoZiA9PiBmLm5hbWUpO1xuICBjb25zdCBzYW1wbGUgPSBnZXRTYW1wbGVGb3JUeXBlQW5hbHl6ZUFycm93KHRhYmxlLCBoZWFkZXJSb3cpO1xuICBjb25zdCBrZXBsZXJGaWVsZHMgPSBnZXRGaWVsZHNGcm9tRGF0YShzYW1wbGUsIGhlYWRlclJvdyk7XG4gIGNvbnN0IGdlb0Fycm93TWV0YWRhdGEgPSBnZXRHZW9BcnJvd01ldGFkYXRhRnJvbVNjaGVtYSh0YWJsZSk7XG5cbiAgcmV0dXJuIHRhYmxlLnNjaGVtYS5maWVsZHMubWFwKChmaWVsZDogYXJyb3cuRmllbGQsIGZpZWxkSW5kZXg6IG51bWJlcikgPT4ge1xuICAgIGxldCB0eXBlID0gYXJyb3dEYXRhVHlwZVRvRmllbGRUeXBlKGZpZWxkLnR5cGUpO1xuICAgIGxldCBhbmFseXplclR5cGUgPSBhcnJvd0RhdGFUeXBlVG9BbmFseXplckRhdGFUeXBlKGZpZWxkLnR5cGUpO1xuICAgIGxldCBmb3JtYXQgPSAnJztcblxuICAgIC8vIGdlb21ldHJ5IGZpZWxkcyBwcm9kdWNlZCBieSBEdWNrREIncyBzdF9hc2dlb2pzb24oKVxuICAgIGlmIChmaWVsZFR5cGVTdWdnZXN0aW9uc1tmaWVsZC5uYW1lXSA9PT0gJ0pTT04nKSB7XG4gICAgICB0eXBlID0gQUxMX0ZJRUxEX1RZUEVTLmdlb2pzb247XG4gICAgICBhbmFseXplclR5cGUgPSBBbmFseXplckRBVEFfVFlQRVMuR0VPTUVUUllfRlJPTV9TVFJJTkc7XG4gICAgfSBlbHNlIGlmIChcbiAgICAgIGZpZWxkVHlwZVN1Z2dlc3Rpb25zW2ZpZWxkLm5hbWVdID09PSAnR0VPTUVUUlknIHx8XG4gICAgICBmaWVsZC5tZXRhZGF0YS5nZXQoR0VPQVJST1dfTUVUQURBVEFfS0VZKT8uc3RhcnRzV2l0aCgnZ2VvYXJyb3cnKVxuICAgICkge1xuICAgICAgdHlwZSA9IEFMTF9GSUVMRF9UWVBFUy5nZW9hcnJvdztcbiAgICAgIGFuYWx5emVyVHlwZSA9IEFuYWx5emVyREFUQV9UWVBFUy5HRU9NRVRSWTtcbiAgICB9IGVsc2UgaWYgKGdlb0Fycm93TWV0YWRhdGFbZmllbGQubmFtZV0pIHtcbiAgICAgIHR5cGUgPSBBTExfRklFTERfVFlQRVMuZ2VvYXJyb3c7XG4gICAgICBhbmFseXplclR5cGUgPSBBbmFseXplckRBVEFfVFlQRVMuR0VPTUVUUlk7XG4gICAgICBmaWVsZC5tZXRhZGF0YT8uc2V0KEdFT0FSUk9XX01FVEFEQVRBX0tFWSwgZ2VvQXJyb3dNZXRhZGF0YVtmaWVsZC5uYW1lXSk7XG4gICAgfSBlbHNlIGlmIChmaWVsZFR5cGVTdWdnZXN0aW9uc1tmaWVsZC5uYW1lXSA9PT0gJ0JMT0InKSB7XG4gICAgICAvLyBXaGVuIGFycm93IHdrYiBjb2x1bW4gc2F2ZWQgdG8gRHVja0RCIGFzIEJMT0Igd2l0aG91dCBhbnkgbWV0YWRhdGEsIHRoZW4gcXVlcmllZCBiYWNrXG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBkYXRhID0gdGFibGUuZ2V0Q2hpbGRBdChmaWVsZEluZGV4KT8uZ2V0KDApO1xuICAgICAgICBpZiAoZGF0YSkge1xuICAgICAgICAgIGNvbnN0IGJpbmFyeUdlbyA9IHBhcnNlU3luYyhkYXRhLCBXS0JMb2FkZXIpO1xuICAgICAgICAgIGlmIChiaW5hcnlHZW8pIHtcbiAgICAgICAgICAgIHR5cGUgPSBBTExfRklFTERfVFlQRVMuZ2VvYXJyb3c7XG4gICAgICAgICAgICBhbmFseXplclR5cGUgPSBBbmFseXplckRBVEFfVFlQRVMuR0VPTUVUUlk7XG4gICAgICAgICAgICBmaWVsZC5tZXRhZGF0YT8uc2V0KEdFT0FSUk9XX01FVEFEQVRBX0tFWSwgR0VPQVJST1dfRVhURU5TSU9OUy5XS0IpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgLy8gaWdub3JlLCBub3QgV0tCXG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIFRPRE8gc2hvdWxkIHdlIHVzZSBLZXBsZXIgZ2V0RmllbGRzRnJvbURhdGEgaW5zdGVhZFxuICAgICAgLy8gb2YgYXJyb3dEYXRhVHlwZVRvRmllbGRUeXBlIGZvciBhbGwgZmllbGRzP1xuICAgICAgY29uc3Qga2VwbGVyRmllbGQgPSBrZXBsZXJGaWVsZHNbZmllbGRJbmRleF07XG4gICAgICBpZiAoa2VwbGVyRmllbGQudHlwZSA9PT0gQUxMX0ZJRUxEX1RZUEVTLnRpbWVzdGFtcCkge1xuICAgICAgICB0eXBlID0ga2VwbGVyRmllbGQudHlwZTtcbiAgICAgICAgYW5hbHl6ZXJUeXBlID0ga2VwbGVyRmllbGQuYW5hbHl6ZXJUeXBlO1xuICAgICAgICBmb3JtYXQgPSBrZXBsZXJGaWVsZC5mb3JtYXQ7XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIC4uLmZpZWxkLFxuICAgICAgbmFtZTogZmllbGQubmFtZSxcbiAgICAgIGlkOiBmaWVsZC5uYW1lLFxuICAgICAgZGlzcGxheU5hbWU6IGZpZWxkLm5hbWUsXG4gICAgICBmb3JtYXQ6IGZvcm1hdCxcbiAgICAgIGZpZWxkSWR4OiBmaWVsZEluZGV4LFxuICAgICAgdHlwZSxcbiAgICAgIGFuYWx5emVyVHlwZSxcbiAgICAgIHZhbHVlQWNjZXNzb3I6IChkYzogYW55KSA9PiBkID0+IHtcbiAgICAgICAgcmV0dXJuIGRjLnZhbHVlQXQoZC5pbmRleCwgZmllbGRJbmRleCk7XG4gICAgICB9LFxuICAgICAgbWV0YWRhdGE6IGZpZWxkLm1ldGFkYXRhXG4gICAgfTtcbiAgfSk7XG59XG5cbi8qKlxuICogUGFyc2UgYXJyb3cgYmF0Y2hlcyByZXR1cm5lZCBmcm9tIHBhcnNlSW5CYXRjaGVzKClcbiAqXG4gKiBAcGFyYW0gYXJyb3dUYWJsZSB0aGUgYXJyb3cgdGFibGUgdG8gcGFyc2VcbiAqIEByZXR1cm5zIGRhdGFzZXQgY29udGFpbmluZyBgZmllbGRzYCBhbmQgYHJvd3NgIG9yIG51bGxcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2Nlc3NBcnJvd0JhdGNoZXMoYXJyb3dCYXRjaGVzOiBhcnJvdy5SZWNvcmRCYXRjaFtdKTogUHJvY2Vzc29yUmVzdWx0IHwgbnVsbCB7XG4gIGlmIChhcnJvd0JhdGNoZXMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbiAgY29uc3QgYXJyb3dUYWJsZSA9IG5ldyBhcnJvdy5UYWJsZShhcnJvd0JhdGNoZXMpO1xuICBjb25zdCBmaWVsZHMgPSBhcnJvd1NjaGVtYVRvRmllbGRzKGFycm93VGFibGUpO1xuXG4gIGNvbnN0IGNvbHMgPSBbLi4uQXJyYXkoYXJyb3dUYWJsZS5udW1Db2xzKS5rZXlzKCldLm1hcChpID0+IGFycm93VGFibGUuZ2V0Q2hpbGRBdChpKSk7XG5cbiAgLy8gcmV0dXJuIGVtcHR5IHJvd3MgYW5kIHVzZSByYXcgYXJyb3cgdGFibGUgdG8gY29uc3RydWN0IGNvbHVtbi13aXNlIGRhdGEgY29udGFpbmVyXG4gIHJldHVybiB7XG4gICAgZmllbGRzLFxuICAgIHJvd3M6IFtdLFxuICAgIGNvbHMsXG4gICAgbWV0YWRhdGE6IGFycm93VGFibGUuc2NoZW1hLm1ldGFkYXRhLFxuICAgIC8vIFNhdmUgb3JpZ2luYWwgYXJyb3cgc2NoZW1hLCBmb3IgYmV0dGVyIGluZ2VzdGlvbiBpbnRvIER1Y2tEQi5cbiAgICAvLyBUT0RPIGNvbnNpZGVyIHJldHVybmluZyBhcnJvd1RhYmxlIGluIGNvbHMsIG5vdCBhbiBhcnJheSBvZiBWZWN0b3JzIGZyb20gYXJyb3dUYWJsZS5cbiAgICBhcnJvd1NjaGVtYTogYXJyb3dUYWJsZS5zY2hlbWFcbiAgfTtcbn1cblxuZXhwb3J0IGNvbnN0IERBVEFTRVRfSEFORExFUlMgPSB7XG4gIFtEQVRBU0VUX0ZPUk1BVFMucm93XTogcHJvY2Vzc1Jvd09iamVjdCxcbiAgW0RBVEFTRVRfRk9STUFUUy5nZW9qc29uXTogcHJvY2Vzc0dlb2pzb24sXG4gIFtEQVRBU0VUX0ZPUk1BVFMuY3N2XTogcHJvY2Vzc0NzdkRhdGEsXG4gIFtEQVRBU0VUX0ZPUk1BVFMuYXJyb3ddOiBwcm9jZXNzQXJyb3dUYWJsZSxcbiAgW0RBVEFTRVRfRk9STUFUUy5rZXBsZXJnbF06IHByb2Nlc3NLZXBsZXJnbERhdGFzZXRcbn07XG5cbmV4cG9ydCBjb25zdCBQcm9jZXNzb3JzOiB7XG4gIHByb2Nlc3NHZW9qc29uOiB0eXBlb2YgcHJvY2Vzc0dlb2pzb247XG4gIHByb2Nlc3NDc3ZEYXRhOiB0eXBlb2YgcHJvY2Vzc0NzdkRhdGE7XG4gIHByb2Nlc3NBcnJvd1RhYmxlOiB0eXBlb2YgcHJvY2Vzc0Fycm93VGFibGU7XG4gIHByb2Nlc3N