From 9845babadbeb7f084efabae322194f06f17163bd Mon Sep 17 00:00:00 2001 From: Damian Strong Date: Wed, 9 Dec 2015 12:45:30 -0500 Subject: [PATCH 1/4] Example Unit and E2e tests for all reStart-Angular pieces as well as modified Gulpfile to handle building/executing tests with "gulp karma" and "gulp protractor". may require more configuration to get working on windows machines as the pathing can be an issue for protractor. --- Gulpfile.js | 100 +- karma.conf.js | 79 + package.json | 15 +- protractor.conf.js | 7 + src/assets/css/styles.css | 17 +- src/assets/js/scripts.js | 2 +- src/assets/js/vendor/angular-mock.js | 2470 ++++ src/assets/js/vendor/jasmine.js | 3298 ++++++ src/assets/js/vendor/vendor.js | 9905 +++++++++++++---- src/reStart-app/core/Page.ctrl.js | 146 +- src/reStart-app/core/Page.ctrl.spec.js | 47 + src/reStart-app/core/Page.factory.spec.js | 25 + src/reStart-app/core/Utils.factory.spec.js | 26 + .../core/get-data/JSONData.factory.spec.js | 34 + .../core/get-data/Res.factory.spec.js | 28 + src/reStart-app/core/ui/loading.dir.spec.js | 31 + .../core/ui/trustAsHTML.filter.spec.js | 17 + .../modules/header/Header.ctrl.spec.js | 28 + .../modules/header/header.tpl.html | 4 +- .../modules/header/navControl.dir.js | 1 + .../modules/header/navControl.dir.spec.js | 43 + .../pages/error404/Error404.ctrl.js | 4 + .../pages/error404/Error404.ctrl.spec.js | 32 + src/reStart-app/pages/home/Home.ctrl.js | 12 + src/reStart-app/pages/home/Home.ctrl.spec.js | 43 + src/reStart-app/pages/home/Home.view.html | 4 +- src/reStart-app/pages/sub/Sub.ctrl.spec.js | 25 + src/reStart-app/pages/sub/sample.dir.spec.js | 31 + src/reStart-app/pages/sub/sample.tpl.html | 4 +- src/reStart-app/reStart-app.js | 1434 +-- src/reStart-app/reStart-app.spec.js | 411 + tests/e2e.js | 105 + tests/integration/home.spec.js | 69 + tests/integration/subpage.spec.js | 35 + 34 files changed, 15664 insertions(+), 2868 deletions(-) create mode 100644 karma.conf.js create mode 100644 protractor.conf.js create mode 100644 src/assets/js/vendor/angular-mock.js create mode 100644 src/assets/js/vendor/jasmine.js create mode 100644 src/reStart-app/core/Page.ctrl.spec.js create mode 100644 src/reStart-app/core/Page.factory.spec.js create mode 100644 src/reStart-app/core/Utils.factory.spec.js create mode 100644 src/reStart-app/core/get-data/JSONData.factory.spec.js create mode 100644 src/reStart-app/core/get-data/Res.factory.spec.js create mode 100644 src/reStart-app/core/ui/loading.dir.spec.js create mode 100644 src/reStart-app/core/ui/trustAsHTML.filter.spec.js create mode 100644 src/reStart-app/modules/header/Header.ctrl.spec.js create mode 100644 src/reStart-app/modules/header/navControl.dir.spec.js create mode 100644 src/reStart-app/pages/error404/Error404.ctrl.spec.js create mode 100644 src/reStart-app/pages/home/Home.ctrl.spec.js create mode 100644 src/reStart-app/pages/sub/Sub.ctrl.spec.js create mode 100644 src/reStart-app/pages/sub/sample.dir.spec.js create mode 100644 src/reStart-app/reStart-app.spec.js create mode 100644 tests/e2e.js create mode 100644 tests/integration/home.spec.js create mode 100644 tests/integration/subpage.spec.js diff --git a/Gulpfile.js b/Gulpfile.js index ee06241..252f1ad 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -10,7 +10,11 @@ var gulp = require('gulp'), sourcemaps = require('gulp-sourcemaps'), minifyCSS = require('gulp-minify-css'), autoprefixer = require('gulp-autoprefixer'), - concat = require('gulp-concat'); + concat = require('gulp-concat'), + deleteLines = require('gulp-delete-lines'), + Server = require('karma').Server, + fpath = require('path'), + child_process = require('child_process'); /** * File paths @@ -37,6 +41,10 @@ var path = { jsAngular: { src: basePath.src + '/reStart-app/', dest: basePath.dest + '/reStart-app/' + }, + e2e:{ + src:'./tests/integration/', + dest:'./tests' } }; @@ -126,14 +134,52 @@ function jsVendor() { * Save */ function jsAngular() { - return gulp.src([path.jsAngular.src + 'core/app-setup/app.module.js', path.jsAngular.src + '**/*.js', '!' + path.jsAngular.src + 'reStart-app.js']) - .pipe(sourcemaps.init()) - .pipe(concat('reStart-app.js')) + return gulp.src([path.jsAngular.src + 'core/app-setup/app.module.js', path.jsAngular.src + '**/*.js', '!' + path.jsAngular.src + 'reStart-app.js','!' + path.jsAngular.src + '**/*.spec.js']) + //remove lines marked with //test code if production + .pipe(isProduction ?deleteLines({ + 'filters': [ + /test code/ + ] + }): gutil.noop()) + .pipe(sourcemaps.init()) + .pipe(concat('reStart-app.js')) .pipe(sourcemaps.write()) .pipe(isProduction ? uglify() : gutil.noop() ) .pipe(gulp.dest(path.jsAngular.dest)); } +/** + * function tests() + * + * Init sourcemaps + * Concatenate .spec files + * Write sourcemaps + * Save + */ +function tests() { + return gulp.src([path.jsAngular.src + '**/*.spec.js','!'+path.jsAngular.src+'reStart-app.spec.js']) + .pipe(sourcemaps.init()) + .pipe(concat('reStart-app.spec.js')) + .pipe(sourcemaps.write()) + .pipe(gulp.dest(path.jsAngular.dest)); +} + +/** + * function e2e() + * + * Init sourcemaps + * Concatenate .spec files + * Write sourcemaps + * Save + */ +function e2e() { + return gulp.src([path.e2e.src + '**/*.spec.js']) + .pipe(sourcemaps.init()) + .pipe(concat('e2e.js')) + .pipe(sourcemaps.write()) + .pipe(gulp.dest(path.e2e.dest)); +} + /** * function serve() * @@ -150,6 +196,40 @@ function serve() { } } +/** + * function karma() + * + * Start karma test runner + * Use singleRun false for CI testing + */ + function karma(){ + return gulp.task('karma', function (done) { + new Server({ + configFile: __dirname + '/karma.conf.js', + singleRun: true + }, done).start(); + }); + } + +/** + * function e2eTests + * + * Start protractor and runs e2e tests + */ + function e2eTests(){ + var argv = process.argv.slice(3); // forward args to protractor + return child_process.spawn(getProtractorBinary('protractor'), argv, { + stdio: 'inherit' + })//.once('close', done); + } + function getProtractorBinary(binaryName){ + var winExt = /^win/.test(process.platform)? '.cmd' : ''; + var pkgPath = require.resolve('protractor'); + var protractorDir = fpath.resolve(fpath.join(fpath.dirname(pkgPath), '..', 'bin')); + return fpath.join(protractorDir, '/'+binaryName+winExt); +} + + /** * Gulp tasks */ @@ -158,7 +238,13 @@ gulp.task('styles', styles); gulp.task('js', js); gulp.task('jsVendor', jsVendor); gulp.task('jsAngular', jsAngular); +gulp.task('tests', tests); +gulp.task('e2e', e2e); gulp.task('serve', serve); +//Start karma after files have been rebuilt and test compiled +gulp.task('karma',['js','jsVendor','jsAngular','tests'],karma); +//Start protractor after karma runs +gulp.task('protractor',['e2e','serve'],e2eTests); /** * Default build task @@ -168,11 +254,13 @@ gulp.task('serve', serve); * Use "gulp --prod" to trigger production/build mode from commandline */ -gulp.task('default', ['serve', 'styles', 'jsVendor', 'js', 'jsAngular'], function() { +gulp.task('default', ['serve', 'styles', 'jsVendor', 'js', 'jsAngular','karma','protractor'], function() { if (!isProduction) { gulp.watch(path.css.src + '**/*.scss', ['styles']); gulp.watch([path.jsVendor.src + '**/*.js', '!' + path.jsVendor.src + 'vendor.js'], ['jsVendor']); gulp.watch([path.js.src + '**/*.js', '!' + path.js.src + 'scripts.js', '!' + path.js.src + 'vendor/*'], ['js']); - gulp.watch([path.jsAngular.src + '**/*.js', '!' + path.jsAngular.src + 'reStart-app.js'], ['jsAngular']); + gulp.watch([path.jsAngular.src + '**/*.js', '!' + path.jsAngular.src + 'reStart-app.js', '!' + path.jsAngular.src + '**/*.spec.js'], ['jsAngular']); + gulp.watch([path.jsAngular.src + '**/*.spec.js','!'+ path.jsAngular.src+ 'reStart-app.spec.js'], ['tests','karma']); + gulp.watch([path.e2e.src + '**/*.spec.js'], ['e2e','protractor']); } }); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..c95f313 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,79 @@ +// Karma configuration +// Generated on Tue Dec 01 2015 14:06:19 GMT-0500 (Eastern Standard Time) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'src/assets/js/vendor/vendor.js', + 'src/assets/js/scripts.js', + 'src/reStart-app/reStart-app.js', + 'src/reStart-app/reStart-app.spec.js', + 'src/reStart-app/**/*.html' + ], + + + // list of files to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + "src/reStart-app/**/*.tpl.html": ["ng-html2js"] + }, + + ngHtml2JsPreprocessor: { + stripPrefix:'src/', + // the name of the Angular module to create + moduleName: 'templates' + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultanous + concurrency: Infinity + }) +} diff --git a/package.json b/package.json index 3d42abe..471adaf 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "url": "https://github.com/kmaida/reStart-angular.git" }, "keywords": [ - "angular", "sass", "gulp", "responsive" + "angular", + "sass", + "gulp", + "responsive" ], "license": "GNU Public License", "bugs": { @@ -26,10 +29,18 @@ "gulp-concat": "^2.5.2", "gulp-connect": "^2.2.0", "gulp-debug": "^2.0.1", + "gulp-delete-lines": "0.0.7", "gulp-minify-css": "^1.0.0", + "gulp-protractor": "^2.0.0", "gulp-sass": "^2.0.4", "gulp-sourcemaps": "^1.5.1", "gulp-uglify": "^1.1.0", - "gulp-util": "^3.0.4" + "gulp-util": "^3.0.4", + "jasmine-core": "^2.3.4", + "karma": "^0.13.15", + "karma-chrome-launcher": "^0.2.2", + "karma-jasmine": "^0.3.6", + "karma-ng-html2js-preprocessor": "^0.2.0", + "protractor": "^3.0.0" } } diff --git a/protractor.conf.js b/protractor.conf.js new file mode 100644 index 0000000..8b9192d --- /dev/null +++ b/protractor.conf.js @@ -0,0 +1,7 @@ +exports.config = { + framework: 'jasmine', + //version must match jar file + seleniumServerJar: './node_modules/protractor/selenium/selenium-server-standalone-2.48.2.jar', + //seleniumAddress: 'http://localhost:4444/wd/hub', + specs: ['./tests/e2e.js'] +}; \ No newline at end of file diff --git a/src/assets/css/styles.css b/src/assets/css/styles.css index 1b28c61..c541bc6 100644 --- a/src/assets/css/styles.css +++ b/src/assets/css/styles.css @@ -178,8 +178,8 @@ tr { /*-- Inline link touch targets --*/ .touch p a { - margin: 0 -0.5em; - padding: 0 0.5em; } + margin: 0 -.5em; + padding: 0 .5em; } /*-- Forms --*/ input[type="text"], @@ -210,7 +210,7 @@ button { body { background: #eee; color: #333; - font: normal 16px/1.2 "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif; } + font: normal 16px/1.2 'Helvetica Neue', Helvetica, Arial, Verdana, sans-serif; } .layout-canvas { background: #fff; } @@ -315,7 +315,7 @@ body { background: #fff; } body { color: #000; - font: normal 16px/1.4 Georgia, "Times New Roman", serif; } + font: normal 16px/1.4 Georgia, 'Times New Roman', serif; } /*-- Hidden Elements --*/ .header, .footer { @@ -382,8 +382,7 @@ body { bottom: -9px; } .nav-open .toggle-offcanvas span { background: transparent; } - .nav-open .toggle-offcanvas span:before, - .nav-open .toggle-offcanvas span:after { + .nav-open .toggle-offcanvas span:before, .nav-open .toggle-offcanvas span:after { top: 0; } .nav-open .toggle-offcanvas span:before { -webkit-transform: rotate(45deg); @@ -415,9 +414,7 @@ body { .header .nav-list a { display: block; padding: 6px; } - .header .nav-list a:hover, - .header .nav-list a:active, - .header .nav-list a:focus { + .header .nav-list a:hover, .header .nav-list a:active, .header .nav-list a:focus { text-decoration: none; } @media screen and (min-width: 768px) { .header .nav-list { @@ -481,4 +478,4 @@ body { 404 --------------------*/ -/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlcy5zY3NzIiwiY29yZS9wYXJ0aWFscy9fY29sb3JzLnZhcnMuc2NzcyIsImNvcmUvcGFydGlhbHMvX2xheW91dC52YXJzLnNjc3MiLCJjb3JlL3BhcnRpYWxzL19yZXNwb25zaXZlLnBhcnRpYWwuc2NzcyIsImNvcmUvX2Jhc2Uuc2NzcyIsInN0eWxlcy5jc3MiLCJjb3JlL19mb250cy5zY3NzIiwiY29yZS9fcHJlc2VudGF0aW9uLnNjc3MiLCJjb3JlL19sYXlvdXQuc2NzcyIsImNvcmUvX3ByaW50LnNjc3MiLCJtb2R1bGVzL19oZWFkZXIuc2NzcyIsIm1vZHVsZXMvX25hdi5zY3NzIiwibW9kdWxlcy9fZm9vdGVyLnNjc3MiLCJtb2R1bGVzL19sb2FkaW5nLnNjc3MiLCJwYWdlcy9fNDA0LnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztHQU1FO0FBRUYsZUFBYztBQ1JkOzt1QkFFc0I7QUFFdEIsNEJBQTJCO0FBRTNCLG9EQUFtRDtBQVNuRCx5Q0FBd0M7QUFLeEMsNEJBQTJCO0FDcEIzQjs7dUJBRXNCO0FBRXRCLGtCQUFpQjtBQ0pqQjs7dUJBRXNCO0FBRXRCLG9CQUFtQjtBQUtuQixpQkFBZ0I7QUNUaEI7O3VCQUVzQjtBQVFhOzs7Ozs7O0VBQ2xDLFdBQVU7RUFBRSxZQUFXO0VBQ3ZCLFdBQVU7RUFDVixrQkFBaUI7RUFBRSxjQUFhO0VBQ2hDLHNCQUFxQjtFQUFFLHFCQUFvQjtFQUFFLHNCQUFxQjtFQUNsRSxnQkFBZTtFQUNmLHVCQUFzQjtFQUN0QiwwQkFBeUIsRUFQa0I7O0FBUzVDO0VBQU8sb0JBQW1CO0VBQUUsZ0NBQStCLEVBQXJEOztBQUNGO0VBQUssa0JBQWlCLEVBQWxCOztBQUNSO0VBQVEsMkJBQTBCO0VBQUUsbUJBQWtCLEVBQS9DOztBQUNNO0VBQUssa0JBQWlCO0VBQUUscUJBQW9CLEVBQXhDOztBQUNaO0VBQU0sZ0JBQWUsRUFBaEI7O0FBQ1U7RUFBSyxxQkFBb0IsRUFBckI7O0FBQ2dFO0VBQVUsZ0JBQWUsRUFBaEI7O0FBQ3JGO0VBQ1gsd0JBQXVCLEVBREo7O0FBSXBCOzt1QkFFc0I7QUFFdEIsb0JBQW1CO0FBRW5CO0VBQUssb0JBQW1CLEVBQXBCOztBQUNKO0VBQVMsbUJBQWtCLEVBQW5COztBQUNDO0VBQUksaURBQWdELEVBQWpEOztBQUNZO0VBQUkscUJBQW9CLEVBQXJCOztBQUMzQjtFQUFLLDBCQUF5QjtFQUFFLG9CQUFtQixFQUEvQzs7QUFDSjtFQUFLLDZCQUE0QjtFQUFFLG9CQUFtQixFQUFsRDs7QUFDOEM7RUFBUyxpQkFBZ0IsRUFBakI7O0FBQzFEO0VBQU0sK0JBQThCLEVBQS9COztBQUNpQjtFQUFJLDJCQUEwQjtFQUFFLGNBQWEsRUFBMUM7O0FBQ3JCO0VBQU8sa0JBQWlCO0VBQUUsd0JBQXVCO0VBQUUsK0RBQThEO0VBQUUsYUFBWSxFQUF6SDs7QUFDSztFQUFXLHdCQUF1QjtFQUFFLHNCQUFxQjtFQUFFLG9CQUFtQjtFQUFFLGtCQUFnQixFQUF0Rjs7QUFDbEI7RUFBUyx3QkFBdUIsRUFBeEI7O0FBQ2Y7RUFBVyxnQkFBZSxFQUFoQjs7QUFBb0IsNkRBQTREO0FBQzlFO0VBQU0sZ0JBQWUsRUFBaEI7O0FBQ2pCO0VBQU0sdUJBQXNCLEVBQXZCOztBQUNMO0VBQU0scUJBQW9CLEVBQXJCOztBQUNMO0VBQVEsZ0JBQWU7RUFBRSxjQUFhLEVBQS9COztBQUNQO0VBQVEsK0JIekNZLEVHeUNiOztBQUNQO0VBQUssbUJBQWtCLEVBQW5COztBQUNJO0VBQUssbUJBQWlCLEVBQWxCOztBQUNaO0VBQUssK0JINUNlLEVHNENoQjs7QUFFSixtQkFBa0I7QUFHVDs7RUFDUixjQUFhO0VBQ2IsZ0JBQWUsRUFGQzs7QUFJUjtFQUFTLGFBQVksRUFBYjs7QUFDWjtFQUFZLFNBQVEsRUFBVDs7QUFFaEIsaUVBQWdFO0FDZ0doRTs7Ozs7O0VEeEZDLDBCQUF5QixFQURiOztBQUliLDRCQUEyQjtBQUUzQjtFQUNDLGtCQUFpQjtFQUNqQixtQkFBa0I7RUFDbEIscUJBQW9CLEVBSGhCOztBQU1MLG9DQUFtQztBQUUxQjtFQUNSLGtCQUFlO0VBQ2Ysa0JBQWUsRUFGSjs7QUFLWixnQkFBZTtBQUtmOzs7O0VBQ0MsaUJBQWdCO0VBQUUscUJBQW9CLEVBRDdCOztBQUdJO0VBQ2Isc0NIckZvQjtFR3NGcEIsNENIdEZvQixFR29GTTs7QUFJM0I7RUFDQyxvQkFBbUI7RUFDbkIsdUJBQXNCO0VBQ3RCLG9CQUFtQjtFQUNuQixrQkFBZ0I7RUFDaEIsd0JBQXVCLEVBTGhCOztBRTFHUjs7dUJBRXNCO0FBRXRCLGtDQUFpQztBQ0pqQzs7dUJBRXNCO0FBRXRCO0VBQ0Msa0JOUW1CO0VNUG5CLGFOSXFCO0VNSHJCLCtFQUE2RSxFQUh4RTs7QUFLTjtFQUNDLGtCTkRpQixFTUFGOztBQUloQix5RkFBd0Y7QUFFeEY7RUFDQyxhTFZ3QixFS1NQO0VKSGpCO0lJR0Q7TUFJRSxrQkxaNEIsRUtRWixFQUFBOztBQU9sQjtFQUNDLGlCQUFnQjtFQUNoQixzQkFBb0IsRUFGSDtFSlZqQjtJSVVEO01BS0UsZUFBYztNQUNkLG9CQUFtQixFQU5ILEVBQUE7O0FDdEJsQjs7dUJBRXNCO0FBRXRCO0VBQ0Msa0JBQWlCLEVBRFo7O0FBR047RUFDQyxrQkFBaUI7RUFBRSx1REFBc0QsRUFEeEQ7O0FBSWxCLDBHQUF5RztBQUV6RyxpQ0FBZ0M7QUFFaEM7RUFDQyxxQ0FBNEI7RUFBNUIsNkJBQTRCO0VBQzVCLG9CQUFtQjtFQUNuQixTQUFRO0VBQ1IsYUFBWSxFQUpHO0VMSGY7SUtHRDtNQU9FLDBCQUFpQjtNQUFqQixrQkFBaUI7TUFDakIseUJBQWdCO01BQWhCLHFCQUFnQjtNQUFoQixpQkFBZ0IsRUFSRixFQUFBO0VBVUU7SUFDaEIsa0RBQWdDO0lBQWhDLGtDQUFnQztJQUNoQyx5Q0FBc0I7SUFBdEIsaUNBQXNCLEVBRkg7RUFJTztJQUMxQiw2Q0FBc0I7SUFBdEIscUNBQXNCLEVBRE87RUFHQTtJQUM3QixhQUFZLEVBRG9COztBQUtsQyxpQkFBZ0I7QUx6QmY7RUsyQkQ7SUFFRSxlQUFjLEVBRkssRUFBQTs7QUFNckIscUJBQW9CO0FBRVo7RUFDUCxxQ0FBNEI7RUFBNUIsNkJBQTRCO0VBQzVCLGVBQWM7RUFBRSxxQkFBb0I7RUFDcEMsY0FBYTtFQUNiLGtCQUFpQjtFQUNqQixvQkFBbUI7RUFDbkIsUUFBTztFQUNQLGNBQWEsRUFQQTtFTG5DYjtJS21DTztNQVVOLGdCQUFlO01BQ2Ysb0JBQW1CO01BQ25CLGFBQVksRUFaQSxFQUFBO0VBZUg7O0lBQ1QsZ0JBQWU7SUFBRSxxQkFBb0IsRUFEekI7RUFHSTtJQUNoQiw2Q0FBc0I7SUFBdEIscUNBQXNCLEVBREg7SUxyRHBCO01LcURpQjtRQUlmLHlCQUFnQjtRQUFoQixxQkFBZ0I7UUFBaEIsaUJBQWdCLEVBSkUsRUFBQTtFQU9ZO0lBQy9CLGFBQVksRUFEc0I7RUFHTDtJQUM3QixjQUFhLEVBRG1CO0lML0RqQztNSytEOEI7UUFJNUIsU0FBUSxFQUp1QixFQUFBOztBQzNFbEM7O3VCQUVzQjtBQUV0QjtFQUVDO0lBQ0Msa0JBQWlCLEVBRGY7RUFHSDtJQUNDLGFBQVk7SUFDWix5REFBdUQsRUFGbEQ7RUFLTiwwQkFBeUI7RUFHekI7O0lBQ0MsZUFBYyxFQUROO0VBSVQseUJBQXdCO0VBR3ZCOztJQUNBLGFBQVk7SUFDWiw0QkFBMkIsRUFGakI7RUFLRjs7SUFDUiwrQkFBNEI7SUFDNUIsZ0JBQWUsRUFGQyxFQUFBOztBVFJsQixrQkFBaUI7QVVyQmpCOzt1QkFFc0I7QUFFdEI7RUFDQyxrQlRNcUI7RVNMckIsYVRHaUI7RVNGakIsY0FBYTtFQUNiLHFCQUFvQjtFQUNwQixvQkFBbUIsRUFMQztFQU9uQjtJQUNBLGlCQUFnQjtJQUNoQixtQkFBa0I7SUFDbEIsV0FBVTtJQUNWLHFCQUFtQjtJQUNuQixvQkFBbUI7SUFDbkIsUUFBTztJQUNQLG9CQUFtQjtJQUNuQixhQUFZLEVBUkE7O0FDWGQ7O3VCQUVzQjtBQUV0Qix1R0FBc0c7QUFFdEc7RUFDQyxrQlZJcUI7RVVIckIsa0RWU3FCO0VVUnJCLHVCQUFzQjtFQUN0QixjQUFhO0VBQ2Isc0JBQW9CO0VBQ3BCLG9CQUFtQjtFQUNuQixvQkFBbUI7RUFDbkIsYUFBWTtFQUNaLGNBQWEsRUFUSztFQWFkOzs7SUFDSCxrQlZYZ0I7SVVZaEIsb0JBQW1CO0lBQ25CLGFBQVk7SUFDWixnQkFBZTtJQUNmLGFBQVk7SUFDWixvQkFBbUI7SUFDbkIsMkNBQWlDO0lBQWpDLG1DQUFpQztJQUNqQyxhQUFZLEVBUkQ7RUFXVjtJQUNBLFdBQVUsRUFERDtFQUdUO0lBQ0EsY0FBYSxFQURMO0VBS1Q7SUFDQyx5QkFBd0IsRUFEbkI7SUFJSjs7TUFDQSxRQUFPLEVBREM7SUFHUjtNQUNBLGtDQUFpQjtNQUFqQiw4QkFBaUI7TUFBakIsMEJBQWlCLEVBRFI7SUFHVDtNQUNBLG1DQUFpQjtNQUFqQiwrQkFBaUI7TUFBakIsMkJBQWlCLEVBRFQ7O0FBT1osNEZBQTJGO0FBRW5GO0VBQ1Asc0RBQXNDO0VBQ3RDLGFUckR3QixFU21EWDtFUjdDYjtJUTZDTztNQUtOLGtCVm5Eb0I7TVVvRHBCLGtCQUFpQjtNQUNqQixrQlR6RDRCLEVTa0RoQixFQUFBO0VBVUw7SUFDUCxtQkFBa0IsRUFEVTtJUnZEN0I7TVF1RFE7UUFJTixtRFZ0RG1CLEVVa0RRLEVBQUE7RUFPNUI7SUFDQSxrQkFBaUI7SUFDakIsa0JBQWlCO0lBQ2pCLGlCQUFnQixFQUhUO0lBS1A7TUFDQyxnQkFBZTtNQUNmLGNBQWEsRUFGWDtNQU1EOzs7UUFDQSx1QkFBc0IsRUFEZDtJUnpFWDtNUThEQztRQWlCQyxzQkFBYztRQUFkLHVCQUFjO1FBQWQsc0JBQWM7UUFBZCxlQUFjO1FBQ2QseUJBQWdCO1FBQWhCLHFCQUFnQjtRQUFoQixpQkFBZ0I7UUFDaEIsMEJBQXdCO1FBQXhCLGlDQUF3QjtRQUF4Qix1QkFBd0I7UUFBeEIseUJBQXdCO1FBQ3hCLFlBQVc7UUFDWCxhQUFZLEVBckJOO1FBMEJMOzs7O1VBQ0EsYVY1RmMsRVUyRk47UUFHVDtVQUNDLGlCQUFlLEVBRFosRUFBQTs7QUN2R1A7O3VCQUVzQjtBQUV0QjtFQUNDLGFWQ3dCO0VVQXhCLG9CQUFtQixFQUZYO0VUUVI7SVNSRDtNQUtFLGtCVkY0QixFVUhyQixFQUFBOztBQ0pUOzt1QkFFc0I7QUFHcEI7RUFDQSxjQUFhO0VBQ2IsaUJBQWdCO0VBQ2YsUUFBTztFQUFFLFNBQVE7RUFDbEIsYUFBWTtFQUNaLGNBQWEsRUFMSDs7QUFPVjtFQUNBLHNDQUFnQjtFQUNoQixvQkFBbUI7RUFDbEIsUUFBTztFQUFFLFdBQVU7RUFDcEIsYUFBWSxFQUpGOztBQU1WO0VBQ0EseUJBQXVCO0VBQ3ZCLG9CQUFtQjtFQUNsQixVQUFTO0VBQUUsV0FBVSxFQUhaOztBYlVaLGdCQUFlO0FjNUJmOzt1QkFFc0IiLCJmaWxlIjoic3R5bGVzLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi8qXG4gKiByZVN0YXJ0IEJvaWxlcnBsYXRlIC8gcmVTdGFydCBBbmd1bGFyXG4gKiBBdXRob3I6IEtpbSBNYWlkYSBcbiAqIEF1dGhvciBVUkk6IDxodHRwOi8va2ltLW1haWRhLmNvbT5cbiAqIFNvdXJjZTogPGh0dHBzOi8vZ2l0aHViLmNvbS9rbWFpZGEvcmVTdGFydC1hbmd1bGFyPlxuICogTGljZW5zZTogR05VIFB1YmxpYyBMaWNlbnNlXG4qL1xuXG4vKi0tIENvcmUgLS0qL1xuXG4vLyBwYXJ0aWFsc1xuQGltcG9ydCAnY29yZS9wYXJ0aWFscy9jb2xvcnMudmFycyc7XG5AaW1wb3J0ICdjb3JlL3BhcnRpYWxzL2xheW91dC52YXJzJztcbkBpbXBvcnQgJ2NvcmUvcGFydGlhbHMvcmVzcG9uc2l2ZS5wYXJ0aWFsJztcblxuQGltcG9ydCAnY29yZS9iYXNlJztcbkBpbXBvcnQgJ2NvcmUvZm9udHMnO1xuQGltcG9ydCAnY29yZS9wcmVzZW50YXRpb24nO1xuQGltcG9ydCAnY29yZS9sYXlvdXQnO1xuQGltcG9ydCAnY29yZS9wcmludCc7XG5cbi8qLS0gTW9kdWxlcyAtLSovXG5cbkBpbXBvcnQgJ21vZHVsZXMvaGVhZGVyJztcbkBpbXBvcnQgJ21vZHVsZXMvbmF2JztcbkBpbXBvcnQgJ21vZHVsZXMvZm9vdGVyJztcbkBpbXBvcnQgJ21vZHVsZXMvbG9hZGluZyc7XG5cbi8qLS0gUGFnZXMgLS0qL1xuXG5AaW1wb3J0ICdwYWdlcy80MDQnOyIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0Q09MT1IgVkFSSUFCTEVTXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbi8qLS0gQ29sb3IgZGVmaW5pdGlvbnMgLS0qL1xuXG4vKiBIZXggY29kZSBuYW1lcyBmcm9tIGh0dHA6Ly9uYW1lLW9mLWNvbG9yLmNvbS8gKi9cblxuJGNvbG9yLWJsYWNrOiAjMDAwO1xuJGNvbG9yLXdoaXRlOiAjZmZmO1xuJGNvbG9yLW1pbmVTaGFmdDogIzMzMztcbiRjb2xvci1kYXZ5c0dyYXk6ICM1NTU7XG4kY29sb3ItY2VsZXN0ZTogI2NjYztcbiRjb2xvci1nYWxsZXJ5OiAjZWVlO1xuXG4vKiBDb2xvciBwYXJ0aWFsbHkgZGVmaW5lZCBieSBvcGFjaXR5ICovXG5cbiRjb2xvci1ncmF5LXJnYmE6IHJnYmEoMjU1LDI1NSwyNTUsLjUpO1xuJGNvbG9yLXJlZC1yZ2JhOiByZ2JhKDE2OSw2OCw2NiwuNik7XG5cbi8qLS0gQ29sb3IgYnkgZnVuY3Rpb24gLS0qL1xuXG4kY29sb3ItYmctbGlnaHQ6ICRjb2xvci1nYWxsZXJ5O1xuJGNvbG9yLWJnOiAkY29sb3Itd2hpdGU7XG4kY29sb3ItYm9yZGVyOiAkY29sb3ItY2VsZXN0ZTtcbiRjb2xvci1tb2R1bGUtYmc6ICRjb2xvci1jZWxlc3RlO1xuJGNvbG9yLW5hdi1iZzogJGNvbG9yLWRhdnlzR3JheTtcbiRjb2xvci10ZXh0OiAkY29sb3ItbWluZVNoYWZ0O1xuJGNvbG9yLWxpbmstY29udHJhc3Q6ICRjb2xvci13aGl0ZTtcbiRjb2xvci1kaXNhYmxlZC1yZ2JhOiAkY29sb3ItZ3JheS1yZ2JhO1xuJGNvbG9yLWludmFsaWQtcmdiYTogJGNvbG9yLXJlZC1yZ2JhOyIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0TEFZT1VUIFZBUklBQkxFU1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG4vKi0tIFBhZGRpbmcgLS0qL1xuXG4kcGFkZGluZy1zY3JlZW4tc21hbGw6IDMlO1xuJHBhZGRpbmctc2NyZWVuLWxhcmdlOiAxLjUlIDMlOyIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICBSRVNQT05TSVZFXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbi8qLS0gVmFyaWFibGVzIC0tKi9cblxuJG1xLXNtYWxsOiAnc2NyZWVuIGFuZCAobWF4LXdpZHRoOiA3NjdweCknO1xuJG1xLWxhcmdlOiAnc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCknO1xuXG4vKi0tIE1peGlucyAtLSovXG5cbkBtaXhpbiBtcSgkbXFTdHJpbmcpIHtcblx0QG1lZGlhICN7JG1xU3RyaW5nfSB7XG5cdFx0QGNvbnRlbnQ7XG5cdH1cbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgQ1NTIFJFU0VUXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmh0bWwsIGJvZHksIGRpdiwgc3BhbiwgaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgcCwgYmxvY2txdW90ZSwgcHJlLFxuYSwgYWJiciwgYWNyb255bSwgYWRkcmVzcywgYmlnLCBjaXRlLCBjb2RlLCBkZWwsIGRmbiwgZW0sIGltZywgaW5zLFxua2JkLCBxLCBzLCBzYW1wLCBzbWFsbCwgc3Ryb25nLCB0dCwgdmFyLCBkbCwgZHQsXG5kZCwgb2wsIHVsLCBsaSwgZmllbGRzZXQsIGZvcm0sIGlucHV0LCBidXR0b24sIHRleHRhcmVhLCBsYWJlbCwgbGVnZW5kLCB0YWJsZSwgY2FwdGlvbixcbnRib2R5LCB0Zm9vdCwgdGhlYWQsIHRyLCB0aCwgdGQsXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsXG5mb290ZXIsIGhlYWRlciwgaGdyb3VwLCBtZW51LCBuYXYsIHNlY3Rpb24ge1xuXHRtYXJnaW46IDA7IHBhZGRpbmc6IDA7XG5cdGJvcmRlcjogMDtcblx0Ym9yZGVyLXJhZGl1czogMDtcdC8qIGZvciBpT1MgKi9cblx0Zm9udC13ZWlnaHQ6IGluaGVyaXQ7IGZvbnQtc3R5bGU6IGluaGVyaXQ7IGZvbnQtZmFtaWx5OiBpbmhlcml0O1xuXHRsaW5lLWhlaWdodDogMTtcblx0dGV4dC1kZWNvcmF0aW9uOiBub25lO1xuXHR2ZXJ0aWNhbC1hbGlnbjogYmFzZWxpbmU7XG59XG5odG1sIHsgb3ZlcmZsb3cteTogc2Nyb2xsOyAtd2Via2l0LXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IH1cbm9sLCB1bCB7IGxpc3Qtc3R5bGU6IG5vbmU7IH1cbnRhYmxlIHsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgYm9yZGVyLXNwYWNpbmc6IDA7IH1cbmNhcHRpb24sIHRoLCB0ZCB7IHRleHQtYWxpZ246IGxlZnQ7IGZvbnQtd2VpZ2h0OiBub3JtYWw7IH1cbnN1cCwgc3ViIHsgbGluZS1oZWlnaHQ6IDE7IH1cbmgxLCBoMiwgaDMsIGg0LCBoNSwgaDYgeyBmb250LXdlaWdodDogbm9ybWFsOyB9XG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsIGZvb3RlciwgaGVhZGVyLCBoZ3JvdXAsIG1lbnUsIG5hdiwgc2VjdGlvbiB7IGRpc3BsYXk6IGJsb2NrOyB9XG4qLCo6YmVmb3JlLCo6YWZ0ZXIge1xuXHRib3gtc2l6aW5nOiBib3JkZXItYm94O1xufVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgIEJBU0lDU1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG4vKi0tIE5vcm1hbGl6ZSAtLSovXG5cbmVtIHsgZm9udC1zdHlsZTogaXRhbGljOyB9XG5zdHJvbmcgeyBmb250LXdlaWdodDogYm9sZDsgfVxucTpsYW5nKGVuKSB7IHF1b3RlczogJyYjODIyMDsnICcmIzgyMjE7JyAnJiM4MjE2OycgJyYjODIxNzsnOyB9XG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBwIHsgbWFyZ2luLWJvdHRvbTogMTVweDsgfVxudWwgeyBsaXN0LXN0eWxlOiBvdXRzaWRlIGRpc2M7IHBhZGRpbmctbGVmdDogMjRweDsgfVxub2wgeyBsaXN0LXN0eWxlOiBvdXRzaWRlIGRlY2ltYWw7IHBhZGRpbmctbGVmdDogMjRweDsgfVxuYSwgbGFiZWwsIGlucHV0W3R5cGU9YnV0dG9uXSwgaW5wdXRbdHlwZT1zdWJtaXRdLCBidXR0b24geyBjdXJzb3I6IHBvaW50ZXI7IH1cbmRlbCB7IHRleHQtZGVjb3JhdGlvbjogbGluZS10aHJvdWdoOyB9XG5hYmJyW3RpdGxlXSwgZGZuW3RpdGxlXSB7IGJvcmRlci1ib3R0b206IDFweCBkb3R0ZWQ7IGN1cnNvcjogaGVscDsgfVxucHJlLGNvZGUgeyBiYWNrZ3JvdW5kOiAjZWVlOyBib3JkZXI6IDFweCBzb2xpZCAjY2NjOyBmb250LWZhbWlseTogQ29uc29sYXMsICdMdWNpZGEgQ29uc29sZScsICdDb3VyaWVyIE5ldycsIHNlcmlmOyBwYWRkaW5nOiAyJTsgfVxuaW5wdXQsIHNlbGVjdCwgdGV4dGFyZWEgeyBib3JkZXI6IDFweCBzb2xpZCAjY2NjOyBmb250LWZhbWlseTogaW5oZXJpdDsgZm9udC1zaXplOiBpbmhlcml0OyBwYWRkaW5nOiAzcHggNnB4OyB9XG5pbnB1dCwgc2VsZWN0IHsgdmVydGljYWwtYWxpZ246IG1pZGRsZTsgfVxudGV4dGFyZWEgeyBvdmVyZmxvdzogYXV0bzsgfSAvKiBwcmV2ZW50cyBzY3JvbGxiYXIgZnJvbSBzaG93aW5nIHVwIHdoZW4gdW5uZWVkZWQgaW4gSUUgKi9cbnNtYWxsLCBzdXAsIHN1YiB7IGZvbnQtc2l6ZTogODUlOyB9XG5zdXAgeyB2ZXJ0aWNhbC1hbGlnbjogc3VwZXI7IH1cbnN1YiB7IHZlcnRpY2FsLWFsaWduOiBzdWI7IH1cbnRhYmxlIHsgbWFyZ2luOiAxMHB4IDA7IHBhZGRpbmc6IDNweDsgfVxudGhlYWQgeyBib3JkZXItYm90dG9tOiAycHggc29saWQgJGNvbG9yLWJvcmRlcjsgfVxudGggeyBmb250LXdlaWdodDogYm9sZDsgfVxudGgsIHRyLCB0ZCB7IHBhZGRpbmc6IDRweCAxMnB4OyB9XG50ciB7IGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAkY29sb3ItYm9yZGVyfVxuXG4vKi0tIENsZWFyZml4IC0tKi9cblxuLmNsZWFyZml4OmJlZm9yZSxcbi5jbGVhcmZpeDphZnRlciB7XG5cdGNvbnRlbnQ6IFwiIFwiO1xuXHRkaXNwbGF5OiB0YWJsZTtcbn1cbi5jbGVhcmZpeDphZnRlciB7IGNsZWFyOiBib3RoOyB9XG4uaWU3IC5jbGVhcmZpeCB7IHpvb206IDE7IH1cblxuLyotLSBuZy1jbG9hazogcHJldmVudCBGT1VDIGJlZm9yZSBBbmd1bGFyIEphdmFTY3JpcHQgbG9hZHMgLS0qL1xuXG5bbmdcXDpjbG9ha10sXG5bbmctY2xvYWtdLFxuW2RhdGEtbmctY2xvYWtdLFxuW3gtbmctY2xvYWtdLFxuLm5nLWNsb2FrLFxuLngtbmctY2xvYWsge1xuXHRkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7XG59XG5cbi8qLS0gSW1hZ2UgUmVwbGFjZW1lbnQgLS0qL1xuXG4uaXIge1xuXHRvdmVyZmxvdzogaGlkZGVuO1xuXHR0ZXh0LWluZGVudDogMjAwJTtcblx0d2hpdGUtc3BhY2U6IG5vd3JhcDtcbn1cblxuLyotLSBJbmxpbmUgbGluayB0b3VjaCB0YXJnZXRzIC0tKi9cblxuLnRvdWNoIHAgYSB7XG5cdG1hcmdpbjogMCAtLjVlbTtcblx0cGFkZGluZzogMCAuNWVtO1xufVxuXG4vKi0tIEZvcm1zIC0tKi9cblxuaW5wdXRbdHlwZT1cInRleHRcIl0sXG5pbnB1dFt0eXBlPVwibnVtYmVyXCJdLFxuaW5wdXRbdHlwZT1cInBhc3N3b3JkXCJdLFxudGV4dGFyZWEge1xuXHRmb250LXNpemU6IDE2cHg7XHQvKiBmb3IgaU9TIHBob25lcyAqL1xufVxuaW5wdXQubmctZGlydHkubmctaW52YWxpZCB7XG5cdGJvcmRlci1jb2xvcjogJGNvbG9yLWludmFsaWQtcmdiYTtcblx0Ym94LXNoYWRvdzogMCAwIDZweCAkY29sb3ItaW52YWxpZC1yZ2JhO1xufVxuYnV0dG9uIHtcblx0Ym9yZGVyLXJhZGl1czogNHB4O1xuXHRkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG5cdGZvbnQtc2l6ZTogaW5oZXJpdDtcblx0cGFkZGluZzogNXB4IDRweDtcblx0dmVydGljYWwtYWxpZ246IG1pZGRsZTtcbn0iLCIvKlxuICogcmVTdGFydCBCb2lsZXJwbGF0ZSAvIHJlU3RhcnQgQW5ndWxhclxuICogQXV0aG9yOiBLaW0gTWFpZGEgXG4gKiBBdXRob3IgVVJJOiA8aHR0cDovL2tpbS1tYWlkYS5jb20+XG4gKiBTb3VyY2U6IDxodHRwczovL2dpdGh1Yi5jb20va21haWRhL3JlU3RhcnQtYW5ndWxhcj5cbiAqIExpY2Vuc2U6IEdOVSBQdWJsaWMgTGljZW5zZVxuKi9cbi8qLS0gQ29yZSAtLSovXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdENPTE9SIFZBUklBQkxFU1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBDb2xvciBkZWZpbml0aW9ucyAtLSovXG4vKiBIZXggY29kZSBuYW1lcyBmcm9tIGh0dHA6Ly9uYW1lLW9mLWNvbG9yLmNvbS8gKi9cbi8qIENvbG9yIHBhcnRpYWxseSBkZWZpbmVkIGJ5IG9wYWNpdHkgKi9cbi8qLS0gQ29sb3IgYnkgZnVuY3Rpb24gLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuXHRMQVlPVVQgVkFSSUFCTEVTXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4vKi0tIFBhZGRpbmcgLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuXHQgIFJFU1BPTlNJVkVcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbi8qLS0gVmFyaWFibGVzIC0tKi9cbi8qLS0gTWl4aW5zIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICBDU1MgUkVTRVRcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmh0bWwsIGJvZHksIGRpdiwgc3BhbiwgaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgcCwgYmxvY2txdW90ZSwgcHJlLFxuYSwgYWJiciwgYWNyb255bSwgYWRkcmVzcywgYmlnLCBjaXRlLCBjb2RlLCBkZWwsIGRmbiwgZW0sIGltZywgaW5zLFxua2JkLCBxLCBzLCBzYW1wLCBzbWFsbCwgc3Ryb25nLCB0dCwgdmFyLCBkbCwgZHQsXG5kZCwgb2wsIHVsLCBsaSwgZmllbGRzZXQsIGZvcm0sIGlucHV0LCBidXR0b24sIHRleHRhcmVhLCBsYWJlbCwgbGVnZW5kLCB0YWJsZSwgY2FwdGlvbixcbnRib2R5LCB0Zm9vdCwgdGhlYWQsIHRyLCB0aCwgdGQsXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsXG5mb290ZXIsIGhlYWRlciwgaGdyb3VwLCBtZW51LCBuYXYsIHNlY3Rpb24ge1xuICBtYXJnaW46IDA7XG4gIHBhZGRpbmc6IDA7XG4gIGJvcmRlcjogMDtcbiAgYm9yZGVyLXJhZGl1czogMDtcbiAgLyogZm9yIGlPUyAqL1xuICBmb250LXdlaWdodDogaW5oZXJpdDtcbiAgZm9udC1zdHlsZTogaW5oZXJpdDtcbiAgZm9udC1mYW1pbHk6IGluaGVyaXQ7XG4gIGxpbmUtaGVpZ2h0OiAxO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gIHZlcnRpY2FsLWFsaWduOiBiYXNlbGluZTsgfVxuXG5odG1sIHtcbiAgb3ZlcmZsb3cteTogc2Nyb2xsO1xuICAtd2Via2l0LXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IH1cblxub2wsIHVsIHtcbiAgbGlzdC1zdHlsZTogbm9uZTsgfVxuXG50YWJsZSB7XG4gIGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7XG4gIGJvcmRlci1zcGFjaW5nOiAwOyB9XG5cbmNhcHRpb24sIHRoLCB0ZCB7XG4gIHRleHQtYWxpZ246IGxlZnQ7XG4gIGZvbnQtd2VpZ2h0OiBub3JtYWw7IH1cblxuc3VwLCBzdWIge1xuICBsaW5lLWhlaWdodDogMTsgfVxuXG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2IHtcbiAgZm9udC13ZWlnaHQ6IG5vcm1hbDsgfVxuXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsIGZvb3RlciwgaGVhZGVyLCBoZ3JvdXAsIG1lbnUsIG5hdiwgc2VjdGlvbiB7XG4gIGRpc3BsYXk6IGJsb2NrOyB9XG5cbiosICo6YmVmb3JlLCAqOmFmdGVyIHtcbiAgYm94LXNpemluZzogYm9yZGVyLWJveDsgfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgIEJBU0lDU1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBOb3JtYWxpemUgLS0qL1xuZW0ge1xuICBmb250LXN0eWxlOiBpdGFsaWM7IH1cblxuc3Ryb25nIHtcbiAgZm9udC13ZWlnaHQ6IGJvbGQ7IH1cblxucTpsYW5nKGVuKSB7XG4gIHF1b3RlczogJyYjODIyMDsnICcmIzgyMjE7JyAnJiM4MjE2OycgJyYjODIxNzsnOyB9XG5cbmgxLCBoMiwgaDMsIGg0LCBoNSwgaDYsIHAge1xuICBtYXJnaW4tYm90dG9tOiAxNXB4OyB9XG5cbnVsIHtcbiAgbGlzdC1zdHlsZTogb3V0c2lkZSBkaXNjO1xuICBwYWRkaW5nLWxlZnQ6IDI0cHg7IH1cblxub2wge1xuICBsaXN0LXN0eWxlOiBvdXRzaWRlIGRlY2ltYWw7XG4gIHBhZGRpbmctbGVmdDogMjRweDsgfVxuXG5hLCBsYWJlbCwgaW5wdXRbdHlwZT1idXR0b25dLCBpbnB1dFt0eXBlPXN1Ym1pdF0sIGJ1dHRvbiB7XG4gIGN1cnNvcjogcG9pbnRlcjsgfVxuXG5kZWwge1xuICB0ZXh0LWRlY29yYXRpb246IGxpbmUtdGhyb3VnaDsgfVxuXG5hYmJyW3RpdGxlXSwgZGZuW3RpdGxlXSB7XG4gIGJvcmRlci1ib3R0b206IDFweCBkb3R0ZWQ7XG4gIGN1cnNvcjogaGVscDsgfVxuXG5wcmUsIGNvZGUge1xuICBiYWNrZ3JvdW5kOiAjZWVlO1xuICBib3JkZXI6IDFweCBzb2xpZCAjY2NjO1xuICBmb250LWZhbWlseTogQ29uc29sYXMsICdMdWNpZGEgQ29uc29sZScsICdDb3VyaWVyIE5ldycsIHNlcmlmO1xuICBwYWRkaW5nOiAyJTsgfVxuXG5pbnB1dCwgc2VsZWN0LCB0ZXh0YXJlYSB7XG4gIGJvcmRlcjogMXB4IHNvbGlkICNjY2M7XG4gIGZvbnQtZmFtaWx5OiBpbmhlcml0O1xuICBmb250LXNpemU6IGluaGVyaXQ7XG4gIHBhZGRpbmc6IDNweCA2cHg7IH1cblxuaW5wdXQsIHNlbGVjdCB7XG4gIHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7IH1cblxudGV4dGFyZWEge1xuICBvdmVyZmxvdzogYXV0bzsgfVxuXG4vKiBwcmV2ZW50cyBzY3JvbGxiYXIgZnJvbSBzaG93aW5nIHVwIHdoZW4gdW5uZWVkZWQgaW4gSUUgKi9cbnNtYWxsLCBzdXAsIHN1YiB7XG4gIGZvbnQtc2l6ZTogODUlOyB9XG5cbnN1cCB7XG4gIHZlcnRpY2FsLWFsaWduOiBzdXBlcjsgfVxuXG5zdWIge1xuICB2ZXJ0aWNhbC1hbGlnbjogc3ViOyB9XG5cbnRhYmxlIHtcbiAgbWFyZ2luOiAxMHB4IDA7XG4gIHBhZGRpbmc6IDNweDsgfVxuXG50aGVhZCB7XG4gIGJvcmRlci1ib3R0b206IDJweCBzb2xpZCAjY2NjOyB9XG5cbnRoIHtcbiAgZm9udC13ZWlnaHQ6IGJvbGQ7IH1cblxudGgsIHRyLCB0ZCB7XG4gIHBhZGRpbmc6IDRweCAxMnB4OyB9XG5cbnRyIHtcbiAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNjY2M7IH1cblxuLyotLSBDbGVhcmZpeCAtLSovXG4uY2xlYXJmaXg6YmVmb3JlLFxuLmNsZWFyZml4OmFmdGVyIHtcbiAgY29udGVudDogXCIgXCI7XG4gIGRpc3BsYXk6IHRhYmxlOyB9XG5cbi5jbGVhcmZpeDphZnRlciB7XG4gIGNsZWFyOiBib3RoOyB9XG5cbi5pZTcgLmNsZWFyZml4IHtcbiAgem9vbTogMTsgfVxuXG4vKi0tIG5nLWNsb2FrOiBwcmV2ZW50IEZPVUMgYmVmb3JlIEFuZ3VsYXIgSmF2YVNjcmlwdCBsb2FkcyAtLSovXG5bbmdcXDpjbG9ha10sXG5bbmctY2xvYWtdLFxuW2RhdGEtbmctY2xvYWtdLFxuW3gtbmctY2xvYWtdLFxuLm5nLWNsb2FrLFxuLngtbmctY2xvYWsge1xuICBkaXNwbGF5OiBub25lICFpbXBvcnRhbnQ7IH1cblxuLyotLSBJbWFnZSBSZXBsYWNlbWVudCAtLSovXG4uaXIge1xuICBvdmVyZmxvdzogaGlkZGVuO1xuICB0ZXh0LWluZGVudDogMjAwJTtcbiAgd2hpdGUtc3BhY2U6IG5vd3JhcDsgfVxuXG4vKi0tIElubGluZSBsaW5rIHRvdWNoIHRhcmdldHMgLS0qL1xuLnRvdWNoIHAgYSB7XG4gIG1hcmdpbjogMCAtMC41ZW07XG4gIHBhZGRpbmc6IDAgMC41ZW07IH1cblxuLyotLSBGb3JtcyAtLSovXG5pbnB1dFt0eXBlPVwidGV4dFwiXSxcbmlucHV0W3R5cGU9XCJudW1iZXJcIl0sXG5pbnB1dFt0eXBlPVwicGFzc3dvcmRcIl0sXG50ZXh0YXJlYSB7XG4gIGZvbnQtc2l6ZTogMTZweDtcbiAgLyogZm9yIGlPUyBwaG9uZXMgKi8gfVxuXG5pbnB1dC5uZy1kaXJ0eS5uZy1pbnZhbGlkIHtcbiAgYm9yZGVyLWNvbG9yOiByZ2JhKDE2OSwgNjgsIDY2LCAwLjYpO1xuICBib3gtc2hhZG93OiAwIDAgNnB4IHJnYmEoMTY5LCA2OCwgNjYsIDAuNik7IH1cblxuYnV0dG9uIHtcbiAgYm9yZGVyLXJhZGl1czogNHB4O1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gIGZvbnQtc2l6ZTogaW5oZXJpdDtcbiAgcGFkZGluZzogNXB4IDRweDtcbiAgdmVydGljYWwtYWxpZ246IG1pZGRsZTsgfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgIEZPTlRTXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4vKiBGdWxsIEBmb250LWZhY2UgZGVjbGFyYXRpb24gKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0IFBSRVNFTlRBVElPTlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuYm9keSB7XG4gIGJhY2tncm91bmQ6ICNlZWU7XG4gIGNvbG9yOiAjMzMzO1xuICBmb250OiBub3JtYWwgMTZweC8xLjIgXCJIZWx2ZXRpY2EgTmV1ZVwiLCBIZWx2ZXRpY2EsIEFyaWFsLCBWZXJkYW5hLCBzYW5zLXNlcmlmOyB9XG5cbi5sYXlvdXQtY2FudmFzIHtcbiAgYmFja2dyb3VuZDogI2ZmZjsgfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gQ29udGVudCAtLSovXG4uY29udGVudC13cmFwcGVyIHtcbiAgcGFkZGluZzogMyU7IH1cbiAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAuY29udGVudC13cmFwcGVyIHtcbiAgICAgIHBhZGRpbmc6IDEuNSUgMyU7IH0gfVxuXG4uY29udGVudC1oZWFkaW5nIHtcbiAgZm9udC1zaXplOiAzNnB4O1xuICBtYXJnaW46IC0xLjUlIDAgMjBweDsgfVxuICBAbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAgIC5jb250ZW50LWhlYWRpbmcge1xuICAgICAgbWFyZ2luLXRvcDogMDtcbiAgICAgIHRleHQtYWxpZ246IGNlbnRlcjsgfSB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiBMQVlPVVQgRlVOQ1RJT05BTElUWVxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuYm9keSB7XG4gIG1pbi13aWR0aDogMzIwcHg7IH1cblxuLmxheW91dC1vdmVyZmxvdyB7XG4gIG92ZXJmbG93OiBoaWRkZW47XG4gIC8qIG5lY2Vzc2FyeSB0byBoYW5kbGUgb2ZmY2FudmFzIHNjcm9sbGJhciBiZWhhdmlvciAqLyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBPZmYtY2FudmFzIEZ1bmN0aW9uYWxpdHkgLS0qL1xuLyotLSBDb250ZW50IGNhbnZhcyB3cmFwcGVyIC0tKi9cbi5sYXlvdXQtY2FudmFzIHtcbiAgYmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlOyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmxheW91dC1jYW52YXMge1xuICAgICAgdHJhbnNpdGlvbjogbm9uZTtcbiAgICAgIHRyYW5zZm9ybTogbm9uZTsgfSB9XG4gIC5jc3N0cmFuc2Zvcm1zM2QgLmxheW91dC1jYW52YXMge1xuICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAyNTBtcyBlYXNlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgMCwgMCk7IH1cbiAgLmNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gLmxheW91dC1jYW52YXMge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMjcwcHgsIDAsIDApOyB9XG4gIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5sYXlvdXQtY2FudmFzIHtcbiAgICBsZWZ0OiAyNzBweDsgfVxuXG4vKi0tIEhlYWRlciAtLSovXG5AbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAuaGVhZGVyLW1vYmlsZS1wYWdlIHtcbiAgICBkaXNwbGF5OiBub25lOyB9IH1cblxuLyotLSBOYXZpZ2F0aW9uIC0tKi9cbi5oZWFkZXIgLm5hdiB7XG4gIGJhY2tmYWNlLXZpc2liaWxpdHk6IGhpZGRlbjtcbiAgZGlzcGxheTogbm9uZTtcbiAgLyogZGVhbCB3aXRoIEZPVUMgKi9cbiAgaGVpZ2h0OiAxMDAlO1xuICBvdmVyZmxvdy15OiBhdXRvO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgd2lkdGg6IDI3MHB4OyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmhlYWRlciAubmF2IHtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgICAgd2lkdGg6IDEwMCU7IH0gfVxuICAubmF2LWNsb3NlZCAuaGVhZGVyIC5uYXYsXG4gIC5uYXYtb3BlbiAuaGVhZGVyIC5uYXYge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIC8qIGRlYWwgd2l0aCBGT1VDICovIH1cbiAgLmNzc3RyYW5zZm9ybXMzZCAuaGVhZGVyIC5uYXYge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoLTEwMCUsIDAsIDApOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5jc3N0cmFuc2Zvcm1zM2QgLmhlYWRlciAubmF2IHtcbiAgICAgICAgdHJhbnNmb3JtOiBub25lOyB9IH1cbiAgLm5vLWNzc3RyYW5zZm9ybXMzZCAubmF2LWNsb3NlZCAuaGVhZGVyIC5uYXYge1xuICAgIGxlZnQ6IC0xMDAlOyB9XG4gIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5oZWFkZXIgLm5hdiB7XG4gICAgbGVmdDogLTI3MHB4OyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5oZWFkZXIgLm5hdiB7XG4gICAgICAgIGxlZnQ6IDA7IH0gfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgIFBSSU5UXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5AbWVkaWEgcHJpbnQge1xuICAqIHtcbiAgICBiYWNrZ3JvdW5kOiAjZmZmOyB9XG4gIGJvZHkge1xuICAgIGNvbG9yOiAjMDAwO1xuICAgIGZvbnQ6IG5vcm1hbCAxNnB4LzEuNCBHZW9yZ2lhLCBcIlRpbWVzIE5ldyBSb21hblwiLCBzZXJpZjsgfVxuICAvKi0tIEhpZGRlbiBFbGVtZW50cyAtLSovXG4gIC5oZWFkZXIsXG4gIC5mb290ZXIge1xuICAgIGRpc3BsYXk6IG5vbmU7IH1cbiAgLyotLSBTaG93IGxpbmsgVVJMcyAtLSovXG4gIGE6bGluayxcbiAgYTp2aXNpdGVkIHtcbiAgICBjb2xvcjogYmx1ZTtcbiAgICB0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsgfVxuICBhOmxpbms6YWZ0ZXIsXG4gIGE6dmlzaXRlZDphZnRlciB7XG4gICAgY29udGVudDogXCIgW1wiIGF0dHIoaHJlZikgXCJdIFwiO1xuICAgIGZvbnQtc2l6ZTogNzUlOyB9IH1cblxuLyotLSBNb2R1bGVzIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgIEhFQURFUlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLmhlYWRlci1tb2JpbGUtcGFnZSB7XG4gIGJhY2tncm91bmQ6ICM1NTU7XG4gIGNvbG9yOiAjZmZmO1xuICBoZWlnaHQ6IDUwcHg7XG4gIG1hcmdpbi1ib3R0b206IDEwcHg7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTsgfVxuICAuaGVhZGVyLW1vYmlsZS1wYWdlLXNpdGVUaXRsZSB7XG4gICAgZm9udC1zaXplOiAzMHB4O1xuICAgIGxpbmUtaGVpZ2h0OiA1MHB4O1xuICAgIG1hcmdpbjogMDtcbiAgICBwYWRkaW5nOiAwIDAgMCA1MHB4O1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6IDA7XG4gICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgIHdpZHRoOiAxMDAlOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0IE5BVklHQVRJT05cbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBIYW1idXJnZXIgbWVudSB0b2dnbGUgLS0qL1xuLnRvZ2dsZS1vZmZjYW52YXMge1xuICBiYWNrZ3JvdW5kOiAjNTU1O1xuICBib3JkZXItcmlnaHQ6IDFweCBzb2xpZCByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgaGVpZ2h0OiA1MHB4O1xuICBwYWRkaW5nOiAyMy41cHggMTNweDtcbiAgcG9zaXRpb246IHJlbGF0aXZlO1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIHdpZHRoOiA1MHB4O1xuICB6LWluZGV4OiAxMDA7IH1cbiAgLnRvZ2dsZS1vZmZjYW52YXMgc3BhbixcbiAgLnRvZ2dsZS1vZmZjYW52YXMgc3BhbjpiZWZvcmUsXG4gIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YWZ0ZXIge1xuICAgIGJhY2tncm91bmQ6ICNmZmY7XG4gICAgYm9yZGVyLXJhZGl1czogMXB4O1xuICAgIGNvbnRlbnQ6ICcnO1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIGhlaWdodDogM3B4O1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0cmFuc2l0aW9uOiBhbGwgMjUwbXMgZWFzZS1pbi1vdXQ7XG4gICAgd2lkdGg6IDI0cHg7IH1cbiAgLnRvZ2dsZS1vZmZjYW52YXMgc3BhbjpiZWZvcmUge1xuICAgIHRvcDogLTlweDsgfVxuICAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmFmdGVyIHtcbiAgICBib3R0b206IC05cHg7IH1cbiAgLm5hdi1vcGVuIC50b2dnbGUtb2ZmY2FudmFzIHNwYW4ge1xuICAgIGJhY2tncm91bmQ6IHRyYW5zcGFyZW50OyB9XG4gICAgLm5hdi1vcGVuIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YmVmb3JlLFxuICAgIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmFmdGVyIHtcbiAgICAgIHRvcDogMDsgfVxuICAgIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmJlZm9yZSB7XG4gICAgICB0cmFuc2Zvcm06IHJvdGF0ZSg0NWRlZyk7IH1cbiAgICAubmF2LW9wZW4gLnRvZ2dsZS1vZmZjYW52YXMgc3BhbjphZnRlciB7XG4gICAgICB0cmFuc2Zvcm06IHJvdGF0ZSgtNDVkZWcpOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBOYXZpZ2F0aW9uIC0tKi9cbi5oZWFkZXIgLm5hdiB7XG4gIGJveC1zaGFkb3c6IGluc2V0IC04cHggMCA4cHggLTZweCByZ2JhKDAsIDAsIDAsIDAuMik7XG4gIHBhZGRpbmc6IDMlOyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmhlYWRlciAubmF2IHtcbiAgICAgIGJhY2tncm91bmQ6ICM1NTU7XG4gICAgICBib3gtc2hhZG93OiBub25lO1xuICAgICAgcGFkZGluZzogMS41JSAzJTsgfSB9XG4gIC5oZWFkZXIgLm5hdiAuYWN0aXZlIC5uYXYtbGlzdC1pdGVtLXRleHQge1xuICAgIGZvbnQtd2VpZ2h0OiBib2xkOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5oZWFkZXIgLm5hdiAuYWN0aXZlIC5uYXYtbGlzdC1pdGVtLXRleHQge1xuICAgICAgICBib3JkZXItYm90dG9tOiAxcHggc29saWQgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpOyB9IH1cbiAgLmhlYWRlciAubmF2LWxpc3Qge1xuICAgIGxpc3Qtc3R5bGU6IG5vbmU7XG4gICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgICBwYWRkaW5nLWxlZnQ6IDA7IH1cbiAgICAuaGVhZGVyIC5uYXYtbGlzdCBhIHtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgcGFkZGluZzogNnB4OyB9XG4gICAgICAuaGVhZGVyIC5uYXYtbGlzdCBhOmhvdmVyLFxuICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTphY3RpdmUsXG4gICAgICAuaGVhZGVyIC5uYXYtbGlzdCBhOmZvY3VzIHtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5oZWFkZXIgLm5hdi1saXN0IHtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgZmxleC13cmFwOiB3cmFwO1xuICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgICAgICAgcGFkZGluZzogMDtcbiAgICAgICAgd2lkdGg6IDEwMCU7IH1cbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYSxcbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTpob3ZlcixcbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTphY3RpdmUsXG4gICAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGE6Zm9jdXMge1xuICAgICAgICAgIGNvbG9yOiAjZmZmOyB9XG4gICAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGxpIHtcbiAgICAgICAgICBwYWRkaW5nOiAwIDIwcHg7IH0gfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgICBGT09URVJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbi5mb290ZXIge1xuICBwYWRkaW5nOiAzJTtcbiAgdGV4dC1hbGlnbjogY2VudGVyOyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmZvb3RlciB7XG4gICAgICBwYWRkaW5nOiAxLjUlIDMlOyB9IH1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuXHQgICAgTE9BRElOR1xuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLmxvYWRpbmctd3JhcHBlciB7XG4gIGhlaWdodDogMTAwJTtcbiAgcG9zaXRpb246IGZpeGVkO1xuICB0b3A6IDA7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlO1xuICB6LWluZGV4OiA5OTk7IH1cblxuLmxvYWRpbmctb3ZlcmxheSB7XG4gIGJhY2tncm91bmQ6IHJnYmEoMjU1LCAyNTUsIDI1NSwgMC45KTtcbiAgcG9zaXRpb246IGFic29sdXRlO1xuICB0b3A6IDA7XG4gIGJvdHRvbTogMDtcbiAgd2lkdGg6IDEwMCU7IH1cblxuLmxvYWRpbmctc3Bpbm5lciB7XG4gIG1hcmdpbjogLTE2cHggMCAwIC0xNnB4O1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogNTAlO1xuICBsZWZ0OiA1MCU7IH1cblxuLyotLSBQYWdlcyAtLSovXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgICA0MDRcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgRk9OVFNcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuLyogRnVsbCBAZm9udC1mYWNlIGRlY2xhcmF0aW9uICovXG5cbkBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDogMzIwcHgpLCBzY3JlZW4gYW5kIChtYXgtZGV2aWNlLXdpZHRoOiA3MjBweCkgYW5kIChvcmllbnRhdGlvbjpwb3J0cmFpdCksIHNjcmVlbiBhbmQgKG1heC1kZXZpY2Utd2lkdGg6IDEyODBweCkgYW5kIChvcmllbnRhdGlvbjpsYW5kc2NhcGUpIHtcblx0LyogZm9yIG1vYmlsZSBkZXZpY2VzLCBvbmx5IGxvYWQgU1ZHIGZvbnRcblx0c2VlOiBodHRwOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzIwODkwNDg5L2ZvbnQtZmFjZS1kZWNsYXJhdGlvbnMtZG9udC13b3JrLWluLWFuZHJvaWQtNC0zLWludGVybmV0LWJyb3dzZXJcblxuXHRAZm9udC1mYWNlIHtcblx0XHRmb250LWZhbWlseTogJ0ZvbnROYW1lJztcblx0XHRzcmM6IHVybCgnLi4vZm9udHMvRm9udE5hbWUuc3ZnI0ZvbnROYW1lJykgZm9ybWF0KCdzdmcnKTtcblx0XHRmb250LXdlaWdodDogbm9ybWFsO1xuXHRcdGZvbnQtc3R5bGU6IG5vcm1hbDtcblx0fSAqL1xufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0IFBSRVNFTlRBVElPTlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5ib2R5IHtcblx0YmFja2dyb3VuZDogJGNvbG9yLWJnLWxpZ2h0O1xuXHRjb2xvcjogJGNvbG9yLXRleHQ7XG5cdGZvbnQ6IG5vcm1hbCAxNnB4LzEuMiAnSGVsdmV0aWNhIE5ldWUnLCBIZWx2ZXRpY2EsIEFyaWFsLCBWZXJkYW5hLCBzYW5zLXNlcmlmO1xufVxuLmxheW91dC1jYW52YXMge1xuXHRiYWNrZ3JvdW5kOiAkY29sb3ItYmc7XG59XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBDb250ZW50IC0tKi9cblxuLmNvbnRlbnQtd3JhcHBlciB7XG5cdHBhZGRpbmc6ICRwYWRkaW5nLXNjcmVlbi1zbWFsbDtcblxuXHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcblx0XHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tbGFyZ2U7XG5cdH1cbn1cbi5jb250ZW50LWhlYWRpbmcge1xuXHRmb250LXNpemU6IDM2cHg7XG5cdG1hcmdpbjogLTEuNSUgMCAyMHB4O1xuXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xuXHRcdG1hcmdpbi10b3A6IDA7XG5cdFx0dGV4dC1hbGlnbjogY2VudGVyO1xuXHR9XG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuIExBWU9VVCBGVU5DVElPTkFMSVRZXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmJvZHkge1xuXHRtaW4td2lkdGg6IDMyMHB4O1xufVxuLmxheW91dC1vdmVyZmxvdyB7XG5cdG92ZXJmbG93OiBoaWRkZW47XHQvKiBuZWNlc3NhcnkgdG8gaGFuZGxlIG9mZmNhbnZhcyBzY3JvbGxiYXIgYmVoYXZpb3IgKi9cbn1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIE9mZi1jYW52YXMgRnVuY3Rpb25hbGl0eSAtLSovXG5cbi8qLS0gQ29udGVudCBjYW52YXMgd3JhcHBlciAtLSovXG5cbi5sYXlvdXQtY2FudmFzIHtcblx0YmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuO1xuXHRwb3NpdGlvbjogcmVsYXRpdmU7XG5cdGxlZnQ6IDA7XG5cdHdpZHRoOiAxMDAlO1xuXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xuXHRcdHRyYW5zaXRpb246IG5vbmU7XG5cdFx0dHJhbnNmb3JtOiBub25lO1xuXHR9XG5cdC5jc3N0cmFuc2Zvcm1zM2QgJiB7XG5cdFx0dHJhbnNpdGlvbjogdHJhbnNmb3JtIDI1MG1zIGVhc2U7XG5cdFx0dHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgwLDAsMCk7XG5cdH1cblx0LmNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gJiB7XG5cdFx0dHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgyNzBweCwwLDApO1xuXHR9XG5cdC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuICYge1xuXHRcdGxlZnQ6IDI3MHB4O1xuXHR9XG59XG5cbi8qLS0gSGVhZGVyIC0tKi9cblxuLmhlYWRlci1tb2JpbGUtcGFnZSB7XG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xuXHRcdGRpc3BsYXk6IG5vbmU7XG5cdH1cbn1cblxuLyotLSBOYXZpZ2F0aW9uIC0tKi9cblxuLmhlYWRlciAubmF2IHtcblx0YmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuO1xuXHRkaXNwbGF5OiBub25lO1x0LyogZGVhbCB3aXRoIEZPVUMgKi9cblx0aGVpZ2h0OiAxMDAlO1xuXHRvdmVyZmxvdy15OiBhdXRvO1xuXHRwb3NpdGlvbjogYWJzb2x1dGU7XG5cdHRvcDogMDtcblx0d2lkdGg6IDI3MHB4O1xuXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xuXHRcdGRpc3BsYXk6IGJsb2NrO1xuXHRcdHBvc2l0aW9uOiByZWxhdGl2ZTtcblx0XHR3aWR0aDogMTAwJTtcblx0fVxuXHQubmF2LWNsb3NlZCAmLFxuXHQubmF2LW9wZW4gJiB7XG5cdFx0ZGlzcGxheTogYmxvY2s7XHQvKiBkZWFsIHdpdGggRk9VQyAqL1xuXHR9XG5cdC5jc3N0cmFuc2Zvcm1zM2QgJiB7XG5cdFx0dHJhbnNmb3JtOiB0cmFuc2xhdGUzZCgtMTAwJSwwLDApO1xuXG5cdFx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XG5cdFx0XHR0cmFuc2Zvcm06IG5vbmU7XG5cdFx0fVxuXHR9XG5cdC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1jbG9zZWQgJiB7XG5cdFx0bGVmdDogLTEwMCU7XG5cdH1cblx0Lm5vLWNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gJiB7XG5cdFx0bGVmdDogLTI3MHB4O1xuXG5cdFx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XG5cdFx0XHRsZWZ0OiAwO1xuXHRcdH1cblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgUFJJTlRcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuQG1lZGlhIHByaW50IHtcblxuXHQqIHtcblx0XHRiYWNrZ3JvdW5kOiAjZmZmO1xuXHR9XG5cdGJvZHkge1xuXHRcdGNvbG9yOiAjMDAwO1xuXHRcdGZvbnQ6IG5vcm1hbCAxNnB4LzEuNCBHZW9yZ2lhLCAnVGltZXMgTmV3IFJvbWFuJywgc2VyaWY7XG5cdH1cblxuXHQvKi0tIEhpZGRlbiBFbGVtZW50cyAtLSovXG5cblx0LmhlYWRlcixcblx0LmZvb3RlciB7XG5cdFx0ZGlzcGxheTogbm9uZTtcblx0fVxuXG5cdC8qLS0gU2hvdyBsaW5rIFVSTHMgLS0qL1xuXG5cdGE6bGluayxcblx0YTp2aXNpdGVkIHtcblx0XHRjb2xvcjogYmx1ZTtcblx0XHR0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTtcblx0fVxuXHRhOmxpbms6YWZ0ZXIsXG5cdGE6dmlzaXRlZDphZnRlciB7XG5cdFx0Y29udGVudDpcIiBbXCIgYXR0cihocmVmKSBcIl0gXCI7XG5cdFx0Zm9udC1zaXplOiA3NSU7XG5cdH1cblxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgIEhFQURFUlxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG4uaGVhZGVyLW1vYmlsZS1wYWdlIHtcblx0YmFja2dyb3VuZDogJGNvbG9yLW5hdi1iZztcblx0Y29sb3I6ICRjb2xvci13aGl0ZTtcblx0aGVpZ2h0OiA1MHB4O1xuXHRtYXJnaW4tYm90dG9tOiAxMHB4O1xuXHRwb3NpdGlvbjogcmVsYXRpdmU7XG5cblx0Ji1zaXRlVGl0bGUge1xuXHRcdGZvbnQtc2l6ZTogMzBweDtcblx0XHRsaW5lLWhlaWdodDogNTBweDtcblx0XHRtYXJnaW46IDA7XG5cdFx0cGFkZGluZzogMCAwIDAgNTBweDtcblx0XHRwb3NpdGlvbjogYWJzb2x1dGU7XG5cdFx0dG9wOiAwO1xuXHRcdHRleHQtYWxpZ246IGNlbnRlcjtcblx0XHR3aWR0aDogMTAwJTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0IE5BVklHQVRJT05cbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIEhhbWJ1cmdlciBtZW51IHRvZ2dsZSAtLSovXG5cbi50b2dnbGUtb2ZmY2FudmFzIHtcblx0YmFja2dyb3VuZDogJGNvbG9yLW5hdi1iZztcblx0Ym9yZGVyLXJpZ2h0OiAxcHggc29saWQgJGNvbG9yLWRpc2FibGVkLXJnYmE7XG5cdGRpc3BsYXk6IGlubGluZS1ibG9jaztcblx0aGVpZ2h0OiA1MHB4O1xuXHRwYWRkaW5nOiAyMy41cHggMTNweDtcblx0cG9zaXRpb246IHJlbGF0aXZlO1xuXHR0ZXh0LWFsaWduOiBjZW50ZXI7XG5cdHdpZHRoOiA1MHB4O1xuXHR6LWluZGV4OiAxMDA7XG5cblx0c3Bhbixcblx0c3BhbjpiZWZvcmUsXG5cdHNwYW46YWZ0ZXIge1xuXHRcdGJhY2tncm91bmQ6ICRjb2xvci13aGl0ZTtcblx0XHRib3JkZXItcmFkaXVzOiAxcHg7XG5cdFx0Y29udGVudDogJyc7XG5cdFx0ZGlzcGxheTogYmxvY2s7XG5cdFx0aGVpZ2h0OiAzcHg7XG5cdFx0cG9zaXRpb246IGFic29sdXRlO1xuXHRcdHRyYW5zaXRpb246IGFsbCAyNTBtcyBlYXNlLWluLW91dDtcblx0XHR3aWR0aDogMjRweDtcblx0fVxuXHRzcGFuIHtcblx0XHQmOmJlZm9yZSB7XG5cdFx0XHR0b3A6IC05cHg7XG5cdFx0fVxuXHRcdCY6YWZ0ZXIge1xuXHRcdFx0Ym90dG9tOiAtOXB4O1xuXHRcdH1cblx0fVxuXHQubmF2LW9wZW4gJiB7XG5cdFx0c3BhbiB7XG5cdFx0XHRiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDtcblxuXHRcdFx0JjpiZWZvcmUsXG5cdFx0XHQmOmFmdGVyIHtcblx0XHRcdFx0dG9wOiAwO1xuXHRcdFx0fVxuXHRcdFx0JjpiZWZvcmUge1xuXHRcdFx0XHR0cmFuc2Zvcm06IHJvdGF0ZSg0NWRlZyk7XG5cdFx0XHR9XG5cdFx0XHQmOmFmdGVyIHtcblx0XHRcdFx0dHJhbnNmb3JtOiByb3RhdGUoLTQ1ZGVnKTtcblx0XHRcdH1cblx0XHR9XG5cdH1cbn1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIE5hdmlnYXRpb24gLS0qL1xuXG4uaGVhZGVyIC5uYXYge1xuXHRib3gtc2hhZG93OiBpbnNldCAtOHB4IDAgOHB4IC02cHggcmdiYSgwLDAsMCwwLjIpO1xuXHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tc21hbGw7XG5cblx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XG5cdFx0YmFja2dyb3VuZDogJGNvbG9yLW5hdi1iZztcblx0XHRib3gtc2hhZG93OiBub25lO1xuXHRcdHBhZGRpbmc6ICRwYWRkaW5nLXNjcmVlbi1sYXJnZTtcblx0fVxuXG5cdC5hY3RpdmUgLm5hdi1saXN0LWl0ZW0tdGV4dCB7XG5cdFx0Zm9udC13ZWlnaHQ6IGJvbGQ7XG5cblx0XHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcblx0XHRcdGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAkY29sb3ItZGlzYWJsZWQtcmdiYTtcblx0XHR9XG5cdH1cblx0Ji1saXN0IHtcblx0XHRsaXN0LXN0eWxlOiBub25lO1xuXHRcdG1hcmdpbi1ib3R0b206IDA7XG5cdFx0cGFkZGluZy1sZWZ0OiAwO1xuXG5cdFx0YSB7XG5cdFx0XHRkaXNwbGF5OiBibG9jaztcblx0XHRcdHBhZGRpbmc6IDZweDtcblxuXHRcdFx0Jjpob3Zlcixcblx0XHRcdCY6YWN0aXZlLFxuXHRcdFx0Jjpmb2N1cyB7XG5cdFx0XHRcdHRleHQtZGVjb3JhdGlvbjogbm9uZTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcblx0XHRcdGRpc3BsYXk6IGZsZXg7XG5cdFx0XHRmbGV4LXdyYXA6IHdyYXA7XG5cdFx0XHRqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcblx0XHRcdHBhZGRpbmc6IDA7XG5cdFx0XHR3aWR0aDogMTAwJTtcblxuXHRcdFx0YSxcblx0XHRcdGE6aG92ZXIsXG5cdFx0XHRhOmFjdGl2ZSxcblx0XHRcdGE6Zm9jdXMge1xuXHRcdFx0XHRjb2xvcjogJGNvbG9yLXdoaXRlO1xuXHRcdFx0fVxuXHRcdFx0bGkge1xuXHRcdFx0XHRwYWRkaW5nOiAwIDIwcHg7XG5cdFx0XHR9XG5cdFx0fVxuXHR9XG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxuXHQgICAgRk9PVEVSXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbi5mb290ZXIge1xuXHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tc21hbGw7XG5cdHRleHQtYWxpZ246IGNlbnRlcjtcblxuXHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcblx0XHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tbGFyZ2U7XG5cdH1cbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cdCAgICBMT0FESU5HXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbi5sb2FkaW5nIHtcblx0Ji13cmFwcGVyIHtcblx0XHRoZWlnaHQ6IDEwMCU7XG5cdFx0cG9zaXRpb246IGZpeGVkO1xuXHRcdFx0dG9wOiAwOyBsZWZ0OiAwO1xuXHRcdHdpZHRoOiAxMDAlO1xuXHRcdHotaW5kZXg6IDk5OTtcblx0fVxuXHQmLW92ZXJsYXkge1xuXHRcdGJhY2tncm91bmQ6IHJnYmEoMjU1LDI1NSwyNTUsLjkpO1xuXHRcdHBvc2l0aW9uOiBhYnNvbHV0ZTtcblx0XHRcdHRvcDogMDsgYm90dG9tOiAwO1xuXHRcdHdpZHRoOiAxMDAlO1xuXHR9XG5cdCYtc3Bpbm5lciB7XG5cdFx0bWFyZ2luOiAtMTZweCAwIDAgLTE2cHg7XG5cdFx0cG9zaXRpb246IGFic29sdXRlO1xuXHRcdFx0dG9wOiA1MCU7IGxlZnQ6IDUwJTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cblx0ICAgIDQwNFxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG4uZXJyb3I0MDQtd3JhcHBlciB7XG5cbn0iXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= */ \ No newline at end of file +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3N0ZGluIiwiY29yZS9wYXJ0aWFscy9fY29sb3JzLnZhcnMuc2NzcyIsImNvcmUvcGFydGlhbHMvX2xheW91dC52YXJzLnNjc3MiLCJjb3JlL3BhcnRpYWxzL19yZXNwb25zaXZlLnBhcnRpYWwuc2NzcyIsImNvcmUvX2Jhc2Uuc2NzcyIsInN0eWxlcy5jc3MiLCJjb3JlL19mb250cy5zY3NzIiwiY29yZS9fcHJlc2VudGF0aW9uLnNjc3MiLCJjb3JlL19sYXlvdXQuc2NzcyIsImNvcmUvX3ByaW50LnNjc3MiLCJtb2R1bGVzL19oZWFkZXIuc2NzcyIsIm1vZHVsZXMvX25hdi5zY3NzIiwibW9kdWxlcy9fZm9vdGVyLnNjc3MiLCJtb2R1bGVzL19sb2FkaW5nLnNjc3MiLCJwYWdlcy9fNDA0LnNjc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7OztFQU1FO0FBRUYsY0FBYztBQ1JkOztzQkFFc0I7QUFFdEIsMkJBQTJCO0FBRTNCLG1EQUFtRDtBQVNuRCx3Q0FBd0M7QUFLeEMsMkJBQTJCO0FDcEIzQjs7c0JBRXNCO0FBRXRCLGlCQUFpQjtBQ0pqQjs7c0JBRXNCO0FBRXRCLG1CQUFtQjtBQUtuQixnQkFBZ0I7QUNUaEI7O3NCQUVzQjtBQUV0Qjs7Ozs7OztFQU9DLFVBQVU7RUFBRSxXQUFXO0VBQ3ZCLFVBQVU7RUFDVixpQkFBaUI7RUFBRSxhQUFhO0VBQ2hDLHFCQUFxQjtFQUFFLG9CQUFvQjtFQUFFLHFCQUFxQjtFQUNsRSxlQUFlO0VBQ2Ysc0JBQXNCO0VBQ3RCLHlCQUF5QixFQUN6Qjs7QUFDRDtFQUFPLG1CQUFtQjtFQUFFLCtCQUErQixFQUFJOztBQUMvRDtFQUFTLGlCQUFpQixFQUFJOztBQUM5QjtFQUFRLDBCQUEwQjtFQUFFLGtCQUFrQixFQUFJOztBQUMxRDtFQUFrQixpQkFBaUI7RUFBRSxvQkFBb0IsRUFBSTs7QUFDN0Q7RUFBVyxlQUFlLEVBQUk7O0FBQzlCO0VBQXlCLG9CQUFvQixFQUFJOztBQUNqRDtFQUFrRyxlQUFlLEVBQUk7O0FBQ3JIO0VBQ0MsdUJBQXVCLEVBQ3ZCOztBQUVEOztzQkFFc0I7QUFFdEIsbUJBQW1CO0FBRW5CO0VBQUssbUJBQW1CLEVBQUk7O0FBQzVCO0VBQVMsa0JBQWtCLEVBQUk7O0FBQy9CO0VBQWEsZ0RBQWdELEVBQUk7O0FBQ2pFO0VBQTRCLG9CQUFvQixFQUFJOztBQUNwRDtFQUFLLHlCQUF5QjtFQUFFLG1CQUFtQixFQUFJOztBQUN2RDtFQUFLLDRCQUE0QjtFQUFFLG1CQUFtQixFQUFJOztBQUMxRDtFQUEyRCxnQkFBZ0IsRUFBSTs7QUFDL0U7RUFBTSw4QkFBOEIsRUFBSTs7QUFDeEM7RUFBMEIsMEJBQTBCO0VBQUUsYUFBYSxFQUFJOztBQUN2RTtFQUFXLGlCQUFpQjtFQUFFLHVCQUF1QjtFQUFFLDhEQUE4RDtFQUFFLFlBQVksRUFBSTs7QUFDdkk7RUFBMEIsdUJBQXVCO0VBQUUscUJBQXFCO0VBQUUsbUJBQW1CO0VBQUUsaUJBQWlCLEVBQUk7O0FBQ3BIO0VBQWdCLHVCQUF1QixFQUFJOztBQUMzQztFQUFXLGVBQWUsRUFBSTs7QUFBQSw0REFBNEQ7QUFDMUY7RUFBa0IsZUFBZSxFQUFJOztBQUNyQztFQUFNLHNCQUFzQixFQUFJOztBQUNoQztFQUFNLG9CQUFvQixFQUFJOztBQUM5QjtFQUFRLGVBQWU7RUFBRSxhQUFhLEVBQUk7O0FBQzFDO0VBQVEsOEJIekNZLEVHeUM4Qjs7QUFDbEQ7RUFBSyxrQkFBa0IsRUFBSTs7QUFDM0I7RUFBYSxrQkFBa0IsRUFBSTs7QUFDbkM7RUFBSyw4Qkg1Q2UsRUc0Q3lCOztBQUU3QyxrQkFBa0I7QUFFbEI7O0VBRUMsYUFBYTtFQUNiLGVBQWUsRUFDZjs7QUFDRDtFQUFrQixZQUFZLEVBQUk7O0FBQ2xDO0VBQWlCLFFBQVEsRUFBSTs7QUFFN0IsZ0VBQWdFO0FDZ0doRTs7Ozs7O0VEeEZDLHlCQUF5QixFQUN6Qjs7QUFFRCwyQkFBMkI7QUFFM0I7RUFDQyxpQkFBaUI7RUFDakIsa0JBQWtCO0VBQ2xCLG9CQUFvQixFQUNwQjs7QUFFRCxtQ0FBbUM7QUFFbkM7RUFDQyxnQkFBZ0I7RUFDaEIsZ0JBQWdCLEVBQ2hCOztBQUVELGVBQWU7QUFFZjs7OztFQUlDLGdCQUFnQjtFQUFFLG9CQUFvQixFQUN0Qzs7QUFDRDtFQUNDLHFDSHJGb0I7RUdzRnBCLDJDSHRGb0IsRUd1RnBCOztBQUNEO0VBQ0MsbUJBQW1CO0VBQ25CLHNCQUFzQjtFQUN0QixtQkFBbUI7RUFDbkIsaUJBQWlCO0VBQ2pCLHVCQUF1QixFQUN2Qjs7QUVoSEQ7O3NCQUVzQjtBQUV0QixpQ0FBaUM7QUNKakM7O3NCQUVzQjtBQUV0QjtFQUNDLGlCTlFtQjtFTVBuQixZTklxQjtFTUhyQiw4RUFBOEUsRUFDOUU7O0FBQ0Q7RUFDQyxpQk5EaUIsRU1FakI7O0FBRUQsd0ZBQXdGO0FBRXhGO0VBQ0MsWUxWd0IsRUtleEI7RUpUQTtJSUdEO01BSUUsaUJMWjRCLEVLYzdCLEVBQUE7O0FBQ0Q7RUFDQyxnQkFBZ0I7RUFDaEIscUJBQXFCLEVBTXJCO0VKbEJBO0lJVUQ7TUFLRSxjQUFjO01BQ2QsbUJBQW1CLEVBRXBCLEVBQUE7O0FDOUJEOztzQkFFc0I7QUFFdEI7RUFDQyxpQkFBaUIsRUFDakI7O0FBQ0Q7RUFDQyxpQkFBaUI7RUFBRSxzREFBc0QsRUFDekU7O0FBRUQseUdBQXlHO0FBRXpHLGdDQUFnQztBQUVoQztFQUNDLG9DQUE0QjtFQUE1Qiw0QkFBNEI7RUFDNUIsbUJBQW1CO0VBQ25CLFFBQVE7RUFDUixZQUFZLEVBZ0JaO0VMdkJBO0lLR0Q7TUFPRSx5QkFBaUI7TUFBakIsaUJBQWlCO01BQ2pCLHdCQUFnQjtNQUFoQixvQkFBZ0I7TUFBaEIsZ0JBQWdCLEVBWWpCLEVBQUE7RUFWQTtJQUNDLGlEQUFpQztJQUFqQyxpQ0FBaUM7SUFDakMsd0NBQXNCO0lBQXRCLGdDQUFzQixFQUN0QjtFQUNEO0lBQ0MsNENBQXNCO0lBQXRCLG9DQUFzQixFQUN0QjtFQUNEO0lBQ0MsWUFBWSxFQUNaOztBQUdGLGdCQUFnQjtBTHpCZjtFSzJCRDtJQUVFLGNBQWMsRUFFZixFQUFBOztBQUVELG9CQUFvQjtBQUVwQjtFQUNDLG9DQUE0QjtFQUE1Qiw0QkFBNEI7RUFDNUIsY0FBYztFQUFFLG9CQUFvQjtFQUNwQyxhQUFhO0VBQ2IsaUJBQWlCO0VBQ2pCLG1CQUFtQjtFQUNuQixPQUFPO0VBQ1AsYUFBYSxFQTRCYjtFTHRFQTtJS21DRDtNQVVFLGVBQWU7TUFDZixtQkFBbUI7TUFDbkIsWUFBWSxFQXVCYixFQUFBO0VBckJBOztJQUVDLGVBQWU7SUFBRSxvQkFBb0IsRUFDckM7RUFDRDtJQUNDLDRDQUFzQjtJQUF0QixvQ0FBc0IsRUFLdEI7SUwzREQ7TUtxREE7UUFJRSx3QkFBZ0I7UUFBaEIsb0JBQWdCO1FBQWhCLGdCQUFnQixFQUVqQixFQUFBO0VBQ0Q7SUFDQyxZQUFZLEVBQ1o7RUFDRDtJQUNDLGFBQWEsRUFLYjtJTHJFRDtNSytEQTtRQUlFLFFBQVEsRUFFVCxFQUFBOztBQ2pGRjs7c0JBRXNCO0FBRXRCO0VBRUM7SUFDQyxpQkFBaUIsRUFDakI7RUFDRDtJQUNDLFlBQVk7SUFDWix3REFBd0QsRUFDeEQ7RUFFRCx5QkFBeUI7RUFFekI7O0lBRUMsY0FBYyxFQUNkO0VBRUQsd0JBQXdCO0VBRXhCOztJQUVDLFlBQVk7SUFDWiwyQkFBMkIsRUFDM0I7RUFDRDs7SUFFQyw4QkFBNEI7SUFDNUIsZUFBZSxFQUNmLEVBQUE7O0FUWEYsaUJBQWlCO0FVckJqQjs7c0JBRXNCO0FBRXRCO0VBQ0MsaUJUTXFCO0VTTHJCLFlUR2lCO0VTRmpCLGFBQWE7RUFDYixvQkFBb0I7RUFDcEIsbUJBQW1CLEVBWW5CO0VBakJEO0lBUUUsZ0JBQWdCO0lBQ2hCLGtCQUFrQjtJQUNsQixVQUFVO0lBQ1Ysb0JBQW9CO0lBQ3BCLG1CQUFtQjtJQUNuQixPQUFPO0lBQ1AsbUJBQW1CO0lBQ25CLFlBQVksRUFDWjs7QUNwQkY7O3NCQUVzQjtBQUV0QixzR0FBc0c7QUFFdEc7RUFDQyxpQlZJcUI7RVVIckIsaURWU3FCO0VVUnJCLHNCQUFzQjtFQUN0QixhQUFhO0VBQ2IscUJBQXFCO0VBQ3JCLG1CQUFtQjtFQUNuQixtQkFBbUI7RUFDbkIsWUFBWTtFQUNaLGFBQWEsRUFzQ2I7RUEvQ0Q7OztJQWNFLGlCVlhnQjtJVVloQixtQkFBbUI7SUFDbkIsWUFBWTtJQUNaLGVBQWU7SUFDZixZQUFZO0lBQ1osbUJBQW1CO0lBQ25CLDBDQUFrQztJQUFsQyxrQ0FBa0M7SUFDbEMsWUFBWSxFQUNaO0VBdEJGO0lBeUJHLFVBQVUsRUFDVjtFQTFCSDtJQTRCRyxhQUFhLEVBQ2I7RUFFRjtJQUVFLHdCQUF3QixFQVl4QjtJQWRGO01BTUcsT0FBTyxFQUNQO0lBUEg7TUFTRyxpQ0FBaUI7TUFBakIsNkJBQWlCO01BQWpCLHlCQUFpQixFQUNqQjtJQVZIO01BWUcsa0NBQWlCO01BQWpCLDhCQUFpQjtNQUFqQiwwQkFBaUIsRUFDakI7O0FBS0osMkZBQTJGO0FBRTNGO0VBQ0MscURBQXNDO0VBQ3RDLFlUckR3QixFU3NHeEI7RVJoR0E7SVE2Q0Q7TUFLRSxpQlZuRG9CO01Vb0RwQixpQkFBaUI7TUFDakIsaUJUekQ0QixFU3FHN0IsRUFBQTtFQW5ERDtJQVdFLGtCQUFrQixFQUtsQjtJUjdERDtNUTZDRDtRQWNHLGtEVnREbUIsRVV3RHBCLEVBQUE7RUFoQkY7SUFrQkUsaUJBQWlCO0lBQ2pCLGlCQUFpQjtJQUNqQixnQkFBZ0IsRUE4QmhCO0lBbERGO01BdUJHLGVBQWU7TUFDZixhQUFhLEVBT2I7TUEvQkg7UUE2Qkksc0JBQXNCLEVBQ3RCO0lSM0VIO01RNkNEO1FBa0NHLHFCQUFjO1FBQWQsc0JBQWM7UUFBZCxxQkFBYztRQUFkLGNBQWM7UUFDZCx3QkFBZ0I7UUFBaEIsb0JBQWdCO1FBQWhCLGdCQUFnQjtRQUNoQix5QkFBd0I7UUFBeEIsZ0NBQXdCO1FBQXhCLHNCQUF3QjtRQUF4Qix3QkFBd0I7UUFDeEIsV0FBVztRQUNYLFlBQVksRUFZYjtRQWxERjs7OztVQTRDSSxZVjVGYyxFVTZGZDtRQTdDSjtVQStDSSxnQkFBZ0IsRUFDaEIsRUFBQTs7QUN6R0o7O3NCQUVzQjtBQUV0QjtFQUNDLFlWQ3dCO0VVQXhCLG1CQUFtQixFQUtuQjtFVENBO0lTUkQ7TUFLRSxpQlZGNEIsRVVJN0IsRUFBQTs7QUNYRDs7c0JBRXNCO0FBRXRCO0VBRUUsYUFBYTtFQUNiLGdCQUFnQjtFQUNmLE9BQU87RUFBRSxRQUFRO0VBQ2xCLFlBQVk7RUFDWixhQUFhLEVBQ2I7O0FBUEY7RUFTRSxxQ0FBZ0I7RUFDaEIsbUJBQW1CO0VBQ2xCLE9BQU87RUFBRSxVQUFVO0VBQ3BCLFlBQVksRUFDWjs7QUFiRjtFQWVFLHdCQUF3QjtFQUN4QixtQkFBbUI7RUFDbEIsU0FBUztFQUFFLFVBQVUsRUFDdEI7O0FiTUYsZUFBZTtBYzVCZjs7c0JBRXNCIiwiZmlsZSI6InN0eWxlcy5jc3MiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxyXG4gKiByZVN0YXJ0IEJvaWxlcnBsYXRlIC8gcmVTdGFydCBBbmd1bGFyXHJcbiAqIEF1dGhvcjogS2ltIE1haWRhIFxyXG4gKiBBdXRob3IgVVJJOiA8aHR0cDovL2tpbS1tYWlkYS5jb20+XHJcbiAqIFNvdXJjZTogPGh0dHBzOi8vZ2l0aHViLmNvbS9rbWFpZGEvcmVTdGFydC1hbmd1bGFyPlxyXG4gKiBMaWNlbnNlOiBHTlUgUHVibGljIExpY2Vuc2VcclxuKi9cclxuXHJcbi8qLS0gQ29yZSAtLSovXHJcblxyXG4vLyBwYXJ0aWFsc1xyXG5AaW1wb3J0ICdjb3JlL3BhcnRpYWxzL2NvbG9ycy52YXJzJztcclxuQGltcG9ydCAnY29yZS9wYXJ0aWFscy9sYXlvdXQudmFycyc7XHJcbkBpbXBvcnQgJ2NvcmUvcGFydGlhbHMvcmVzcG9uc2l2ZS5wYXJ0aWFsJztcclxuXHJcbkBpbXBvcnQgJ2NvcmUvYmFzZSc7XHJcbkBpbXBvcnQgJ2NvcmUvZm9udHMnO1xyXG5AaW1wb3J0ICdjb3JlL3ByZXNlbnRhdGlvbic7XHJcbkBpbXBvcnQgJ2NvcmUvbGF5b3V0JztcclxuQGltcG9ydCAnY29yZS9wcmludCc7XHJcblxyXG4vKi0tIE1vZHVsZXMgLS0qL1xyXG5cclxuQGltcG9ydCAnbW9kdWxlcy9oZWFkZXInO1xyXG5AaW1wb3J0ICdtb2R1bGVzL25hdic7XHJcbkBpbXBvcnQgJ21vZHVsZXMvZm9vdGVyJztcclxuQGltcG9ydCAnbW9kdWxlcy9sb2FkaW5nJztcclxuXHJcbi8qLS0gUGFnZXMgLS0qL1xyXG5cclxuQGltcG9ydCAncGFnZXMvNDA0JzsiLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0Q09MT1IgVkFSSUFCTEVTXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cclxuXHJcbi8qLS0gQ29sb3IgZGVmaW5pdGlvbnMgLS0qL1xyXG5cclxuLyogSGV4IGNvZGUgbmFtZXMgZnJvbSBodHRwOi8vbmFtZS1vZi1jb2xvci5jb20vICovXHJcblxyXG4kY29sb3ItYmxhY2s6ICMwMDA7XHJcbiRjb2xvci13aGl0ZTogI2ZmZjtcclxuJGNvbG9yLW1pbmVTaGFmdDogIzMzMztcclxuJGNvbG9yLWRhdnlzR3JheTogIzU1NTtcclxuJGNvbG9yLWNlbGVzdGU6ICNjY2M7XHJcbiRjb2xvci1nYWxsZXJ5OiAjZWVlO1xyXG5cclxuLyogQ29sb3IgcGFydGlhbGx5IGRlZmluZWQgYnkgb3BhY2l0eSAqL1xyXG5cclxuJGNvbG9yLWdyYXktcmdiYTogcmdiYSgyNTUsMjU1LDI1NSwuNSk7XHJcbiRjb2xvci1yZWQtcmdiYTogcmdiYSgxNjksNjgsNjYsLjYpO1xyXG5cclxuLyotLSBDb2xvciBieSBmdW5jdGlvbiAtLSovXHJcblxyXG4kY29sb3ItYmctbGlnaHQ6ICRjb2xvci1nYWxsZXJ5O1xyXG4kY29sb3ItYmc6ICRjb2xvci13aGl0ZTtcclxuJGNvbG9yLWJvcmRlcjogJGNvbG9yLWNlbGVzdGU7XHJcbiRjb2xvci1tb2R1bGUtYmc6ICRjb2xvci1jZWxlc3RlO1xyXG4kY29sb3ItbmF2LWJnOiAkY29sb3ItZGF2eXNHcmF5O1xyXG4kY29sb3ItdGV4dDogJGNvbG9yLW1pbmVTaGFmdDtcclxuJGNvbG9yLWxpbmstY29udHJhc3Q6ICRjb2xvci13aGl0ZTtcclxuJGNvbG9yLWRpc2FibGVkLXJnYmE6ICRjb2xvci1ncmF5LXJnYmE7XHJcbiRjb2xvci1pbnZhbGlkLXJnYmE6ICRjb2xvci1yZWQtcmdiYTsiLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0TEFZT1VUIFZBUklBQkxFU1xyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXHJcblxyXG4vKi0tIFBhZGRpbmcgLS0qL1xyXG5cclxuJHBhZGRpbmctc2NyZWVuLXNtYWxsOiAzJTtcclxuJHBhZGRpbmctc2NyZWVuLWxhcmdlOiAxLjUlIDMlOyIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgIFJFU1BPTlNJVkVcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuLyotLSBWYXJpYWJsZXMgLS0qL1xyXG5cclxuJG1xLXNtYWxsOiAnc2NyZWVuIGFuZCAobWF4LXdpZHRoOiA3NjdweCknO1xyXG4kbXEtbGFyZ2U6ICdzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSc7XHJcblxyXG4vKi0tIE1peGlucyAtLSovXHJcblxyXG5AbWl4aW4gbXEoJG1xU3RyaW5nKSB7XHJcblx0QG1lZGlhICN7JG1xU3RyaW5nfSB7XHJcblx0XHRAY29udGVudDtcclxuXHR9XHJcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICBDU1MgUkVTRVRcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuaHRtbCwgYm9keSwgZGl2LCBzcGFuLCBoMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBwLCBibG9ja3F1b3RlLCBwcmUsXHJcbmEsIGFiYnIsIGFjcm9ueW0sIGFkZHJlc3MsIGJpZywgY2l0ZSwgY29kZSwgZGVsLCBkZm4sIGVtLCBpbWcsIGlucyxcclxua2JkLCBxLCBzLCBzYW1wLCBzbWFsbCwgc3Ryb25nLCB0dCwgdmFyLCBkbCwgZHQsXHJcbmRkLCBvbCwgdWwsIGxpLCBmaWVsZHNldCwgZm9ybSwgaW5wdXQsIGJ1dHRvbiwgdGV4dGFyZWEsIGxhYmVsLCBsZWdlbmQsIHRhYmxlLCBjYXB0aW9uLFxyXG50Ym9keSwgdGZvb3QsIHRoZWFkLCB0ciwgdGgsIHRkLFxyXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsXHJcbmZvb3RlciwgaGVhZGVyLCBoZ3JvdXAsIG1lbnUsIG5hdiwgc2VjdGlvbiB7XHJcblx0bWFyZ2luOiAwOyBwYWRkaW5nOiAwO1xyXG5cdGJvcmRlcjogMDtcclxuXHRib3JkZXItcmFkaXVzOiAwO1x0LyogZm9yIGlPUyAqL1xyXG5cdGZvbnQtd2VpZ2h0OiBpbmhlcml0OyBmb250LXN0eWxlOiBpbmhlcml0OyBmb250LWZhbWlseTogaW5oZXJpdDtcclxuXHRsaW5lLWhlaWdodDogMTtcclxuXHR0ZXh0LWRlY29yYXRpb246IG5vbmU7XHJcblx0dmVydGljYWwtYWxpZ246IGJhc2VsaW5lO1xyXG59XHJcbmh0bWwgeyBvdmVyZmxvdy15OiBzY3JvbGw7IC13ZWJraXQtdGV4dC1zaXplLWFkanVzdDogMTAwJTsgfVxyXG5vbCwgdWwgeyBsaXN0LXN0eWxlOiBub25lOyB9XHJcbnRhYmxlIHsgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsgYm9yZGVyLXNwYWNpbmc6IDA7IH1cclxuY2FwdGlvbiwgdGgsIHRkIHsgdGV4dC1hbGlnbjogbGVmdDsgZm9udC13ZWlnaHQ6IG5vcm1hbDsgfVxyXG5zdXAsIHN1YiB7IGxpbmUtaGVpZ2h0OiAxOyB9XHJcbmgxLCBoMiwgaDMsIGg0LCBoNSwgaDYgeyBmb250LXdlaWdodDogbm9ybWFsOyB9XHJcbmFydGljbGUsIGFzaWRlLCBjYW52YXMsIGRldGFpbHMsIGZpZ2NhcHRpb24sIGZpZ3VyZSwgZm9vdGVyLCBoZWFkZXIsIGhncm91cCwgbWVudSwgbmF2LCBzZWN0aW9uIHsgZGlzcGxheTogYmxvY2s7IH1cclxuKiwqOmJlZm9yZSwqOmFmdGVyIHtcclxuXHRib3gtc2l6aW5nOiBib3JkZXItYm94O1xyXG59XHJcblxyXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgQkFTSUNTXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cclxuXHJcbi8qLS0gTm9ybWFsaXplIC0tKi9cclxuXHJcbmVtIHsgZm9udC1zdHlsZTogaXRhbGljOyB9XHJcbnN0cm9uZyB7IGZvbnQtd2VpZ2h0OiBib2xkOyB9XHJcbnE6bGFuZyhlbikgeyBxdW90ZXM6ICcmIzgyMjA7JyAnJiM4MjIxOycgJyYjODIxNjsnICcmIzgyMTc7JzsgfVxyXG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBwIHsgbWFyZ2luLWJvdHRvbTogMTVweDsgfVxyXG51bCB7IGxpc3Qtc3R5bGU6IG91dHNpZGUgZGlzYzsgcGFkZGluZy1sZWZ0OiAyNHB4OyB9XHJcbm9sIHsgbGlzdC1zdHlsZTogb3V0c2lkZSBkZWNpbWFsOyBwYWRkaW5nLWxlZnQ6IDI0cHg7IH1cclxuYSwgbGFiZWwsIGlucHV0W3R5cGU9YnV0dG9uXSwgaW5wdXRbdHlwZT1zdWJtaXRdLCBidXR0b24geyBjdXJzb3I6IHBvaW50ZXI7IH1cclxuZGVsIHsgdGV4dC1kZWNvcmF0aW9uOiBsaW5lLXRocm91Z2g7IH1cclxuYWJiclt0aXRsZV0sIGRmblt0aXRsZV0geyBib3JkZXItYm90dG9tOiAxcHggZG90dGVkOyBjdXJzb3I6IGhlbHA7IH1cclxucHJlLGNvZGUgeyBiYWNrZ3JvdW5kOiAjZWVlOyBib3JkZXI6IDFweCBzb2xpZCAjY2NjOyBmb250LWZhbWlseTogQ29uc29sYXMsICdMdWNpZGEgQ29uc29sZScsICdDb3VyaWVyIE5ldycsIHNlcmlmOyBwYWRkaW5nOiAyJTsgfVxyXG5pbnB1dCwgc2VsZWN0LCB0ZXh0YXJlYSB7IGJvcmRlcjogMXB4IHNvbGlkICNjY2M7IGZvbnQtZmFtaWx5OiBpbmhlcml0OyBmb250LXNpemU6IGluaGVyaXQ7IHBhZGRpbmc6IDNweCA2cHg7IH1cclxuaW5wdXQsIHNlbGVjdCB7IHZlcnRpY2FsLWFsaWduOiBtaWRkbGU7IH1cclxudGV4dGFyZWEgeyBvdmVyZmxvdzogYXV0bzsgfSAvKiBwcmV2ZW50cyBzY3JvbGxiYXIgZnJvbSBzaG93aW5nIHVwIHdoZW4gdW5uZWVkZWQgaW4gSUUgKi9cclxuc21hbGwsIHN1cCwgc3ViIHsgZm9udC1zaXplOiA4NSU7IH1cclxuc3VwIHsgdmVydGljYWwtYWxpZ246IHN1cGVyOyB9XHJcbnN1YiB7IHZlcnRpY2FsLWFsaWduOiBzdWI7IH1cclxudGFibGUgeyBtYXJnaW46IDEwcHggMDsgcGFkZGluZzogM3B4OyB9XHJcbnRoZWFkIHsgYm9yZGVyLWJvdHRvbTogMnB4IHNvbGlkICRjb2xvci1ib3JkZXI7IH1cclxudGggeyBmb250LXdlaWdodDogYm9sZDsgfVxyXG50aCwgdHIsIHRkIHsgcGFkZGluZzogNHB4IDEycHg7IH1cclxudHIgeyBib3JkZXItYm90dG9tOiAxcHggc29saWQgJGNvbG9yLWJvcmRlcn1cclxuXHJcbi8qLS0gQ2xlYXJmaXggLS0qL1xyXG5cclxuLmNsZWFyZml4OmJlZm9yZSxcclxuLmNsZWFyZml4OmFmdGVyIHtcclxuXHRjb250ZW50OiBcIiBcIjtcclxuXHRkaXNwbGF5OiB0YWJsZTtcclxufVxyXG4uY2xlYXJmaXg6YWZ0ZXIgeyBjbGVhcjogYm90aDsgfVxyXG4uaWU3IC5jbGVhcmZpeCB7IHpvb206IDE7IH1cclxuXHJcbi8qLS0gbmctY2xvYWs6IHByZXZlbnQgRk9VQyBiZWZvcmUgQW5ndWxhciBKYXZhU2NyaXB0IGxvYWRzIC0tKi9cclxuXHJcbltuZ1xcOmNsb2FrXSxcclxuW25nLWNsb2FrXSxcclxuW2RhdGEtbmctY2xvYWtdLFxyXG5beC1uZy1jbG9ha10sXHJcbi5uZy1jbG9hayxcclxuLngtbmctY2xvYWsge1xyXG5cdGRpc3BsYXk6IG5vbmUgIWltcG9ydGFudDtcclxufVxyXG5cclxuLyotLSBJbWFnZSBSZXBsYWNlbWVudCAtLSovXHJcblxyXG4uaXIge1xyXG5cdG92ZXJmbG93OiBoaWRkZW47XHJcblx0dGV4dC1pbmRlbnQ6IDIwMCU7XHJcblx0d2hpdGUtc3BhY2U6IG5vd3JhcDtcclxufVxyXG5cclxuLyotLSBJbmxpbmUgbGluayB0b3VjaCB0YXJnZXRzIC0tKi9cclxuXHJcbi50b3VjaCBwIGEge1xyXG5cdG1hcmdpbjogMCAtLjVlbTtcclxuXHRwYWRkaW5nOiAwIC41ZW07XHJcbn1cclxuXHJcbi8qLS0gRm9ybXMgLS0qL1xyXG5cclxuaW5wdXRbdHlwZT1cInRleHRcIl0sXHJcbmlucHV0W3R5cGU9XCJudW1iZXJcIl0sXHJcbmlucHV0W3R5cGU9XCJwYXNzd29yZFwiXSxcclxudGV4dGFyZWEge1xyXG5cdGZvbnQtc2l6ZTogMTZweDtcdC8qIGZvciBpT1MgcGhvbmVzICovXHJcbn1cclxuaW5wdXQubmctZGlydHkubmctaW52YWxpZCB7XHJcblx0Ym9yZGVyLWNvbG9yOiAkY29sb3ItaW52YWxpZC1yZ2JhO1xyXG5cdGJveC1zaGFkb3c6IDAgMCA2cHggJGNvbG9yLWludmFsaWQtcmdiYTtcclxufVxyXG5idXR0b24ge1xyXG5cdGJvcmRlci1yYWRpdXM6IDRweDtcclxuXHRkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XHJcblx0Zm9udC1zaXplOiBpbmhlcml0O1xyXG5cdHBhZGRpbmc6IDVweCA0cHg7XHJcblx0dmVydGljYWwtYWxpZ246IG1pZGRsZTtcclxufSIsIi8qXHJcbiAqIHJlU3RhcnQgQm9pbGVycGxhdGUgLyByZVN0YXJ0IEFuZ3VsYXJcclxuICogQXV0aG9yOiBLaW0gTWFpZGEgXHJcbiAqIEF1dGhvciBVUkk6IDxodHRwOi8va2ltLW1haWRhLmNvbT5cclxuICogU291cmNlOiA8aHR0cHM6Ly9naXRodWIuY29tL2ttYWlkYS9yZVN0YXJ0LWFuZ3VsYXI+XHJcbiAqIExpY2Vuc2U6IEdOVSBQdWJsaWMgTGljZW5zZVxyXG4qL1xuLyotLSBDb3JlIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHRDT0xPUiBWQVJJQUJMRVNcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBDb2xvciBkZWZpbml0aW9ucyAtLSovXG4vKiBIZXggY29kZSBuYW1lcyBmcm9tIGh0dHA6Ly9uYW1lLW9mLWNvbG9yLmNvbS8gKi9cbi8qIENvbG9yIHBhcnRpYWxseSBkZWZpbmVkIGJ5IG9wYWNpdHkgKi9cbi8qLS0gQ29sb3IgYnkgZnVuY3Rpb24gLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdExBWU9VVCBWQVJJQUJMRVNcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBQYWRkaW5nIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgIFJFU1BPTlNJVkVcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLyotLSBWYXJpYWJsZXMgLS0qL1xuLyotLSBNaXhpbnMgLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgQ1NTIFJFU0VUXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmh0bWwsIGJvZHksIGRpdiwgc3BhbiwgaDEsIGgyLCBoMywgaDQsIGg1LCBoNiwgcCwgYmxvY2txdW90ZSwgcHJlLFxuYSwgYWJiciwgYWNyb255bSwgYWRkcmVzcywgYmlnLCBjaXRlLCBjb2RlLCBkZWwsIGRmbiwgZW0sIGltZywgaW5zLFxua2JkLCBxLCBzLCBzYW1wLCBzbWFsbCwgc3Ryb25nLCB0dCwgdmFyLCBkbCwgZHQsXG5kZCwgb2wsIHVsLCBsaSwgZmllbGRzZXQsIGZvcm0sIGlucHV0LCBidXR0b24sIHRleHRhcmVhLCBsYWJlbCwgbGVnZW5kLCB0YWJsZSwgY2FwdGlvbixcbnRib2R5LCB0Zm9vdCwgdGhlYWQsIHRyLCB0aCwgdGQsXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsXG5mb290ZXIsIGhlYWRlciwgaGdyb3VwLCBtZW51LCBuYXYsIHNlY3Rpb24ge1xuICBtYXJnaW46IDA7XG4gIHBhZGRpbmc6IDA7XG4gIGJvcmRlcjogMDtcbiAgYm9yZGVyLXJhZGl1czogMDtcbiAgLyogZm9yIGlPUyAqL1xuICBmb250LXdlaWdodDogaW5oZXJpdDtcbiAgZm9udC1zdHlsZTogaW5oZXJpdDtcbiAgZm9udC1mYW1pbHk6IGluaGVyaXQ7XG4gIGxpbmUtaGVpZ2h0OiAxO1xuICB0ZXh0LWRlY29yYXRpb246IG5vbmU7XG4gIHZlcnRpY2FsLWFsaWduOiBiYXNlbGluZTsgfVxuXG5odG1sIHtcbiAgb3ZlcmZsb3cteTogc2Nyb2xsO1xuICAtd2Via2l0LXRleHQtc2l6ZS1hZGp1c3Q6IDEwMCU7IH1cblxub2wsIHVsIHtcbiAgbGlzdC1zdHlsZTogbm9uZTsgfVxuXG50YWJsZSB7XG4gIGJvcmRlci1jb2xsYXBzZTogY29sbGFwc2U7XG4gIGJvcmRlci1zcGFjaW5nOiAwOyB9XG5cbmNhcHRpb24sIHRoLCB0ZCB7XG4gIHRleHQtYWxpZ246IGxlZnQ7XG4gIGZvbnQtd2VpZ2h0OiBub3JtYWw7IH1cblxuc3VwLCBzdWIge1xuICBsaW5lLWhlaWdodDogMTsgfVxuXG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2IHtcbiAgZm9udC13ZWlnaHQ6IG5vcm1hbDsgfVxuXG5hcnRpY2xlLCBhc2lkZSwgY2FudmFzLCBkZXRhaWxzLCBmaWdjYXB0aW9uLCBmaWd1cmUsIGZvb3RlciwgaGVhZGVyLCBoZ3JvdXAsIG1lbnUsIG5hdiwgc2VjdGlvbiB7XG4gIGRpc3BsYXk6IGJsb2NrOyB9XG5cbiosICo6YmVmb3JlLCAqOmFmdGVyIHtcbiAgYm94LXNpemluZzogYm9yZGVyLWJveDsgfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgQkFTSUNTXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbi8qLS0gTm9ybWFsaXplIC0tKi9cbmVtIHtcbiAgZm9udC1zdHlsZTogaXRhbGljOyB9XG5cbnN0cm9uZyB7XG4gIGZvbnQtd2VpZ2h0OiBib2xkOyB9XG5cbnE6bGFuZyhlbikge1xuICBxdW90ZXM6ICcmIzgyMjA7JyAnJiM4MjIxOycgJyYjODIxNjsnICcmIzgyMTc7JzsgfVxuXG5oMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBwIHtcbiAgbWFyZ2luLWJvdHRvbTogMTVweDsgfVxuXG51bCB7XG4gIGxpc3Qtc3R5bGU6IG91dHNpZGUgZGlzYztcbiAgcGFkZGluZy1sZWZ0OiAyNHB4OyB9XG5cbm9sIHtcbiAgbGlzdC1zdHlsZTogb3V0c2lkZSBkZWNpbWFsO1xuICBwYWRkaW5nLWxlZnQ6IDI0cHg7IH1cblxuYSwgbGFiZWwsIGlucHV0W3R5cGU9YnV0dG9uXSwgaW5wdXRbdHlwZT1zdWJtaXRdLCBidXR0b24ge1xuICBjdXJzb3I6IHBvaW50ZXI7IH1cblxuZGVsIHtcbiAgdGV4dC1kZWNvcmF0aW9uOiBsaW5lLXRocm91Z2g7IH1cblxuYWJiclt0aXRsZV0sIGRmblt0aXRsZV0ge1xuICBib3JkZXItYm90dG9tOiAxcHggZG90dGVkO1xuICBjdXJzb3I6IGhlbHA7IH1cblxucHJlLCBjb2RlIHtcbiAgYmFja2dyb3VuZDogI2VlZTtcbiAgYm9yZGVyOiAxcHggc29saWQgI2NjYztcbiAgZm9udC1mYW1pbHk6IENvbnNvbGFzLCAnTHVjaWRhIENvbnNvbGUnLCAnQ291cmllciBOZXcnLCBzZXJpZjtcbiAgcGFkZGluZzogMiU7IH1cblxuaW5wdXQsIHNlbGVjdCwgdGV4dGFyZWEge1xuICBib3JkZXI6IDFweCBzb2xpZCAjY2NjO1xuICBmb250LWZhbWlseTogaW5oZXJpdDtcbiAgZm9udC1zaXplOiBpbmhlcml0O1xuICBwYWRkaW5nOiAzcHggNnB4OyB9XG5cbmlucHV0LCBzZWxlY3Qge1xuICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlOyB9XG5cbnRleHRhcmVhIHtcbiAgb3ZlcmZsb3c6IGF1dG87IH1cblxuLyogcHJldmVudHMgc2Nyb2xsYmFyIGZyb20gc2hvd2luZyB1cCB3aGVuIHVubmVlZGVkIGluIElFICovXG5zbWFsbCwgc3VwLCBzdWIge1xuICBmb250LXNpemU6IDg1JTsgfVxuXG5zdXAge1xuICB2ZXJ0aWNhbC1hbGlnbjogc3VwZXI7IH1cblxuc3ViIHtcbiAgdmVydGljYWwtYWxpZ246IHN1YjsgfVxuXG50YWJsZSB7XG4gIG1hcmdpbjogMTBweCAwO1xuICBwYWRkaW5nOiAzcHg7IH1cblxudGhlYWQge1xuICBib3JkZXItYm90dG9tOiAycHggc29saWQgI2NjYzsgfVxuXG50aCB7XG4gIGZvbnQtd2VpZ2h0OiBib2xkOyB9XG5cbnRoLCB0ciwgdGQge1xuICBwYWRkaW5nOiA0cHggMTJweDsgfVxuXG50ciB7XG4gIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCAjY2NjOyB9XG5cbi8qLS0gQ2xlYXJmaXggLS0qL1xuLmNsZWFyZml4OmJlZm9yZSxcbi5jbGVhcmZpeDphZnRlciB7XG4gIGNvbnRlbnQ6IFwiIFwiO1xuICBkaXNwbGF5OiB0YWJsZTsgfVxuXG4uY2xlYXJmaXg6YWZ0ZXIge1xuICBjbGVhcjogYm90aDsgfVxuXG4uaWU3IC5jbGVhcmZpeCB7XG4gIHpvb206IDE7IH1cblxuLyotLSBuZy1jbG9hazogcHJldmVudCBGT1VDIGJlZm9yZSBBbmd1bGFyIEphdmFTY3JpcHQgbG9hZHMgLS0qL1xuW25nXFw6Y2xvYWtdLFxuW25nLWNsb2FrXSxcbltkYXRhLW5nLWNsb2FrXSxcblt4LW5nLWNsb2FrXSxcbi5uZy1jbG9hayxcbi54LW5nLWNsb2FrIHtcbiAgZGlzcGxheTogbm9uZSAhaW1wb3J0YW50OyB9XG5cbi8qLS0gSW1hZ2UgUmVwbGFjZW1lbnQgLS0qL1xuLmlyIHtcbiAgb3ZlcmZsb3c6IGhpZGRlbjtcbiAgdGV4dC1pbmRlbnQ6IDIwMCU7XG4gIHdoaXRlLXNwYWNlOiBub3dyYXA7IH1cblxuLyotLSBJbmxpbmUgbGluayB0b3VjaCB0YXJnZXRzIC0tKi9cbi50b3VjaCBwIGEge1xuICBtYXJnaW46IDAgLS41ZW07XG4gIHBhZGRpbmc6IDAgLjVlbTsgfVxuXG4vKi0tIEZvcm1zIC0tKi9cbmlucHV0W3R5cGU9XCJ0ZXh0XCJdLFxuaW5wdXRbdHlwZT1cIm51bWJlclwiXSxcbmlucHV0W3R5cGU9XCJwYXNzd29yZFwiXSxcbnRleHRhcmVhIHtcbiAgZm9udC1zaXplOiAxNnB4O1xuICAvKiBmb3IgaU9TIHBob25lcyAqLyB9XG5cbmlucHV0Lm5nLWRpcnR5Lm5nLWludmFsaWQge1xuICBib3JkZXItY29sb3I6IHJnYmEoMTY5LCA2OCwgNjYsIDAuNik7XG4gIGJveC1zaGFkb3c6IDAgMCA2cHggcmdiYSgxNjksIDY4LCA2NiwgMC42KTsgfVxuXG5idXR0b24ge1xuICBib3JkZXItcmFkaXVzOiA0cHg7XG4gIGRpc3BsYXk6IGlubGluZS1ibG9jaztcbiAgZm9udC1zaXplOiBpbmhlcml0O1xuICBwYWRkaW5nOiA1cHggNHB4O1xuICB2ZXJ0aWNhbC1hbGlnbjogbWlkZGxlOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICBGT05UU1xyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4vKiBGdWxsIEBmb250LWZhY2UgZGVjbGFyYXRpb24gKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgUFJFU0VOVEFUSU9OXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmJvZHkge1xuICBiYWNrZ3JvdW5kOiAjZWVlO1xuICBjb2xvcjogIzMzMztcbiAgZm9udDogbm9ybWFsIDE2cHgvMS4yICdIZWx2ZXRpY2EgTmV1ZScsIEhlbHZldGljYSwgQXJpYWwsIFZlcmRhbmEsIHNhbnMtc2VyaWY7IH1cblxuLmxheW91dC1jYW52YXMge1xuICBiYWNrZ3JvdW5kOiAjZmZmOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBDb250ZW50IC0tKi9cbi5jb250ZW50LXdyYXBwZXIge1xuICBwYWRkaW5nOiAzJTsgfVxuICBAbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAgIC5jb250ZW50LXdyYXBwZXIge1xuICAgICAgcGFkZGluZzogMS41JSAzJTsgfSB9XG5cbi5jb250ZW50LWhlYWRpbmcge1xuICBmb250LXNpemU6IDM2cHg7XG4gIG1hcmdpbjogLTEuNSUgMCAyMHB4OyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmNvbnRlbnQtaGVhZGluZyB7XG4gICAgICBtYXJnaW4tdG9wOiAwO1xuICAgICAgdGV4dC1hbGlnbjogY2VudGVyOyB9IH1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG4gTEFZT1VUIEZVTkNUSU9OQUxJVFlcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuYm9keSB7XG4gIG1pbi13aWR0aDogMzIwcHg7IH1cblxuLmxheW91dC1vdmVyZmxvdyB7XG4gIG92ZXJmbG93OiBoaWRkZW47XG4gIC8qIG5lY2Vzc2FyeSB0byBoYW5kbGUgb2ZmY2FudmFzIHNjcm9sbGJhciBiZWhhdmlvciAqLyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBPZmYtY2FudmFzIEZ1bmN0aW9uYWxpdHkgLS0qL1xuLyotLSBDb250ZW50IGNhbnZhcyB3cmFwcGVyIC0tKi9cbi5sYXlvdXQtY2FudmFzIHtcbiAgYmFja2ZhY2UtdmlzaWJpbGl0eTogaGlkZGVuO1xuICBwb3NpdGlvbjogcmVsYXRpdmU7XG4gIGxlZnQ6IDA7XG4gIHdpZHRoOiAxMDAlOyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmxheW91dC1jYW52YXMge1xuICAgICAgdHJhbnNpdGlvbjogbm9uZTtcbiAgICAgIHRyYW5zZm9ybTogbm9uZTsgfSB9XG4gIC5jc3N0cmFuc2Zvcm1zM2QgLmxheW91dC1jYW52YXMge1xuICAgIHRyYW5zaXRpb246IHRyYW5zZm9ybSAyNTBtcyBlYXNlO1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMCwgMCwgMCk7IH1cbiAgLmNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gLmxheW91dC1jYW52YXMge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoMjcwcHgsIDAsIDApOyB9XG4gIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5sYXlvdXQtY2FudmFzIHtcbiAgICBsZWZ0OiAyNzBweDsgfVxuXG4vKi0tIEhlYWRlciAtLSovXG5AbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAuaGVhZGVyLW1vYmlsZS1wYWdlIHtcbiAgICBkaXNwbGF5OiBub25lOyB9IH1cblxuLyotLSBOYXZpZ2F0aW9uIC0tKi9cbi5oZWFkZXIgLm5hdiB7XG4gIGJhY2tmYWNlLXZpc2liaWxpdHk6IGhpZGRlbjtcbiAgZGlzcGxheTogbm9uZTtcbiAgLyogZGVhbCB3aXRoIEZPVUMgKi9cbiAgaGVpZ2h0OiAxMDAlO1xuICBvdmVyZmxvdy15OiBhdXRvO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgd2lkdGg6IDI3MHB4OyB9XG4gIEBtZWRpYSBzY3JlZW4gYW5kIChtaW4td2lkdGg6IDc2OHB4KSB7XG4gICAgLmhlYWRlciAubmF2IHtcbiAgICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgICAgcG9zaXRpb246IHJlbGF0aXZlO1xuICAgICAgd2lkdGg6IDEwMCU7IH0gfVxuICAubmF2LWNsb3NlZCAuaGVhZGVyIC5uYXYsXG4gIC5uYXYtb3BlbiAuaGVhZGVyIC5uYXYge1xuICAgIGRpc3BsYXk6IGJsb2NrO1xuICAgIC8qIGRlYWwgd2l0aCBGT1VDICovIH1cbiAgLmNzc3RyYW5zZm9ybXMzZCAuaGVhZGVyIC5uYXYge1xuICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlM2QoLTEwMCUsIDAsIDApOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5jc3N0cmFuc2Zvcm1zM2QgLmhlYWRlciAubmF2IHtcbiAgICAgICAgdHJhbnNmb3JtOiBub25lOyB9IH1cbiAgLm5vLWNzc3RyYW5zZm9ybXMzZCAubmF2LWNsb3NlZCAuaGVhZGVyIC5uYXYge1xuICAgIGxlZnQ6IC0xMDAlOyB9XG4gIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5oZWFkZXIgLm5hdiB7XG4gICAgbGVmdDogLTI3MHB4OyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuIC5oZWFkZXIgLm5hdiB7XG4gICAgICAgIGxlZnQ6IDA7IH0gfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgUFJJTlRcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuQG1lZGlhIHByaW50IHtcbiAgKiB7XG4gICAgYmFja2dyb3VuZDogI2ZmZjsgfVxuICBib2R5IHtcbiAgICBjb2xvcjogIzAwMDtcbiAgICBmb250OiBub3JtYWwgMTZweC8xLjQgR2VvcmdpYSwgJ1RpbWVzIE5ldyBSb21hbicsIHNlcmlmOyB9XG4gIC8qLS0gSGlkZGVuIEVsZW1lbnRzIC0tKi9cbiAgLmhlYWRlcixcbiAgLmZvb3RlciB7XG4gICAgZGlzcGxheTogbm9uZTsgfVxuICAvKi0tIFNob3cgbGluayBVUkxzIC0tKi9cbiAgYTpsaW5rLFxuICBhOnZpc2l0ZWQge1xuICAgIGNvbG9yOiBibHVlO1xuICAgIHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lOyB9XG4gIGE6bGluazphZnRlcixcbiAgYTp2aXNpdGVkOmFmdGVyIHtcbiAgICBjb250ZW50OiBcIiBbXCIgYXR0cihocmVmKSBcIl0gXCI7XG4gICAgZm9udC1zaXplOiA3NSU7IH0gfVxuXG4vKi0tIE1vZHVsZXMgLS0qL1xuLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgICBIRUFERVJcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuLmhlYWRlci1tb2JpbGUtcGFnZSB7XG4gIGJhY2tncm91bmQ6ICM1NTU7XG4gIGNvbG9yOiAjZmZmO1xuICBoZWlnaHQ6IDUwcHg7XG4gIG1hcmdpbi1ib3R0b206IDEwcHg7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTsgfVxuICAuaGVhZGVyLW1vYmlsZS1wYWdlLXNpdGVUaXRsZSB7XG4gICAgZm9udC1zaXplOiAzMHB4O1xuICAgIGxpbmUtaGVpZ2h0OiA1MHB4O1xuICAgIG1hcmdpbjogMDtcbiAgICBwYWRkaW5nOiAwIDAgMCA1MHB4O1xuICAgIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgICB0b3A6IDA7XG4gICAgdGV4dC1hbGlnbjogY2VudGVyO1xuICAgIHdpZHRoOiAxMDAlOyB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgTkFWSUdBVElPTlxyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gSGFtYnVyZ2VyIG1lbnUgdG9nZ2xlIC0tKi9cbi50b2dnbGUtb2ZmY2FudmFzIHtcbiAgYmFja2dyb3VuZDogIzU1NTtcbiAgYm9yZGVyLXJpZ2h0OiAxcHggc29saWQgcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjUpO1xuICBkaXNwbGF5OiBpbmxpbmUtYmxvY2s7XG4gIGhlaWdodDogNTBweDtcbiAgcGFkZGluZzogMjMuNXB4IDEzcHg7XG4gIHBvc2l0aW9uOiByZWxhdGl2ZTtcbiAgdGV4dC1hbGlnbjogY2VudGVyO1xuICB3aWR0aDogNTBweDtcbiAgei1pbmRleDogMTAwOyB9XG4gIC50b2dnbGUtb2ZmY2FudmFzIHNwYW4sXG4gIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YmVmb3JlLFxuICAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmFmdGVyIHtcbiAgICBiYWNrZ3JvdW5kOiAjZmZmO1xuICAgIGJvcmRlci1yYWRpdXM6IDFweDtcbiAgICBjb250ZW50OiAnJztcbiAgICBkaXNwbGF5OiBibG9jaztcbiAgICBoZWlnaHQ6IDNweDtcbiAgICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gICAgdHJhbnNpdGlvbjogYWxsIDI1MG1zIGVhc2UtaW4tb3V0O1xuICAgIHdpZHRoOiAyNHB4OyB9XG4gIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YmVmb3JlIHtcbiAgICB0b3A6IC05cHg7IH1cbiAgLnRvZ2dsZS1vZmZjYW52YXMgc3BhbjphZnRlciB7XG4gICAgYm90dG9tOiAtOXB4OyB9XG4gIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuIHtcbiAgICBiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDsgfVxuICAgIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmJlZm9yZSwgLm5hdi1vcGVuIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YWZ0ZXIge1xuICAgICAgdG9wOiAwOyB9XG4gICAgLm5hdi1vcGVuIC50b2dnbGUtb2ZmY2FudmFzIHNwYW46YmVmb3JlIHtcbiAgICAgIHRyYW5zZm9ybTogcm90YXRlKDQ1ZGVnKTsgfVxuICAgIC5uYXYtb3BlbiAudG9nZ2xlLW9mZmNhbnZhcyBzcGFuOmFmdGVyIHtcbiAgICAgIHRyYW5zZm9ybTogcm90YXRlKC00NWRlZyk7IH1cblxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIE5hdmlnYXRpb24gLS0qL1xuLmhlYWRlciAubmF2IHtcbiAgYm94LXNoYWRvdzogaW5zZXQgLThweCAwIDhweCAtNnB4IHJnYmEoMCwgMCwgMCwgMC4yKTtcbiAgcGFkZGluZzogMyU7IH1cbiAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAuaGVhZGVyIC5uYXYge1xuICAgICAgYmFja2dyb3VuZDogIzU1NTtcbiAgICAgIGJveC1zaGFkb3c6IG5vbmU7XG4gICAgICBwYWRkaW5nOiAxLjUlIDMlOyB9IH1cbiAgLmhlYWRlciAubmF2IC5hY3RpdmUgLm5hdi1saXN0LWl0ZW0tdGV4dCB7XG4gICAgZm9udC13ZWlnaHQ6IGJvbGQ7IH1cbiAgICBAbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAgICAgLmhlYWRlciAubmF2IC5hY3RpdmUgLm5hdi1saXN0LWl0ZW0tdGV4dCB7XG4gICAgICAgIGJvcmRlci1ib3R0b206IDFweCBzb2xpZCByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuNSk7IH0gfVxuICAuaGVhZGVyIC5uYXYtbGlzdCB7XG4gICAgbGlzdC1zdHlsZTogbm9uZTtcbiAgICBtYXJnaW4tYm90dG9tOiAwO1xuICAgIHBhZGRpbmctbGVmdDogMDsgfVxuICAgIC5oZWFkZXIgLm5hdi1saXN0IGEge1xuICAgICAgZGlzcGxheTogYmxvY2s7XG4gICAgICBwYWRkaW5nOiA2cHg7IH1cbiAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGE6aG92ZXIsIC5oZWFkZXIgLm5hdi1saXN0IGE6YWN0aXZlLCAuaGVhZGVyIC5uYXYtbGlzdCBhOmZvY3VzIHtcbiAgICAgICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOyB9XG4gICAgQG1lZGlhIHNjcmVlbiBhbmQgKG1pbi13aWR0aDogNzY4cHgpIHtcbiAgICAgIC5oZWFkZXIgLm5hdi1saXN0IHtcbiAgICAgICAgZGlzcGxheTogZmxleDtcbiAgICAgICAgZmxleC13cmFwOiB3cmFwO1xuICAgICAgICBqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcbiAgICAgICAgcGFkZGluZzogMDtcbiAgICAgICAgd2lkdGg6IDEwMCU7IH1cbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYSxcbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTpob3ZlcixcbiAgICAgICAgLmhlYWRlciAubmF2LWxpc3QgYTphY3RpdmUsXG4gICAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGE6Zm9jdXMge1xuICAgICAgICAgIGNvbG9yOiAjZmZmOyB9XG4gICAgICAgIC5oZWFkZXIgLm5hdi1saXN0IGxpIHtcbiAgICAgICAgICBwYWRkaW5nOiAwIDIwcHg7IH0gfVxuXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgIEZPT1RFUlxyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4uZm9vdGVyIHtcbiAgcGFkZGluZzogMyU7XG4gIHRleHQtYWxpZ246IGNlbnRlcjsgfVxuICBAbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAgIC5mb290ZXIge1xuICAgICAgcGFkZGluZzogMS41JSAzJTsgfSB9XG5cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICAgTE9BRElOR1xyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG4ubG9hZGluZy13cmFwcGVyIHtcbiAgaGVpZ2h0OiAxMDAlO1xuICBwb3NpdGlvbjogZml4ZWQ7XG4gIHRvcDogMDtcbiAgbGVmdDogMDtcbiAgd2lkdGg6IDEwMCU7XG4gIHotaW5kZXg6IDk5OTsgfVxuXG4ubG9hZGluZy1vdmVybGF5IHtcbiAgYmFja2dyb3VuZDogcmdiYSgyNTUsIDI1NSwgMjU1LCAwLjkpO1xuICBwb3NpdGlvbjogYWJzb2x1dGU7XG4gIHRvcDogMDtcbiAgYm90dG9tOiAwO1xuICB3aWR0aDogMTAwJTsgfVxuXG4ubG9hZGluZy1zcGlubmVyIHtcbiAgbWFyZ2luOiAtMTZweCAwIDAgLTE2cHg7XG4gIHBvc2l0aW9uOiBhYnNvbHV0ZTtcbiAgdG9wOiA1MCU7XG4gIGxlZnQ6IDUwJTsgfVxuXG4vKi0tIFBhZ2VzIC0tKi9cbi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICAgNDA0XHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICBGT05UU1xyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXHJcblxyXG4vKiBGdWxsIEBmb250LWZhY2UgZGVjbGFyYXRpb24gKi9cclxuXHJcbkBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDogMzIwcHgpLCBzY3JlZW4gYW5kIChtYXgtZGV2aWNlLXdpZHRoOiA3MjBweCkgYW5kIChvcmllbnRhdGlvbjpwb3J0cmFpdCksIHNjcmVlbiBhbmQgKG1heC1kZXZpY2Utd2lkdGg6IDEyODBweCkgYW5kIChvcmllbnRhdGlvbjpsYW5kc2NhcGUpIHtcclxuXHQvKiBmb3IgbW9iaWxlIGRldmljZXMsIG9ubHkgbG9hZCBTVkcgZm9udFxyXG5cdHNlZTogaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yMDg5MDQ4OS9mb250LWZhY2UtZGVjbGFyYXRpb25zLWRvbnQtd29yay1pbi1hbmRyb2lkLTQtMy1pbnRlcm5ldC1icm93c2VyXHJcblxyXG5cdEBmb250LWZhY2Uge1xyXG5cdFx0Zm9udC1mYW1pbHk6ICdGb250TmFtZSc7XHJcblx0XHRzcmM6IHVybCgnLi4vZm9udHMvRm9udE5hbWUuc3ZnI0ZvbnROYW1lJykgZm9ybWF0KCdzdmcnKTtcclxuXHRcdGZvbnQtd2VpZ2h0OiBub3JtYWw7XHJcblx0XHRmb250LXN0eWxlOiBub3JtYWw7XHJcblx0fSAqL1xyXG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCBQUkVTRU5UQVRJT05cclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuYm9keSB7XHJcblx0YmFja2dyb3VuZDogJGNvbG9yLWJnLWxpZ2h0O1xyXG5cdGNvbG9yOiAkY29sb3ItdGV4dDtcclxuXHRmb250OiBub3JtYWwgMTZweC8xLjIgJ0hlbHZldGljYSBOZXVlJywgSGVsdmV0aWNhLCBBcmlhbCwgVmVyZGFuYSwgc2Fucy1zZXJpZjtcclxufVxyXG4ubGF5b3V0LWNhbnZhcyB7XHJcblx0YmFja2dyb3VuZDogJGNvbG9yLWJnO1xyXG59XHJcblxyXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gQ29udGVudCAtLSovXHJcblxyXG4uY29udGVudC13cmFwcGVyIHtcclxuXHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tc21hbGw7XHJcblxyXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xyXG5cdFx0cGFkZGluZzogJHBhZGRpbmctc2NyZWVuLWxhcmdlO1xyXG5cdH1cclxufVxyXG4uY29udGVudC1oZWFkaW5nIHtcclxuXHRmb250LXNpemU6IDM2cHg7XHJcblx0bWFyZ2luOiAtMS41JSAwIDIwcHg7XHJcblxyXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xyXG5cdFx0bWFyZ2luLXRvcDogMDtcclxuXHRcdHRleHQtYWxpZ246IGNlbnRlcjtcclxuXHR9XHJcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcbiBMQVlPVVQgRlVOQ1RJT05BTElUWVxyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXHJcblxyXG5ib2R5IHtcclxuXHRtaW4td2lkdGg6IDMyMHB4O1xyXG59XHJcbi5sYXlvdXQtb3ZlcmZsb3cge1xyXG5cdG92ZXJmbG93OiBoaWRkZW47XHQvKiBuZWNlc3NhcnkgdG8gaGFuZGxlIG9mZmNhbnZhcyBzY3JvbGxiYXIgYmVoYXZpb3IgKi9cclxufVxyXG5cclxuLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIE9mZi1jYW52YXMgRnVuY3Rpb25hbGl0eSAtLSovXHJcblxyXG4vKi0tIENvbnRlbnQgY2FudmFzIHdyYXBwZXIgLS0qL1xyXG5cclxuLmxheW91dC1jYW52YXMge1xyXG5cdGJhY2tmYWNlLXZpc2liaWxpdHk6IGhpZGRlbjtcclxuXHRwb3NpdGlvbjogcmVsYXRpdmU7XHJcblx0bGVmdDogMDtcclxuXHR3aWR0aDogMTAwJTtcclxuXHJcblx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XHJcblx0XHR0cmFuc2l0aW9uOiBub25lO1xyXG5cdFx0dHJhbnNmb3JtOiBub25lO1xyXG5cdH1cclxuXHQuY3NzdHJhbnNmb3JtczNkICYge1xyXG5cdFx0dHJhbnNpdGlvbjogdHJhbnNmb3JtIDI1MG1zIGVhc2U7XHJcblx0XHR0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDAsMCwwKTtcclxuXHR9XHJcblx0LmNzc3RyYW5zZm9ybXMzZCAubmF2LW9wZW4gJiB7XHJcblx0XHR0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKDI3MHB4LDAsMCk7XHJcblx0fVxyXG5cdC5uby1jc3N0cmFuc2Zvcm1zM2QgLm5hdi1vcGVuICYge1xyXG5cdFx0bGVmdDogMjcwcHg7XHJcblx0fVxyXG59XHJcblxyXG4vKi0tIEhlYWRlciAtLSovXHJcblxyXG4uaGVhZGVyLW1vYmlsZS1wYWdlIHtcclxuXHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcclxuXHRcdGRpc3BsYXk6IG5vbmU7XHJcblx0fVxyXG59XHJcblxyXG4vKi0tIE5hdmlnYXRpb24gLS0qL1xyXG5cclxuLmhlYWRlciAubmF2IHtcclxuXHRiYWNrZmFjZS12aXNpYmlsaXR5OiBoaWRkZW47XHJcblx0ZGlzcGxheTogbm9uZTtcdC8qIGRlYWwgd2l0aCBGT1VDICovXHJcblx0aGVpZ2h0OiAxMDAlO1xyXG5cdG92ZXJmbG93LXk6IGF1dG87XHJcblx0cG9zaXRpb246IGFic29sdXRlO1xyXG5cdHRvcDogMDtcclxuXHR3aWR0aDogMjcwcHg7XHJcblxyXG5cdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xyXG5cdFx0ZGlzcGxheTogYmxvY2s7XHJcblx0XHRwb3NpdGlvbjogcmVsYXRpdmU7XHJcblx0XHR3aWR0aDogMTAwJTtcclxuXHR9XHJcblx0Lm5hdi1jbG9zZWQgJixcclxuXHQubmF2LW9wZW4gJiB7XHJcblx0XHRkaXNwbGF5OiBibG9jaztcdC8qIGRlYWwgd2l0aCBGT1VDICovXHJcblx0fVxyXG5cdC5jc3N0cmFuc2Zvcm1zM2QgJiB7XHJcblx0XHR0cmFuc2Zvcm06IHRyYW5zbGF0ZTNkKC0xMDAlLDAsMCk7XHJcblxyXG5cdFx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XHJcblx0XHRcdHRyYW5zZm9ybTogbm9uZTtcclxuXHRcdH1cclxuXHR9XHJcblx0Lm5vLWNzc3RyYW5zZm9ybXMzZCAubmF2LWNsb3NlZCAmIHtcclxuXHRcdGxlZnQ6IC0xMDAlO1xyXG5cdH1cclxuXHQubm8tY3NzdHJhbnNmb3JtczNkIC5uYXYtb3BlbiAmIHtcclxuXHRcdGxlZnQ6IC0yNzBweDtcclxuXHJcblx0XHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcclxuXHRcdFx0bGVmdDogMDtcclxuXHRcdH1cclxuXHR9XHJcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tXHJcblx0ICAgUFJJTlRcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuQG1lZGlhIHByaW50IHtcclxuXHJcblx0KiB7XHJcblx0XHRiYWNrZ3JvdW5kOiAjZmZmO1xyXG5cdH1cclxuXHRib2R5IHtcclxuXHRcdGNvbG9yOiAjMDAwO1xyXG5cdFx0Zm9udDogbm9ybWFsIDE2cHgvMS40IEdlb3JnaWEsICdUaW1lcyBOZXcgUm9tYW4nLCBzZXJpZjtcclxuXHR9XHJcblxyXG5cdC8qLS0gSGlkZGVuIEVsZW1lbnRzIC0tKi9cclxuXHJcblx0LmhlYWRlcixcclxuXHQuZm9vdGVyIHtcclxuXHRcdGRpc3BsYXk6IG5vbmU7XHJcblx0fVxyXG5cclxuXHQvKi0tIFNob3cgbGluayBVUkxzIC0tKi9cclxuXHJcblx0YTpsaW5rLFxyXG5cdGE6dmlzaXRlZCB7XHJcblx0XHRjb2xvcjogYmx1ZTtcclxuXHRcdHRleHQtZGVjb3JhdGlvbjogdW5kZXJsaW5lO1xyXG5cdH1cclxuXHRhOmxpbms6YWZ0ZXIsXHJcblx0YTp2aXNpdGVkOmFmdGVyIHtcclxuXHRcdGNvbnRlbnQ6XCIgW1wiIGF0dHIoaHJlZikgXCJdIFwiO1xyXG5cdFx0Zm9udC1zaXplOiA3NSU7XHJcblx0fVxyXG5cclxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgICAgSEVBREVSXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cclxuXHJcbi5oZWFkZXItbW9iaWxlLXBhZ2Uge1xyXG5cdGJhY2tncm91bmQ6ICRjb2xvci1uYXYtYmc7XHJcblx0Y29sb3I6ICRjb2xvci13aGl0ZTtcclxuXHRoZWlnaHQ6IDUwcHg7XHJcblx0bWFyZ2luLWJvdHRvbTogMTBweDtcclxuXHRwb3NpdGlvbjogcmVsYXRpdmU7XHJcblxyXG5cdCYtc2l0ZVRpdGxlIHtcclxuXHRcdGZvbnQtc2l6ZTogMzBweDtcclxuXHRcdGxpbmUtaGVpZ2h0OiA1MHB4O1xyXG5cdFx0bWFyZ2luOiAwO1xyXG5cdFx0cGFkZGluZzogMCAwIDAgNTBweDtcclxuXHRcdHBvc2l0aW9uOiBhYnNvbHV0ZTtcclxuXHRcdHRvcDogMDtcclxuXHRcdHRleHQtYWxpZ246IGNlbnRlcjtcclxuXHRcdHdpZHRoOiAxMDAlO1xyXG5cdH1cclxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS1cclxuXHQgTkFWSUdBVElPTlxyXG4tLS0tLS0tLS0tLS0tLS0tLS0tLSovXHJcblxyXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gSGFtYnVyZ2VyIG1lbnUgdG9nZ2xlIC0tKi9cclxuXHJcbi50b2dnbGUtb2ZmY2FudmFzIHtcclxuXHRiYWNrZ3JvdW5kOiAkY29sb3ItbmF2LWJnO1xyXG5cdGJvcmRlci1yaWdodDogMXB4IHNvbGlkICRjb2xvci1kaXNhYmxlZC1yZ2JhO1xyXG5cdGRpc3BsYXk6IGlubGluZS1ibG9jaztcclxuXHRoZWlnaHQ6IDUwcHg7XHJcblx0cGFkZGluZzogMjMuNXB4IDEzcHg7XHJcblx0cG9zaXRpb246IHJlbGF0aXZlO1xyXG5cdHRleHQtYWxpZ246IGNlbnRlcjtcclxuXHR3aWR0aDogNTBweDtcclxuXHR6LWluZGV4OiAxMDA7XHJcblxyXG5cdHNwYW4sXHJcblx0c3BhbjpiZWZvcmUsXHJcblx0c3BhbjphZnRlciB7XHJcblx0XHRiYWNrZ3JvdW5kOiAkY29sb3Itd2hpdGU7XHJcblx0XHRib3JkZXItcmFkaXVzOiAxcHg7XHJcblx0XHRjb250ZW50OiAnJztcclxuXHRcdGRpc3BsYXk6IGJsb2NrO1xyXG5cdFx0aGVpZ2h0OiAzcHg7XHJcblx0XHRwb3NpdGlvbjogYWJzb2x1dGU7XHJcblx0XHR0cmFuc2l0aW9uOiBhbGwgMjUwbXMgZWFzZS1pbi1vdXQ7XHJcblx0XHR3aWR0aDogMjRweDtcclxuXHR9XHJcblx0c3BhbiB7XHJcblx0XHQmOmJlZm9yZSB7XHJcblx0XHRcdHRvcDogLTlweDtcclxuXHRcdH1cclxuXHRcdCY6YWZ0ZXIge1xyXG5cdFx0XHRib3R0b206IC05cHg7XHJcblx0XHR9XHJcblx0fVxyXG5cdC5uYXYtb3BlbiAmIHtcclxuXHRcdHNwYW4ge1xyXG5cdFx0XHRiYWNrZ3JvdW5kOiB0cmFuc3BhcmVudDtcclxuXHJcblx0XHRcdCY6YmVmb3JlLFxyXG5cdFx0XHQmOmFmdGVyIHtcclxuXHRcdFx0XHR0b3A6IDA7XHJcblx0XHRcdH1cclxuXHRcdFx0JjpiZWZvcmUge1xyXG5cdFx0XHRcdHRyYW5zZm9ybTogcm90YXRlKDQ1ZGVnKTtcclxuXHRcdFx0fVxyXG5cdFx0XHQmOmFmdGVyIHtcclxuXHRcdFx0XHR0cmFuc2Zvcm06IHJvdGF0ZSgtNDVkZWcpO1xyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fVxyXG59XHJcblxyXG4vKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gTmF2aWdhdGlvbiAtLSovXHJcblxyXG4uaGVhZGVyIC5uYXYge1xyXG5cdGJveC1zaGFkb3c6IGluc2V0IC04cHggMCA4cHggLTZweCByZ2JhKDAsMCwwLDAuMik7XHJcblx0cGFkZGluZzogJHBhZGRpbmctc2NyZWVuLXNtYWxsO1xyXG5cclxuXHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcclxuXHRcdGJhY2tncm91bmQ6ICRjb2xvci1uYXYtYmc7XHJcblx0XHRib3gtc2hhZG93OiBub25lO1xyXG5cdFx0cGFkZGluZzogJHBhZGRpbmctc2NyZWVuLWxhcmdlO1xyXG5cdH1cclxuXHJcblx0LmFjdGl2ZSAubmF2LWxpc3QtaXRlbS10ZXh0IHtcclxuXHRcdGZvbnQtd2VpZ2h0OiBib2xkO1xyXG5cclxuXHRcdEBpbmNsdWRlIG1xKCRtcS1sYXJnZSkge1xyXG5cdFx0XHRib3JkZXItYm90dG9tOiAxcHggc29saWQgJGNvbG9yLWRpc2FibGVkLXJnYmE7XHJcblx0XHR9XHJcblx0fVxyXG5cdCYtbGlzdCB7XHJcblx0XHRsaXN0LXN0eWxlOiBub25lO1xyXG5cdFx0bWFyZ2luLWJvdHRvbTogMDtcclxuXHRcdHBhZGRpbmctbGVmdDogMDtcclxuXHJcblx0XHRhIHtcclxuXHRcdFx0ZGlzcGxheTogYmxvY2s7XHJcblx0XHRcdHBhZGRpbmc6IDZweDtcclxuXHJcblx0XHRcdCY6aG92ZXIsXHJcblx0XHRcdCY6YWN0aXZlLFxyXG5cdFx0XHQmOmZvY3VzIHtcclxuXHRcdFx0XHR0ZXh0LWRlY29yYXRpb246IG5vbmU7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHJcblx0XHRAaW5jbHVkZSBtcSgkbXEtbGFyZ2UpIHtcclxuXHRcdFx0ZGlzcGxheTogZmxleDtcclxuXHRcdFx0ZmxleC13cmFwOiB3cmFwO1xyXG5cdFx0XHRqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjtcclxuXHRcdFx0cGFkZGluZzogMDtcclxuXHRcdFx0d2lkdGg6IDEwMCU7XHJcblxyXG5cdFx0XHRhLFxyXG5cdFx0XHRhOmhvdmVyLFxyXG5cdFx0XHRhOmFjdGl2ZSxcclxuXHRcdFx0YTpmb2N1cyB7XHJcblx0XHRcdFx0Y29sb3I6ICRjb2xvci13aGl0ZTtcclxuXHRcdFx0fVxyXG5cdFx0XHRsaSB7XHJcblx0XHRcdFx0cGFkZGluZzogMCAyMHB4O1xyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fVxyXG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgICBGT09URVJcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuLmZvb3RlciB7XHJcblx0cGFkZGluZzogJHBhZGRpbmctc2NyZWVuLXNtYWxsO1xyXG5cdHRleHQtYWxpZ246IGNlbnRlcjtcclxuXHJcblx0QGluY2x1ZGUgbXEoJG1xLWxhcmdlKSB7XHJcblx0XHRwYWRkaW5nOiAkcGFkZGluZy1zY3JlZW4tbGFyZ2U7XHJcblx0fVxyXG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgICBMT0FESU5HXHJcbi0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cclxuXHJcbi5sb2FkaW5nIHtcclxuXHQmLXdyYXBwZXIge1xyXG5cdFx0aGVpZ2h0OiAxMDAlO1xyXG5cdFx0cG9zaXRpb246IGZpeGVkO1xyXG5cdFx0XHR0b3A6IDA7IGxlZnQ6IDA7XHJcblx0XHR3aWR0aDogMTAwJTtcclxuXHRcdHotaW5kZXg6IDk5OTtcclxuXHR9XHJcblx0Ji1vdmVybGF5IHtcclxuXHRcdGJhY2tncm91bmQ6IHJnYmEoMjU1LDI1NSwyNTUsLjkpO1xyXG5cdFx0cG9zaXRpb246IGFic29sdXRlO1xyXG5cdFx0XHR0b3A6IDA7IGJvdHRvbTogMDtcclxuXHRcdHdpZHRoOiAxMDAlO1xyXG5cdH1cclxuXHQmLXNwaW5uZXIge1xyXG5cdFx0bWFyZ2luOiAtMTZweCAwIDAgLTE2cHg7XHJcblx0XHRwb3NpdGlvbjogYWJzb2x1dGU7XHJcblx0XHRcdHRvcDogNTAlOyBsZWZ0OiA1MCU7XHJcblx0fVxyXG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLVxyXG5cdCAgICA0MDRcclxuLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xyXG5cclxuLmVycm9yNDA0LXdyYXBwZXIge1xyXG5cclxufSJdLCJzb3VyY2VSb290IjoiL3NvdXJjZS8ifQ== */ diff --git a/src/assets/js/scripts.js b/src/assets/js/scripts.js index 527f012..4958d4c 100644 --- a/src/assets/js/scripts.js +++ b/src/assets/js/scripts.js @@ -36,4 +36,4 @@ window.helpers = (function() { } } })(); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhlbHBlcnMuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJzY3JpcHRzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBzdHJpY3QnO1xyXG5cclxud2luZG93LmhlbHBlcnMgPSAoZnVuY3Rpb24oKSB7XHJcblxyXG5cdGluaXQoKTtcclxuXHJcblx0LyoqKlxyXG5cdCAqIGZ1bmN0aW9uIGluaXQoKVxyXG5cdCAqXHJcblx0ICogSW5pdGlhbGl6ZSBwdWJsaWMgd2luZG93LmhlbHBlcnMgZnVuY3Rpb25zXHJcblx0ICovXHJcblx0ZnVuY3Rpb24gaW5pdCgpIHtcclxuXHRcdGZpeEJyb3dzZXJzKCk7XHJcblx0fVxyXG5cclxuXHQvKioqXHJcblx0ICogZnVuY3Rpb24gZml4QnJvd3NlcnMoKVxyXG5cdCAqXHJcblx0ICogRml4IGJyb3dzZXIgd2VpcmRuZXNzXHJcblx0ICogQ29ycmVjdCBNb2Rlcm5penIgYnVnc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGZpeEJyb3dzZXJzKCkge1xyXG5cdFx0dmFyIHVhID0gbmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpLFxyXG5cdFx0XHRjaHJvbWUgPSB1YS5sYXN0SW5kZXhPZignY2hyb21lLycpID4gMCxcclxuXHRcdFx0JGh0bWwgPSAkKCdodG1sJyk7XHJcblx0XHRcclxuXHRcdC8vIE1vZGVybml6ciAyIGJ1ZzogQ2hyb21lIG9uIFdpbmRvd3MgOCBnaXZlcyBhIGZhbHNlIG5lZ2F0aXZlIGZvciB0cmFuc2Zvcm1zM2Qgc3VwcG9ydFxyXG5cdFx0Ly8gR29vZ2xlIGRvZXMgbm90IHBsYW4gdG8gZml4IHRoaXM7IGh0dHBzOi8vY29kZS5nb29nbGUuY29tL3AvY2hyb21pdW0vaXNzdWVzL2RldGFpbD9pZD0xMjkwMDRcclxuXHRcdGlmIChjaHJvbWUpIHtcclxuXHRcdFx0dmFyIGNocm9tZXZlcnNpb24gPSB1YS5zdWJzdHIodWEubGFzdEluZGV4T2YoJ2Nocm9tZS8nKSArIDcsIDIpO1xyXG5cdFx0XHRpZiAoY2hyb21ldmVyc2lvbiA+PSAxMiAmJiAkaHRtbC5oYXNDbGFzcygnbm8tY3NzdHJhbnNmb3JtczNkJykpIHtcclxuXHRcdFx0XHQkaHRtbFxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduby1jc3N0cmFuc2Zvcm1zM2QnKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCdjc3N0cmFuc2Zvcm1zM2QnKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxufSkoKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhlbHBlcnMuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJzY3JpcHRzLmpzIiwic291cmNlc0NvbnRlbnQiOlsiJ3VzZSBzdHJpY3QnO1xyXG5cclxud2luZG93LmhlbHBlcnMgPSAoZnVuY3Rpb24oKSB7XHJcblxyXG5cdGluaXQoKTtcclxuXHJcblx0LyoqKlxyXG5cdCAqIGZ1bmN0aW9uIGluaXQoKVxyXG5cdCAqXHJcblx0ICogSW5pdGlhbGl6ZSBwdWJsaWMgd2luZG93LmhlbHBlcnMgZnVuY3Rpb25zXHJcblx0ICovXHJcblx0ZnVuY3Rpb24gaW5pdCgpIHtcclxuXHRcdGZpeEJyb3dzZXJzKCk7XHJcblx0fVxyXG5cclxuXHQvKioqXHJcblx0ICogZnVuY3Rpb24gZml4QnJvd3NlcnMoKVxyXG5cdCAqXHJcblx0ICogRml4IGJyb3dzZXIgd2VpcmRuZXNzXHJcblx0ICogQ29ycmVjdCBNb2Rlcm5penIgYnVnc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGZpeEJyb3dzZXJzKCkge1xyXG5cdFx0dmFyIHVhID0gbmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpLFxyXG5cdFx0XHRjaHJvbWUgPSB1YS5sYXN0SW5kZXhPZignY2hyb21lLycpID4gMCxcclxuXHRcdFx0JGh0bWwgPSAkKCdodG1sJyk7XHJcblx0XHRcclxuXHRcdC8vIE1vZGVybml6ciAyIGJ1ZzogQ2hyb21lIG9uIFdpbmRvd3MgOCBnaXZlcyBhIGZhbHNlIG5lZ2F0aXZlIGZvciB0cmFuc2Zvcm1zM2Qgc3VwcG9ydFxyXG5cdFx0Ly8gR29vZ2xlIGRvZXMgbm90IHBsYW4gdG8gZml4IHRoaXM7IGh0dHBzOi8vY29kZS5nb29nbGUuY29tL3AvY2hyb21pdW0vaXNzdWVzL2RldGFpbD9pZD0xMjkwMDRcclxuXHRcdGlmIChjaHJvbWUpIHtcclxuXHRcdFx0dmFyIGNocm9tZXZlcnNpb24gPSB1YS5zdWJzdHIodWEubGFzdEluZGV4T2YoJ2Nocm9tZS8nKSArIDcsIDIpO1xyXG5cdFx0XHRpZiAoY2hyb21ldmVyc2lvbiA+PSAxMiAmJiAkaHRtbC5oYXNDbGFzcygnbm8tY3NzdHJhbnNmb3JtczNkJykpIHtcclxuXHRcdFx0XHQkaHRtbFxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduby1jc3N0cmFuc2Zvcm1zM2QnKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCdjc3N0cmFuc2Zvcm1zM2QnKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxufSkoKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= diff --git a/src/assets/js/vendor/angular-mock.js b/src/assets/js/vendor/angular-mock.js new file mode 100644 index 0000000..0df30cb --- /dev/null +++ b/src/assets/js/vendor/angular-mock.js @@ -0,0 +1,2470 @@ +/** + * @license AngularJS v1.4.7 + * (c) 2010-2015 Google, Inc. http://angularjs.org + * License: MIT + */ +(function (window, angular, undefined) { + + 'use strict'; + + /** + * @ngdoc object + * @name angular.mock + * @description + * + * Namespace from 'angular-mocks.js' which contains testing related code. + */ + angular.mock = {}; + + /** + * ! This is a private undocumented service ! + * + * @name $browser + * + * @description + * This service is a mock implementation of {@link ng.$browser}. It provides fake + * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, + * cookies, etc... + * + * The api of this service is the same as that of the real {@link ng.$browser $browser}, except + * that there are several helper methods available which can be used in tests. + */ + angular.mock.$BrowserProvider = function () { + this.$get = function () { + return new angular.mock.$Browser(); + }; + }; + + angular.mock.$Browser = function () { + var self = this; + + this.isMock = true; + self.$$url = "http://server/"; + self.$$lastUrl = self.$$url; // used by url polling fn + self.pollFns = []; + + // TODO(vojta): remove this temporary api + self.$$completeOutstandingRequest = angular.noop; + self.$$incOutstandingRequestCount = angular.noop; + + + // register url polling fn + + self.onUrlChange = function (listener) { + self.pollFns.push( + function () { + if (self.$$lastUrl !== self.$$url || self.$$state !== self.$$lastState) { + self.$$lastUrl = self.$$url; + self.$$lastState = self.$$state; + listener(self.$$url, self.$$state); + } + } + ); + + return listener; + }; + + self.$$applicationDestroyed = angular.noop; + self.$$checkUrlChange = angular.noop; + + self.deferredFns = []; + self.deferredNextId = 0; + + self.defer = function (fn, delay) { + delay = delay || 0; + self.deferredFns.push({ time: (self.defer.now + delay), fn: fn, id: self.deferredNextId }); + self.deferredFns.sort(function (a, b) { return a.time - b.time; }); + return self.deferredNextId++; + }; + + + /** + * @name $browser#defer.now + * + * @description + * Current milliseconds mock time. + */ + self.defer.now = 0; + + + self.defer.cancel = function (deferId) { + var fnIndex; + + angular.forEach(self.deferredFns, function (fn, index) { + if (fn.id === deferId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + self.deferredFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + + /** + * @name $browser#defer.flush + * + * @description + * Flushes all pending requests and executes the defer callbacks. + * + * @param {number=} number of milliseconds to flush. See {@link #defer.now} + */ + self.defer.flush = function (delay) { + if (angular.isDefined(delay)) { + self.defer.now += delay; + } else { + if (self.deferredFns.length) { + self.defer.now = self.deferredFns[self.deferredFns.length - 1].time; + } else { + throw new Error('No deferred tasks to be flushed'); + } + } + + while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { + self.deferredFns.shift().fn(); + } + }; + + self.$$baseHref = '/'; + self.baseHref = function () { + return this.$$baseHref; + }; + }; + angular.mock.$Browser.prototype = { + + /** + * @name $browser#poll + * + * @description + * run all fns in pollFns + */ + poll: function poll() { + angular.forEach(this.pollFns, function (pollFn) { + pollFn(); + }); + }, + + url: function (url, replace, state) { + if (angular.isUndefined(state)) { + state = null; + } + if (url) { + this.$$url = url; + // Native pushState serializes & copies the object; simulate it. + this.$$state = angular.copy(state); + return this; + } + + return this.$$url; + }, + + state: function () { + return this.$$state; + }, + + notifyWhenNoOutstandingRequests: function (fn) { + fn(); + } + }; + + + /** + * @ngdoc provider + * @name $exceptionHandlerProvider + * + * @description + * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors + * passed to the `$exceptionHandler`. + */ + + /** + * @ngdoc service + * @name $exceptionHandler + * + * @description + * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed + * to it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration + * information. + * + * + * ```js + * describe('$exceptionHandlerProvider', function() { + * + * it('should capture log messages and exceptions', function() { + * + * module(function($exceptionHandlerProvider) { + * $exceptionHandlerProvider.mode('log'); + * }); + * + * inject(function($log, $exceptionHandler, $timeout) { + * $timeout(function() { $log.log(1); }); + * $timeout(function() { $log.log(2); throw 'banana peel'; }); + * $timeout(function() { $log.log(3); }); + * expect($exceptionHandler.errors).toEqual([]); + * expect($log.assertEmpty()); + * $timeout.flush(); + * expect($exceptionHandler.errors).toEqual(['banana peel']); + * expect($log.log.logs).toEqual([[1], [2], [3]]); + * }); + * }); + * }); + * ``` + */ + + angular.mock.$ExceptionHandlerProvider = function () { + var handler; + + /** + * @ngdoc method + * @name $exceptionHandlerProvider#mode + * + * @description + * Sets the logging mode. + * + * @param {string} mode Mode of operation, defaults to `rethrow`. + * + * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` + * mode stores an array of errors in `$exceptionHandler.errors`, to allow later + * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and + * {@link ngMock.$log#reset reset()} + * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there + * is a bug in the application or test, so this mock will make these tests fail. + * For any implementations that expect exceptions to be thrown, the `rethrow` mode + * will also maintain a log of thrown errors. + */ + this.mode = function (mode) { + + switch (mode) { + case 'log': + case 'rethrow': + var errors = []; + handler = function (e) { + if (arguments.length == 1) { + errors.push(e); + } else { + errors.push([].slice.call(arguments, 0)); + } + if (mode === "rethrow") { + throw e; + } + }; + handler.errors = errors; + break; + default: + throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); + } + }; + + this.$get = function () { + return handler; + }; + + this.mode('rethrow'); + }; + + + /** + * @ngdoc service + * @name $log + * + * @description + * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays + * (one array per logging level). These arrays are exposed as `logs` property of each of the + * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. + * + */ + angular.mock.$LogProvider = function () { + var debug = true; + + function concat(array1, array2, index) { + return array1.concat(Array.prototype.slice.call(array2, index)); + } + + this.debugEnabled = function (flag) { + if (angular.isDefined(flag)) { + debug = flag; + return this; + } else { + return debug; + } + }; + + this.$get = function () { + var $log = { + log: function () { $log.log.logs.push(concat([], arguments, 0)); }, + warn: function () { $log.warn.logs.push(concat([], arguments, 0)); }, + info: function () { $log.info.logs.push(concat([], arguments, 0)); }, + error: function () { $log.error.logs.push(concat([], arguments, 0)); }, + debug: function () { + if (debug) { + $log.debug.logs.push(concat([], arguments, 0)); + } + } + }; + + /** + * @ngdoc method + * @name $log#reset + * + * @description + * Reset all of the logging arrays to empty. + */ + $log.reset = function () { + /** + * @ngdoc property + * @name $log#log.logs + * + * @description + * Array of messages logged using {@link ng.$log#log `log()`}. + * + * @example + * ```js + * $log.log('Some Log'); + * var first = $log.log.logs.unshift(); + * ``` + */ + $log.log.logs = []; + /** + * @ngdoc property + * @name $log#info.logs + * + * @description + * Array of messages logged using {@link ng.$log#info `info()`}. + * + * @example + * ```js + * $log.info('Some Info'); + * var first = $log.info.logs.unshift(); + * ``` + */ + $log.info.logs = []; + /** + * @ngdoc property + * @name $log#warn.logs + * + * @description + * Array of messages logged using {@link ng.$log#warn `warn()`}. + * + * @example + * ```js + * $log.warn('Some Warning'); + * var first = $log.warn.logs.unshift(); + * ``` + */ + $log.warn.logs = []; + /** + * @ngdoc property + * @name $log#error.logs + * + * @description + * Array of messages logged using {@link ng.$log#error `error()`}. + * + * @example + * ```js + * $log.error('Some Error'); + * var first = $log.error.logs.unshift(); + * ``` + */ + $log.error.logs = []; + /** + * @ngdoc property + * @name $log#debug.logs + * + * @description + * Array of messages logged using {@link ng.$log#debug `debug()`}. + * + * @example + * ```js + * $log.debug('Some Error'); + * var first = $log.debug.logs.unshift(); + * ``` + */ + $log.debug.logs = []; + }; + + /** + * @ngdoc method + * @name $log#assertEmpty + * + * @description + * Assert that all of the logging methods have no logged messages. If any messages are present, + * an exception is thrown. + */ + $log.assertEmpty = function () { + var errors = []; + angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function (logLevel) { + angular.forEach($log[logLevel].logs, function (log) { + angular.forEach(log, function (logItem) { + errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + + (logItem.stack || '')); + }); + }); + }); + if (errors.length) { + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " + + "an expected log message was not checked and removed:"); + errors.push(''); + throw new Error(errors.join('\n---------\n')); + } + }; + + $log.reset(); + return $log; + }; + }; + + + /** + * @ngdoc service + * @name $interval + * + * @description + * Mock implementation of the $interval service. + * + * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to + * move forward by `millis` milliseconds and trigger any functions scheduled to run in that + * time. + * + * @param {function()} fn A function that should be called repeatedly. + * @param {number} delay Number of milliseconds between each function call. + * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat + * indefinitely. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @param {...*=} Pass additional parameters to the executed function. + * @returns {promise} A promise which will be notified on each iteration. + */ + angular.mock.$IntervalProvider = function () { + this.$get = ['$browser', '$rootScope', '$q', '$$q', + function ($browser, $rootScope, $q, $$q) { + var repeatFns = [], + nextRepeatId = 0, + now = 0; + + var $interval = function (fn, delay, count, invokeApply) { + var hasParams = arguments.length > 4, + args = hasParams ? Array.prototype.slice.call(arguments, 4) : [], + iteration = 0, + skipApply = (angular.isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), + promise = deferred.promise; + + count = (angular.isDefined(count)) ? count : 0; + promise.then(null, null, (!hasParams) ? fn : function () { + fn.apply(null, args); + }); + + promise.$$intervalId = nextRepeatId; + + function tick() { + deferred.notify(iteration++); + + if (count > 0 && iteration >= count) { + var fnIndex; + deferred.resolve(iteration); + + angular.forEach(repeatFns, function (fn, index) { + if (fn.id === promise.$$intervalId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + repeatFns.splice(fnIndex, 1); + } + } + + if (skipApply) { + $browser.defer.flush(); + } else { + $rootScope.$apply(); + } + } + + repeatFns.push({ + nextTime: (now + delay), + delay: delay, + fn: tick, + id: nextRepeatId, + deferred: deferred + }); + repeatFns.sort(function (a, b) { return a.nextTime - b.nextTime; }); + + nextRepeatId++; + return promise; + }; + /** + * @ngdoc method + * @name $interval#cancel + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {promise} promise A promise from calling the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully cancelled. + */ + $interval.cancel = function (promise) { + if (!promise) return false; + var fnIndex; + + angular.forEach(repeatFns, function (fn, index) { + if (fn.id === promise.$$intervalId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + repeatFns[fnIndex].deferred.reject('canceled'); + repeatFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + /** + * @ngdoc method + * @name $interval#flush + * @description + * + * Runs interval tasks scheduled to be run in the next `millis` milliseconds. + * + * @param {number=} millis maximum timeout amount to flush up until. + * + * @return {number} The amount of time moved forward. + */ + $interval.flush = function (millis) { + now += millis; + while (repeatFns.length && repeatFns[0].nextTime <= now) { + var task = repeatFns[0]; + task.fn(); + task.nextTime += task.delay; + repeatFns.sort(function (a, b) { return a.nextTime - b.nextTime; }); + } + return millis; + }; + + return $interval; + }]; + }; + + + /* jshint -W101 */ + /* The R_ISO8061_STR regex is never going to fit into the 100 char limit! + * This directive should go inside the anonymous function but a bug in JSHint means that it would + * not be enacted early enough to prevent the warning. + */ + var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; + + function jsonStringToDate(string) { + var match; + if (match = string.match(R_ISO8061_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0; + if (match[9]) { + tzHour = toInt(match[9] + match[10]); + tzMin = toInt(match[9] + match[11]); + } + date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); + date.setUTCHours(toInt(match[4] || 0) - tzHour, + toInt(match[5] || 0) - tzMin, + toInt(match[6] || 0), + toInt(match[7] || 0)); + return date; + } + return string; + } + + function toInt(str) { + return parseInt(str, 10); + } + + function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while (num.length < digits) num = '0' + num; + if (trim) { + num = num.substr(num.length - digits); + } + return neg + num; + } + + + /** + * @ngdoc type + * @name angular.mock.TzDate + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. + * + * Mock of the Date type which has its timezone specified via constructor arg. + * + * The main purpose is to create Date-like instances with timezone fixed to the specified timezone + * offset, so that we can test code that depends on local timezone settings without dependency on + * the time zone settings of the machine where the code is running. + * + * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) + * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* + * + * @example + * !!!! WARNING !!!!! + * This is not a complete Date object so only methods that were implemented can be called safely. + * To make matters worse, TzDate instances inherit stuff from Date via a prototype. + * + * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is + * incomplete we might be missing some non-standard methods. This can result in errors like: + * "Date.prototype.foo called on incompatible Object". + * + * ```js + * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); + * newYearInBratislava.getTimezoneOffset() => -60; + * newYearInBratislava.getFullYear() => 2010; + * newYearInBratislava.getMonth() => 0; + * newYearInBratislava.getDate() => 1; + * newYearInBratislava.getHours() => 0; + * newYearInBratislava.getMinutes() => 0; + * newYearInBratislava.getSeconds() => 0; + * ``` + * + */ + angular.mock.TzDate = function (offset, timestamp) { + var self = new Date(0); + if (angular.isString(timestamp)) { + var tsStr = timestamp; + + self.origDate = jsonStringToDate(timestamp); + + timestamp = self.origDate.getTime(); + if (isNaN(timestamp)) { + throw { + name: "Illegal Argument", + message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" + }; + } + } else { + self.origDate = new Date(timestamp); + } + + var localOffset = new Date(timestamp).getTimezoneOffset(); + self.offsetDiff = localOffset * 60 * 1000 - offset * 1000 * 60 * 60; + self.date = new Date(timestamp + self.offsetDiff); + + self.getTime = function () { + return self.date.getTime() - self.offsetDiff; + }; + + self.toLocaleDateString = function () { + return self.date.toLocaleDateString(); + }; + + self.getFullYear = function () { + return self.date.getFullYear(); + }; + + self.getMonth = function () { + return self.date.getMonth(); + }; + + self.getDate = function () { + return self.date.getDate(); + }; + + self.getHours = function () { + return self.date.getHours(); + }; + + self.getMinutes = function () { + return self.date.getMinutes(); + }; + + self.getSeconds = function () { + return self.date.getSeconds(); + }; + + self.getMilliseconds = function () { + return self.date.getMilliseconds(); + }; + + self.getTimezoneOffset = function () { + return offset * 60; + }; + + self.getUTCFullYear = function () { + return self.origDate.getUTCFullYear(); + }; + + self.getUTCMonth = function () { + return self.origDate.getUTCMonth(); + }; + + self.getUTCDate = function () { + return self.origDate.getUTCDate(); + }; + + self.getUTCHours = function () { + return self.origDate.getUTCHours(); + }; + + self.getUTCMinutes = function () { + return self.origDate.getUTCMinutes(); + }; + + self.getUTCSeconds = function () { + return self.origDate.getUTCSeconds(); + }; + + self.getUTCMilliseconds = function () { + return self.origDate.getUTCMilliseconds(); + }; + + self.getDay = function () { + return self.date.getDay(); + }; + + // provide this method only on browsers that already have it + if (self.toISOString) { + self.toISOString = function () { + return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + + padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + + padNumber(self.origDate.getUTCDate(), 2) + 'T' + + padNumber(self.origDate.getUTCHours(), 2) + ':' + + padNumber(self.origDate.getUTCMinutes(), 2) + ':' + + padNumber(self.origDate.getUTCSeconds(), 2) + '.' + + padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'; + }; + } + + //hide all methods not implemented in this mock that the Date prototype exposes + var unimplementedMethods = ['getUTCDay', + 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', + 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', + 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', + 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', + 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; + + angular.forEach(unimplementedMethods, function (methodName) { + self[methodName] = function () { + throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock"); + }; + }); + + return self; + }; + + //make "tzDateInstance instanceof Date" return true + angular.mock.TzDate.prototype = Date.prototype; + /* jshint +W101 */ + + angular.mock.animate = angular.module('ngAnimateMock', ['ng']) + + .config(['$provide', function ($provide) { + + $provide.factory('$$forceReflow', function () { + function reflowFn() { + reflowFn.totalReflows++; + } + reflowFn.totalReflows = 0; + return reflowFn; + }); + + $provide.factory('$$animateAsyncRun', function () { + var queue = []; + var queueFn = function () { + return function (fn) { + queue.push(fn); + }; + }; + queueFn.flush = function () { + if (queue.length === 0) return false; + + for (var i = 0; i < queue.length; i++) { + queue[i](); + } + queue = []; + + return true; + }; + return queueFn; + }); + + $provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', + '$$forceReflow', '$$animateAsyncRun', '$rootScope', + function ($delegate, $timeout, $browser, $$rAF, + $$forceReflow, $$animateAsyncRun, $rootScope) { + var animate = { + queue: [], + cancel: $delegate.cancel, + on: $delegate.on, + off: $delegate.off, + pin: $delegate.pin, + get reflows() { + return $$forceReflow.totalReflows; + }, + enabled: $delegate.enabled, + flush: function () { + $rootScope.$digest(); + + var doNextRun, somethingFlushed = false; + do { + doNextRun = false; + + if ($$rAF.queue.length) { + $$rAF.flush(); + doNextRun = somethingFlushed = true; + } + + if ($$animateAsyncRun.flush()) { + doNextRun = somethingFlushed = true; + } + } while (doNextRun); + + if (!somethingFlushed) { + throw new Error('No pending animations ready to be closed or flushed'); + } + + $rootScope.$digest(); + } + }; + + angular.forEach( + ['animate', 'enter', 'leave', 'move', 'addClass', 'removeClass', 'setClass'], function (method) { + animate[method] = function () { + animate.queue.push({ + event: method, + element: arguments[0], + options: arguments[arguments.length - 1], + args: arguments + }); + return $delegate[method].apply($delegate, arguments); + }; + }); + + return animate; + }]); + + }]); + + + /** + * @ngdoc function + * @name angular.mock.dump + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available function. + * + * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for + * debugging. + * + * This method is also available on window, where it can be used to display objects on debug + * console. + * + * @param {*} object - any object to turn into string. + * @return {string} a serialized string of the argument + */ + angular.mock.dump = function (object) { + return serialize(object); + + function serialize(object) { + var out; + + if (angular.isElement(object)) { + object = angular.element(object); + out = angular.element('
'); + angular.forEach(object, function (element) { + out.append(angular.element(element).clone()); + }); + out = out.html(); + } else if (angular.isArray(object)) { + out = []; + angular.forEach(object, function (o) { + out.push(serialize(o)); + }); + out = '[ ' + out.join(', ') + ' ]'; + } else if (angular.isObject(object)) { + if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { + out = serializeScope(object); + } else if (object instanceof Error) { + out = object.stack || ('' + object.name + ': ' + object.message); + } else { + // TODO(i): this prevents methods being logged, + // we should have a better way to serialize objects + out = angular.toJson(object, true); + } + } else { + out = String(object); + } + + return out; + } + + function serializeScope(scope, offset) { + offset = offset || ' '; + var log = [offset + 'Scope(' + scope.$id + '): {']; + for (var key in scope) { + if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) { + log.push(' ' + key + ': ' + angular.toJson(scope[key])); + } + } + var child = scope.$$childHead; + while (child) { + log.push(serializeScope(child, offset + ' ')); + child = child.$$nextSibling; + } + log.push('}'); + return log.join('\n' + offset); + } + }; + + /** + * @ngdoc service + * @name $httpBackend + * @description + * Fake HTTP backend implementation suitable for unit testing applications that use the + * {@link ng.$http $http service}. + * + * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less + * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. + * + * During unit testing, we want our unit tests to run quickly and have no external dependencies so + * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or + * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is + * to verify whether a certain request has been sent or not, or alternatively just let the + * application make requests, respond with pre-trained responses and assert that the end result is + * what we expect it to be. + * + * This mock implementation can be used to respond with static or dynamic responses via the + * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). + * + * When an Angular application needs some data from a server, it calls the $http service, which + * sends the request to a real server using $httpBackend service. With dependency injection, it is + * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify + * the requests and respond with some testing data without sending a request to a real server. + * + * There are two ways to specify what test data should be returned as http responses by the mock + * backend when the code under test makes http requests: + * + * - `$httpBackend.expect` - specifies a request expectation + * - `$httpBackend.when` - specifies a backend definition + * + * + * # Request Expectations vs Backend Definitions + * + * Request expectations provide a way to make assertions about requests made by the application and + * to define responses for those requests. The test will fail if the expected requests are not made + * or they are made in the wrong order. + * + * Backend definitions allow you to define a fake backend for your application which doesn't assert + * if a particular request was made or not, it just returns a trained response if a request is made. + * The test will pass whether or not the request gets made during testing. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
+ * + * In cases where both backend definitions and request expectations are specified during unit + * testing, the request expectations are evaluated first. + * + * If a request expectation has no response specified, the algorithm will search your backend + * definitions for an appropriate response. + * + * If a request didn't match any expectation or if the expectation doesn't have the response + * defined, the backend definitions are evaluated in sequential order to see if any of them match + * the request. The response from the first matched definition is returned. + * + * + * # Flushing HTTP requests + * + * The $httpBackend used in production always responds to requests asynchronously. If we preserved + * this behavior in unit testing, we'd have to create async unit tests, which are hard to write, + * to follow and to maintain. But neither can the testing mock respond synchronously; that would + * change the execution of the code under test. For this reason, the mock $httpBackend has a + * `flush()` method, which allows the test to explicitly flush pending requests. This preserves + * the async api of the backend, while allowing the test to execute synchronously. + * + * + * # Unit testing with mock $httpBackend + * The following code shows how to setup and use the mock backend when unit testing a controller. + * First we create the controller under test: + * + ```js + // The module code + angular + .module('MyApp', []) + .controller('MyController', MyController); + + // The controller code + function MyController($scope, $http) { + var authToken; + + $http.get('/auth.py').success(function(data, status, headers) { + authToken = headers('A-Token'); + $scope.user = data; + }); + + $scope.saveMessage = function(message) { + var headers = { 'Authorization': authToken }; + $scope.status = 'Saving...'; + + $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) { + $scope.status = ''; + }).error(function() { + $scope.status = 'Failed...'; + }); + }; + } + ``` + * + * Now we setup the mock backend and create the test specs: + * + ```js + // testing controller + describe('MyController', function() { + var $httpBackend, $rootScope, createController, authRequestHandler; + + // Set up the module + beforeEach(module('MyApp')); + + beforeEach(inject(function($injector) { + // Set up the mock http service responses + $httpBackend = $injector.get('$httpBackend'); + // backend definition common for all tests + authRequestHandler = $httpBackend.when('GET', '/auth.py') + .respond({userId: 'userX'}, {'A-Token': 'xxx'}); + + // Get hold of a scope (i.e. the root scope) + $rootScope = $injector.get('$rootScope'); + // The $controller service is used to create instances of controllers + var $controller = $injector.get('$controller'); + + createController = function() { + return $controller('MyController', {'$scope' : $rootScope }); + }; + })); + + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + + it('should fetch authentication token', function() { + $httpBackend.expectGET('/auth.py'); + var controller = createController(); + $httpBackend.flush(); + }); + + + it('should fail authentication', function() { + + // Notice how you can change the response even after it was set + authRequestHandler.respond(401, ''); + + $httpBackend.expectGET('/auth.py'); + var controller = createController(); + $httpBackend.flush(); + expect($rootScope.status).toBe('Failed...'); + }); + + + it('should send msg to server', function() { + var controller = createController(); + $httpBackend.flush(); + + // now you don’t care about the authentication, but + // the controller will still send the request and + // $httpBackend will respond without you having to + // specify the expectation and response for this request + + $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); + $rootScope.saveMessage('message content'); + expect($rootScope.status).toBe('Saving...'); + $httpBackend.flush(); + expect($rootScope.status).toBe(''); + }); + + + it('should send auth header', function() { + var controller = createController(); + $httpBackend.flush(); + + $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { + // check if the header was sent, if it wasn't the expectation won't + // match the request and the test will fail + return headers['Authorization'] == 'xxx'; + }).respond(201, ''); + + $rootScope.saveMessage('whatever'); + $httpBackend.flush(); + }); + }); + ``` + */ + angular.mock.$HttpBackendProvider = function () { + this.$get = ['$rootScope', '$timeout', createHttpBackendMock]; + }; + + /** + * General factory function for $httpBackend mock. + * Returns instance for unit testing (when no arguments specified): + * - passing through is disabled + * - auto flushing is disabled + * + * Returns instance for e2e testing (when `$delegate` and `$browser` specified): + * - passing through (delegating request to real backend) is enabled + * - auto flushing is enabled + * + * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) + * @param {Object=} $browser Auto-flushing enabled if specified + * @return {Object} Instance of $httpBackend mock + */ + function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { + var definitions = [], + expectations = [], + responses = [], + responsesPush = angular.bind(responses, responses.push), + copy = angular.copy; + + function createResponse(status, data, headers, statusText) { + if (angular.isFunction(status)) return status; + + return function () { + return angular.isNumber(status) + ? [status, data, headers, statusText] + : [200, status, data, headers]; + }; + } + + // TODO(vojta): change params to: method, url, data, headers, callback + function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) { + var xhr = new MockXhr(), + expectation = expectations[0], + wasExpected = false; + + function prettyPrint(data) { + return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) + ? data + : angular.toJson(data); + } + + function wrapResponse(wrapped) { + if (!$browser && timeout) { + timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout); + } + + return handleResponse; + + function handleResponse() { + var response = wrapped.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(), + copy(response[3] || '')); + } + + function handleTimeout() { + for (var i = 0, ii = responses.length; i < ii; i++) { + if (responses[i] === handleResponse) { + responses.splice(i, 1); + callback(-1, undefined, ''); + break; + } + } + } + } + + if (expectation && expectation.match(method, url)) { + if (!expectation.matchData(data)) { + throw new Error('Expected ' + expectation + ' with different data\n' + + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); + } + + if (!expectation.matchHeaders(headers)) { + throw new Error('Expected ' + expectation + ' with different headers\n' + + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + + prettyPrint(headers)); + } + + expectations.shift(); + + if (expectation.response) { + responses.push(wrapResponse(expectation)); + return; + } + wasExpected = true; + } + + var i = -1, definition; + while ((definition = definitions[++i])) { + if (definition.match(method, url, data, headers || {})) { + if (definition.response) { + // if $browser specified, we do auto flush all requests + ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); + } else if (definition.passThrough) { + $delegate(method, url, data, callback, headers, timeout, withCredentials); + } else throw new Error('No response defined !'); + return; + } + } + throw wasExpected ? + new Error('No response defined !') : + new Error('Unexpected request: ' + method + ' ' + url + '\n' + + (expectation ? 'Expected ' + expectation : 'No more request expected')); + } + + /** + * @ngdoc method + * @name $httpBackend#when + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + * + * - respond – + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can + * return an array containing response status (number), response data (string), response + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. + */ + $httpBackend.when = function (method, url, data, headers) { + var definition = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function (status, data, headers, statusText) { + definition.passThrough = undefined; + definition.response = createResponse(status, data, headers, statusText); + return chain; + } + }; + + if ($browser) { + chain.passThrough = function () { + definition.response = undefined; + definition.passThrough = true; + return chain; + }; + } + + definitions.push(definition); + return chain; + }; + + /** + * @ngdoc method + * @name $httpBackend#whenGET + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenHEAD + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenDELETE + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPOST + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPUT + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenJSONP + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + createShortMethods('when'); + + + /** + * @ngdoc method + * @name $httpBackend#expect + * @description + * Creates a new request expectation. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current expectation. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + * + * - respond – + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can + * return an array containing response status (number), response data (string), response + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. + */ + $httpBackend.expect = function (method, url, data, headers) { + var expectation = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function (status, data, headers, statusText) { + expectation.response = createResponse(status, data, headers, statusText); + return chain; + } + }; + + expectations.push(expectation); + return chain; + }; + + + /** + * @ngdoc method + * @name $httpBackend#expectGET + * @description + * Creates a new request expectation for GET requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. See #expect for more info. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectHEAD + * @description + * Creates a new request expectation for HEAD requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectDELETE + * @description + * Creates a new request expectation for DELETE requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPOST + * @description + * Creates a new request expectation for POST requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPUT + * @description + * Creates a new request expectation for PUT requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPATCH + * @description + * Creates a new request expectation for PATCH requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectJSONP + * @description + * Creates a new request expectation for JSONP requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives an url + * and returns true if the url matches the current definition. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + createShortMethods('expect'); + + + /** + * @ngdoc method + * @name $httpBackend#flush + * @description + * Flushes all pending requests using the trained responses. + * + * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, + * all pending requests will be flushed. If there are no pending requests when the flush method + * is called an exception is thrown (as this typically a sign of programming error). + */ + $httpBackend.flush = function (count, digest) { + if (digest !== false) $rootScope.$digest(); + if (!responses.length) throw new Error('No pending request to flush !'); + + if (angular.isDefined(count) && count !== null) { + while (count--) { + if (!responses.length) throw new Error('No more pending request to flush !'); + responses.shift()(); + } + } else { + while (responses.length) { + responses.shift()(); + } + } + $httpBackend.verifyNoOutstandingExpectation(digest); + }; + + + /** + * @ngdoc method + * @name $httpBackend#verifyNoOutstandingExpectation + * @description + * Verifies that all of the requests defined via the `expect` api were made. If any of the + * requests were not made, verifyNoOutstandingExpectation throws an exception. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * ```js + * afterEach($httpBackend.verifyNoOutstandingExpectation); + * ``` + */ + $httpBackend.verifyNoOutstandingExpectation = function (digest) { + if (digest !== false) $rootScope.$digest(); + if (expectations.length) { + throw new Error('Unsatisfied requests: ' + expectations.join(', ')); + } + }; + + + /** + * @ngdoc method + * @name $httpBackend#verifyNoOutstandingRequest + * @description + * Verifies that there are no outstanding requests that need to be flushed. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * ```js + * afterEach($httpBackend.verifyNoOutstandingRequest); + * ``` + */ + $httpBackend.verifyNoOutstandingRequest = function () { + if (responses.length) { + throw new Error('Unflushed requests: ' + responses.length); + } + }; + + + /** + * @ngdoc method + * @name $httpBackend#resetExpectations + * @description + * Resets all request expectations, but preserves all backend definitions. Typically, you would + * call resetExpectations during a multiple-phase test when you want to reuse the same instance of + * $httpBackend mock. + */ + $httpBackend.resetExpectations = function () { + expectations.length = 0; + responses.length = 0; + }; + + return $httpBackend; + + + function createShortMethods(prefix) { + angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function (method) { + $httpBackend[prefix + method] = function (url, headers) { + return $httpBackend[prefix](method, url, undefined, headers); + }; + }); + + angular.forEach(['PUT', 'POST', 'PATCH'], function (method) { + $httpBackend[prefix + method] = function (url, data, headers) { + return $httpBackend[prefix](method, url, data, headers); + }; + }); + } + } + + function MockHttpExpectation(method, url, data, headers) { + + this.data = data; + this.headers = headers; + + this.match = function (m, u, d, h) { + if (method != m) return false; + if (!this.matchUrl(u)) return false; + if (angular.isDefined(d) && !this.matchData(d)) return false; + if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + return true; + }; + + this.matchUrl = function (u) { + if (!url) return true; + if (angular.isFunction(url.test)) return url.test(u); + if (angular.isFunction(url)) return url(u); + return url == u; + }; + + this.matchHeaders = function (h) { + if (angular.isUndefined(headers)) return true; + if (angular.isFunction(headers)) return headers(h); + return angular.equals(headers, h); + }; + + this.matchData = function (d) { + if (angular.isUndefined(data)) return true; + if (data && angular.isFunction(data.test)) return data.test(d); + if (data && angular.isFunction(data)) return data(d); + if (data && !angular.isString(data)) { + return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d)); + } + return data == d; + }; + + this.toString = function () { + return method + ' ' + url; + }; + } + + function createMockXhr() { + return new MockXhr(); + } + + function MockXhr() { + + // hack for testing $http, $httpBackend + MockXhr.$$lastInstance = this; + + this.open = function (method, url, async) { + this.$$method = method; + this.$$url = url; + this.$$async = async; + this.$$reqHeaders = {}; + this.$$respHeaders = {}; + }; + + this.send = function (data) { + this.$$data = data; + }; + + this.setRequestHeader = function (key, value) { + this.$$reqHeaders[key] = value; + }; + + this.getResponseHeader = function (name) { + // the lookup must be case insensitive, + // that's why we try two quick lookups first and full scan last + var header = this.$$respHeaders[name]; + if (header) return header; + + name = angular.lowercase(name); + header = this.$$respHeaders[name]; + if (header) return header; + + header = undefined; + angular.forEach(this.$$respHeaders, function (headerVal, headerName) { + if (!header && angular.lowercase(headerName) == name) header = headerVal; + }); + return header; + }; + + this.getAllResponseHeaders = function () { + var lines = []; + + angular.forEach(this.$$respHeaders, function (value, key) { + lines.push(key + ': ' + value); + }); + return lines.join('\n'); + }; + + this.abort = angular.noop; + } + + + /** + * @ngdoc service + * @name $timeout + * @description + * + * This service is just a simple decorator for {@link ng.$timeout $timeout} service + * that adds a "flush" and "verifyNoPendingTasks" methods. + */ + + angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function ($delegate, $browser) { + + /** + * @ngdoc method + * @name $timeout#flush + * @description + * + * Flushes the queue of pending tasks. + * + * @param {number=} delay maximum timeout amount to flush up until + */ + $delegate.flush = function (delay) { + $browser.defer.flush(delay); + }; + + /** + * @ngdoc method + * @name $timeout#verifyNoPendingTasks + * @description + * + * Verifies that there are no pending tasks that need to be flushed. + */ + $delegate.verifyNoPendingTasks = function () { + if ($browser.deferredFns.length) { + throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + + formatPendingTasksAsString($browser.deferredFns)); + } + }; + + function formatPendingTasksAsString(tasks) { + var result = []; + angular.forEach(tasks, function (task) { + result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); + }); + + return result.join(', '); + } + + return $delegate; + }]; + + angular.mock.$RAFDecorator = ['$delegate', function ($delegate) { + var rafFn = function (fn) { + var index = rafFn.queue.length; + rafFn.queue.push(fn); + return function () { + rafFn.queue.splice(index, 1); + }; + }; + + rafFn.queue = []; + rafFn.supported = $delegate.supported; + + rafFn.flush = function () { + if (rafFn.queue.length === 0) { + throw new Error('No rAF callbacks present'); + } + + var length = rafFn.queue.length; + for (var i = 0; i < length; i++) { + rafFn.queue[i](); + } + + rafFn.queue = rafFn.queue.slice(i); + }; + + return rafFn; + }]; + + /** + * + */ + angular.mock.$RootElementProvider = function () { + this.$get = function () { + return angular.element('
'); + }; + }; + + /** + * @ngdoc service + * @name $controller + * @description + * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing + * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}. + * + * + * ## Example + * + * ```js + * + * // Directive definition ... + * + * myMod.directive('myDirective', { + * controller: 'MyDirectiveController', + * bindToController: { + * name: '@' + * } + * }); + * + * + * // Controller definition ... + * + * myMod.controller('MyDirectiveController', ['log', function($log) { + * $log.info(this.name); + * })]; + * + * + * // In a test ... + * + * describe('myDirectiveController', function() { + * it('should write the bound name to the log', inject(function($controller, $log) { + * var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' }); + * expect(ctrl.name).toEqual('Clark Kent'); + * expect($log.info.logs).toEqual(['Clark Kent']); + * }); + * }); + * + * ``` + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global + * `window` object (not recommended) + * + * The string can use the `controller as property` syntax, where the controller instance is published + * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this + * to work correctly. + * + * @param {Object} locals Injection locals for Controller. + * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used + * to simulate the `bindToController` feature and simplify certain kinds of tests. + * @return {Object} Instance of given controller. + */ + angular.mock.$ControllerDecorator = ['$delegate', function ($delegate) { + return function (expression, locals, later, ident) { + if (later && typeof later === 'object') { + var create = $delegate(expression, locals, true, ident); + angular.extend(create.instance, later); + return create(); + } + return $delegate(expression, locals, later, ident); + }; + }]; + + + /** + * @ngdoc module + * @name ngMock + * @packageName angular-mocks + * @description + * + * # ngMock + * + * The `ngMock` module provides support to inject and mock Angular services into unit tests. + * In addition, ngMock also extends various core ng services such that they can be + * inspected and controlled in a synchronous manner within test code. + * + * + *
+ * + */ + angular.module('ngMock', ['ng']).provider({ + $browser: angular.mock.$BrowserProvider, + $exceptionHandler: angular.mock.$ExceptionHandlerProvider, + $log: angular.mock.$LogProvider, + $interval: angular.mock.$IntervalProvider, + $httpBackend: angular.mock.$HttpBackendProvider, + $rootElement: angular.mock.$RootElementProvider + }).config(['$provide', function ($provide) { + $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); + $provide.decorator('$$rAF', angular.mock.$RAFDecorator); + $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); + $provide.decorator('$controller', angular.mock.$ControllerDecorator); + }]); + + /** + * @ngdoc module + * @name ngMockE2E + * @module ngMockE2E + * @packageName angular-mocks + * @description + * + * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. + * Currently there is only one mock present in this module - + * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. + */ + angular.module('ngMockE2E', ['ng']).config(['$provide', function ($provide) { + $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); + }]); + + /** + * @ngdoc service + * @name $httpBackend + * @module ngMockE2E + * @description + * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of + * applications that use the {@link ng.$http $http service}. + * + * *Note*: For fake http backend implementation suitable for unit testing please see + * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. + * + * This implementation can be used to respond with static or dynamic responses via the `when` api + * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the + * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch + * templates from a webserver). + * + * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application + * is being developed with the real backend api replaced with a mock, it is often desirable for + * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch + * templates or static files from the webserver). To configure the backend with this behavior + * use the `passThrough` request handler of `when` instead of `respond`. + * + * Additionally, we don't want to manually have to flush mocked out requests like we do during unit + * testing. For this reason the e2e $httpBackend flushes mocked out requests + * automatically, closely simulating the behavior of the XMLHttpRequest object. + * + * To setup the application to run with this http backend, you have to create a module that depends + * on the `ngMockE2E` and your application modules and defines the fake backend: + * + * ```js + * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); + * myAppDev.run(function($httpBackend) { + * phones = [{name: 'phone1'}, {name: 'phone2'}]; + * + * // returns the current list of phones + * $httpBackend.whenGET('/phones').respond(phones); + * + * // adds a new phone to the phones array + * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { + * var phone = angular.fromJson(data); + * phones.push(phone); + * return [200, phone, {}]; + * }); + * $httpBackend.whenGET(/^\/templates\//).passThrough(); + * //... + * }); + * ``` + * + * Afterwards, bootstrap your app with this new module. + */ + + /** + * @ngdoc method + * @name $httpBackend#when + * @module ngMockE2E + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + * + * - respond – + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string), response headers + * (Object), and the text for the status (string). + * - passThrough – `{function()}` – Any request matching a backend definition with + * `passThrough` handler will be passed through to the real backend (an XHR request will be made + * to the server.) + * - Both methods return the `requestHandler` object for possible overrides. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenGET + * @module ngMockE2E + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenHEAD + * @module ngMockE2E + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenDELETE + * @module ngMockE2E + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPOST + * @module ngMockE2E + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPUT + * @module ngMockE2E + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPATCH + * @module ngMockE2E + * @description + * Creates a new backend definition for PATCH requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenJSONP + * @module ngMockE2E + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + angular.mock.e2e = {}; + angular.mock.e2e.$httpBackendDecorator = + ['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock]; + + + /** + * @ngdoc type + * @name $rootScope.Scope + * @module ngMock + * @description + * {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These + * methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when + * `ngMock` module is loaded. + * + * In addition to all the regular `Scope` methods, the following helper methods are available: + */ + angular.mock.$RootScopeDecorator = ['$delegate', function ($delegate) { + + var $rootScopePrototype = Object.getPrototypeOf($delegate); + + $rootScopePrototype.$countChildScopes = countChildScopes; + $rootScopePrototype.$countWatchers = countWatchers; + + return $delegate; + + // ------------------------------------------------------------------------------------------ // + + /** + * @ngdoc method + * @name $rootScope.Scope#$countChildScopes + * @module ngMock + * @description + * Counts all the direct and indirect child scopes of the current scope. + * + * The current scope is excluded from the count. The count includes all isolate child scopes. + * + * @returns {number} Total number of child scopes. + */ + function countChildScopes() { + // jshint validthis: true + var count = 0; // exclude the current scope + var pendingChildHeads = [this.$$childHead]; + var currentScope; + + while (pendingChildHeads.length) { + currentScope = pendingChildHeads.shift(); + + while (currentScope) { + count += 1; + pendingChildHeads.push(currentScope.$$childHead); + currentScope = currentScope.$$nextSibling; + } + } + + return count; + } + + + /** + * @ngdoc method + * @name $rootScope.Scope#$countWatchers + * @module ngMock + * @description + * Counts all the watchers of direct and indirect child scopes of the current scope. + * + * The watchers of the current scope are included in the count and so are all the watchers of + * isolate child scopes. + * + * @returns {number} Total number of watchers. + */ + function countWatchers() { + // jshint validthis: true + var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope + var pendingChildHeads = [this.$$childHead]; + var currentScope; + + while (pendingChildHeads.length) { + currentScope = pendingChildHeads.shift(); + + while (currentScope) { + count += currentScope.$$watchers ? currentScope.$$watchers.length : 0; + pendingChildHeads.push(currentScope.$$childHead); + currentScope = currentScope.$$nextSibling; + } + } + + return count; + } + }]; + + + if (window.jasmine || window.mocha) { + + var currentSpec = null, + annotatedFunctions = [], + isSpecRunning = function () { + return !!currentSpec; + }; + + angular.mock.$$annotate = angular.injector.$$annotate; + angular.injector.$$annotate = function (fn) { + if (typeof fn === 'function' && !fn.$inject) { + annotatedFunctions.push(fn); + } + return angular.mock.$$annotate.apply(this, arguments); + }; + + + (window.beforeEach || window.setup)(function () { + annotatedFunctions = []; + currentSpec = this; + }); + + (window.afterEach || window.teardown)(function () { + var injector = currentSpec.$injector; + + annotatedFunctions.forEach(function (fn) { + delete fn.$inject; + }); + + angular.forEach(currentSpec.$modules, function (module) { + if (module && module.$$hashKey) { + module.$$hashKey = undefined; + } + }); + + currentSpec.$injector = null; + currentSpec.$modules = null; + currentSpec = null; + + if (injector) { + injector.get('$rootElement').off(); + } + + // clean up jquery's fragment cache + angular.forEach(angular.element.fragments, function (val, key) { + delete angular.element.fragments[key]; + }); + + MockXhr.$$lastInstance = null; + + angular.forEach(angular.callbacks, function (val, key) { + delete angular.callbacks[key]; + }); + angular.callbacks.counter = 0; + }); + + /** + * @ngdoc function + * @name angular.mock.module + * @description + * + * *NOTE*: This function is also published on window for easy access.
+ * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * This function registers a module configuration code. It collects the configuration information + * which will be used when the injector is created by {@link angular.mock.inject inject}. + * + * See {@link angular.mock.inject inject} for usage example + * + * @param {...(string|Function|Object)} fns any number of modules which are represented as string + * aliases or as anonymous module initialization functions. The modules are used to + * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an + * object literal is passed they will be registered as values in the module, the key being + * the module name and the value being what is returned. + */ + window.module = angular.mock.module = function () { + var moduleFns = Array.prototype.slice.call(arguments, 0); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + if (currentSpec.$injector) { + throw new Error('Injector already created, can not register a module!'); + } else { + var modules = currentSpec.$modules || (currentSpec.$modules = []); + angular.forEach(moduleFns, function (module) { + if (angular.isObject(module) && !angular.isArray(module)) { + modules.push(function ($provide) { + angular.forEach(module, function (value, key) { + $provide.value(key, value); + }); + }); + } else { + modules.push(module); + } + }); + } + } + }; + + /** + * @ngdoc function + * @name angular.mock.inject + * @description + * + * *NOTE*: This function is also published on window for easy access.
+ * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * The inject function wraps a function into an injectable function. The inject() creates new + * instance of {@link auto.$injector $injector} per test, which is then used for + * resolving references. + * + * + * ## Resolving References (Underscore Wrapping) + * Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this + * in multiple `it()` clauses. To be able to do this we must assign the reference to a variable + * that is declared in the scope of the `describe()` block. Since we would, most likely, want + * the variable to have the same name of the reference we have a problem, since the parameter + * to the `inject()` function would hide the outer variable. + * + * To help with this, the injected parameters can, optionally, be enclosed with underscores. + * These are ignored by the injector when the reference name is resolved. + * + * For example, the parameter `_myService_` would be resolved as the reference `myService`. + * Since it is available in the function body as _myService_, we can then assign it to a variable + * defined in an outer scope. + * + * ``` + * // Defined out reference variable outside + * var myService; + * + * // Wrap the parameter in underscores + * beforeEach( inject( function(_myService_){ + * myService = _myService_; + * })); + * + * // Use myService in a series of tests. + * it('makes use of myService', function() { + * myService.doStuff(); + * }); + * + * ``` + * + * See also {@link angular.mock.module angular.mock.module} + * + * ## Example + * Example of what a typical jasmine tests looks like with the inject method. + * ```js + * + * angular.module('myApplicationModule', []) + * .value('mode', 'app') + * .value('version', 'v1.0.1'); + * + * + * describe('MyApp', function() { + * + * // You need to load modules that you want to test, + * // it loads only the "ng" module by default. + * beforeEach(module('myApplicationModule')); + * + * + * // inject() is used to inject arguments of all given functions + * it('should provide a version', inject(function(mode, version) { + * expect(version).toEqual('v1.0.1'); + * expect(mode).toEqual('app'); + * })); + * + * + * // The inject and module method can also be used inside of the it or beforeEach + * it('should override a version and test the new version is injected', function() { + * // module() takes functions or strings (module aliases) + * module(function($provide) { + * $provide.value('version', 'overridden'); // override version here + * }); + * + * inject(function(version) { + * expect(version).toEqual('overridden'); + * }); + * }); + * }); + * + * ``` + * + * @param {...Function} fns any number of functions which will be injected using the injector. + */ + + + + var ErrorAddingDeclarationLocationStack = function (e, errorForStack) { + this.message = e.message; + this.name = e.name; + if (e.line) this.line = e.line; + if (e.sourceId) this.sourceId = e.sourceId; + if (e.stack && errorForStack) + this.stack = e.stack + '\n' + errorForStack.stack; + if (e.stackArray) this.stackArray = e.stackArray; + }; + ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString; + + window.inject = angular.mock.inject = function () { + var blockFns = Array.prototype.slice.call(arguments, 0); + var errorForStack = new Error('Declaration Location'); + return isSpecRunning() ? workFn.call(currentSpec) : workFn; + ///////////////////// + function workFn() { + var modules = currentSpec.$modules || []; + var strictDi = !!currentSpec.$injectorStrict; + modules.unshift('ngMock'); + modules.unshift('ng'); + var injector = currentSpec.$injector; + if (!injector) { + if (strictDi) { + // If strictDi is enabled, annotate the providerInjector blocks + angular.forEach(modules, function (moduleFn) { + if (typeof moduleFn === "function") { + angular.injector.$$annotate(moduleFn); + } + }); + } + injector = currentSpec.$injector = angular.injector(modules, strictDi); + currentSpec.$injectorStrict = strictDi; + } + for (var i = 0, ii = blockFns.length; i < ii; i++) { + if (currentSpec.$injectorStrict) { + // If the injector is strict / strictDi, and the spec wants to inject using automatic + // annotation, then annotate the function here. + injector.annotate(blockFns[i]); + } + try { + /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */ + injector.invoke(blockFns[i] || angular.noop, this); + /* jshint +W040 */ + } catch (e) { + if (e.stack && errorForStack) { + throw new ErrorAddingDeclarationLocationStack(e, errorForStack); + } + throw e; + } finally { + errorForStack = null; + } + } + } + }; + + + angular.mock.inject.strictDi = function (value) { + value = arguments.length ? !!value : true; + return isSpecRunning() ? workFn() : workFn; + + function workFn() { + if (value !== currentSpec.$injectorStrict) { + if (currentSpec.$injector) { + throw new Error('Injector already created, can not modify strict annotations'); + } else { + currentSpec.$injectorStrict = value; + } + } + } + }; + } + + +})(window, window.angular); \ No newline at end of file diff --git a/src/assets/js/vendor/jasmine.js b/src/assets/js/vendor/jasmine.js new file mode 100644 index 0000000..312d591 --- /dev/null +++ b/src/assets/js/vendor/jasmine.js @@ -0,0 +1,3298 @@ +/* +Copyright (c) 2008-2015 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +var getJasmineRequireObj = (function (jasmineGlobal) { + var jasmineRequire; + + if (typeof module !== 'undefined' && module.exports) { + jasmineGlobal = global; + jasmineRequire = exports; + } else { + if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') { + jasmineGlobal = window; + } + jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; + } + + function getJasmineRequire() { + return jasmineRequire; + } + + getJasmineRequire().core = function(jRequire) { + var j$ = {}; + + jRequire.base(j$, jasmineGlobal); + j$.util = jRequire.util(); + j$.errors = jRequire.errors(); + j$.Any = jRequire.Any(j$); + j$.Anything = jRequire.Anything(j$); + j$.CallTracker = jRequire.CallTracker(); + j$.MockDate = jRequire.MockDate(); + j$.Clock = jRequire.Clock(); + j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); + j$.Env = jRequire.Env(j$); + j$.ExceptionFormatter = jRequire.ExceptionFormatter(); + j$.Expectation = jRequire.Expectation(); + j$.buildExpectationResult = jRequire.buildExpectationResult(); + j$.JsApiReporter = jRequire.JsApiReporter(); + j$.matchersUtil = jRequire.matchersUtil(j$); + j$.ObjectContaining = jRequire.ObjectContaining(j$); + j$.ArrayContaining = jRequire.ArrayContaining(j$); + j$.pp = jRequire.pp(j$); + j$.QueueRunner = jRequire.QueueRunner(j$); + j$.ReportDispatcher = jRequire.ReportDispatcher(); + j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); + j$.SpyStrategy = jRequire.SpyStrategy(); + j$.StringMatching = jRequire.StringMatching(j$); + j$.Suite = jRequire.Suite(j$); + j$.Timer = jRequire.Timer(); + j$.TreeProcessor = jRequire.TreeProcessor(); + j$.version = jRequire.version(); + + j$.matchers = jRequire.requireMatchers(jRequire, j$); + + return j$; + }; + + return getJasmineRequire; +})(this); + +getJasmineRequireObj().requireMatchers = function(jRequire, j$) { + var availableMatchers = [ + 'toBe', + 'toBeCloseTo', + 'toBeDefined', + 'toBeFalsy', + 'toBeGreaterThan', + 'toBeLessThan', + 'toBeNaN', + 'toBeNull', + 'toBeTruthy', + 'toBeUndefined', + 'toContain', + 'toEqual', + 'toHaveBeenCalled', + 'toHaveBeenCalledWith', + 'toMatch', + 'toThrow', + 'toThrowError' + ], + matchers = {}; + + for (var i = 0; i < availableMatchers.length; i++) { + var name = availableMatchers[i]; + matchers[name] = jRequire[name](j$); + } + + return matchers; +}; + +getJasmineRequireObj().base = function(j$, jasmineGlobal) { + j$.unimplementedMethod_ = function() { + throw new Error('unimplemented method'); + }; + + j$.MAX_PRETTY_PRINT_DEPTH = 40; + j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; + j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + + j$.getGlobal = function() { + return jasmineGlobal; + }; + + j$.getEnv = function(options) { + var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); + //jasmine. singletons in here (setTimeout blah blah). + return env; + }; + + j$.isArray_ = function(value) { + return j$.isA_('Array', value); + }; + + j$.isString_ = function(value) { + return j$.isA_('String', value); + }; + + j$.isNumber_ = function(value) { + return j$.isA_('Number', value); + }; + + j$.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; + }; + + j$.isDomNode = function(obj) { + return obj.nodeType > 0; + }; + + j$.fnNameFor = function(func) { + return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; + }; + + j$.any = function(clazz) { + return new j$.Any(clazz); + }; + + j$.anything = function() { + return new j$.Anything(); + }; + + j$.objectContaining = function(sample) { + return new j$.ObjectContaining(sample); + }; + + j$.stringMatching = function(expected) { + return new j$.StringMatching(expected); + }; + + j$.arrayContaining = function(sample) { + return new j$.ArrayContaining(sample); + }; + + j$.createSpy = function(name, originalFn) { + + var spyStrategy = new j$.SpyStrategy({ + name: name, + fn: originalFn, + getSpy: function() { return spy; } + }), + callTracker = new j$.CallTracker(), + spy = function() { + var callData = { + object: this, + args: Array.prototype.slice.apply(arguments) + }; + + callTracker.track(callData); + var returnValue = spyStrategy.exec.apply(this, arguments); + callData.returnValue = returnValue; + + return returnValue; + }; + + for (var prop in originalFn) { + if (prop === 'and' || prop === 'calls') { + throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); + } + + spy[prop] = originalFn[prop]; + } + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + j$.createSpyObj = function(baseName, methodNames) { + if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + if (!j$.isArray_(methodNames) || methodNames.length === 0) { + throw 'createSpyObj requires a non-empty array of method names to create spies for'; + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + } + return obj; + }; +}; + +getJasmineRequireObj().util = function() { + + var util = {}; + + util.inherit = function(childClass, parentClass) { + var Subclass = function() { + }; + Subclass.prototype = parentClass.prototype; + childClass.prototype = new Subclass(); + }; + + util.htmlEscape = function(str) { + if (!str) { + return str; + } + return str.replace(/&/g, '&') + .replace(//g, '>'); + }; + + util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) { + arrayOfArgs.push(args[i]); + } + return arrayOfArgs; + }; + + util.isUndefined = function(obj) { + return obj === void 0; + }; + + util.arrayContains = function(array, search) { + var i = array.length; + while (i--) { + if (array[i] === search) { + return true; + } + } + return false; + }; + + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + }; + + return util; +}; + +getJasmineRequireObj().Spec = function(j$) { + function Spec(attrs) { + this.expectationFactory = attrs.expectationFactory; + this.resultCallback = attrs.resultCallback || function() {}; + this.id = attrs.id; + this.description = attrs.description || ''; + this.queueableFn = attrs.queueableFn; + this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; + this.userContext = attrs.userContext || function() { return {}; }; + this.onStart = attrs.onStart || function() {}; + this.getSpecName = attrs.getSpecName || function() { return ''; }; + this.expectationResultFactory = attrs.expectationResultFactory || function() { }; + this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; + this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + if (!this.queueableFn.fn) { + this.pend(); + } + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [], + passedExpectations: [], + pendingReason: '' + }; + } + + Spec.prototype.addExpectationResult = function(passed, data, isError) { + var expectationResult = this.expectationResultFactory(data); + if (passed) { + this.result.passedExpectations.push(expectationResult); + } else { + this.result.failedExpectations.push(expectationResult); + + if (this.throwOnExpectationFailure && !isError) { + throw new j$.errors.ExpectationFailed(); + } + } + }; + + Spec.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Spec.prototype.execute = function(onComplete, enabled) { + var self = this; + + this.onStart(this); + + if (!this.isExecutable() || this.markedPending || enabled === false) { + complete(enabled); + return; + } + + var fns = this.beforeAndAfterFns(); + var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); + + this.queueRunnerFactory({ + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, + onComplete: complete, + userContext: this.userContext() + }); + + function complete(enabledAgain) { + self.result.status = self.status(enabledAgain); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + }; + + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(extractCustomPendingMessage(e)); + return; + } + + if (e instanceof j$.errors.ExpectationFailed) { + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }, true); + }; + + Spec.prototype.disable = function() { + this.disabled = true; + }; + + Spec.prototype.pend = function(message) { + this.markedPending = true; + if (message) { + this.result.pendingReason = message; + } + }; + + Spec.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Spec.prototype.status = function(enabled) { + if (this.disabled || enabled === false) { + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'passed'; + } + }; + + Spec.prototype.isExecutable = function() { + return !this.disabled; + }; + + Spec.prototype.getFullName = function() { + return this.getSpecName(this); + }; + + var extractCustomPendingMessage = function(e) { + var fullMessage = e.toString(), + boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), + boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; + + return fullMessage.substr(boilerplateEnd); + }; + + Spec.pendingSpecExceptionMessage = '=> marked Pending'; + + Spec.isPendingSpecException = function(e) { + return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); + }; + + return Spec; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Spec = jasmineRequire.Spec; +} + +getJasmineRequireObj().Env = function(j$) { + function Env(options) { + options = options || {}; + + var self = this; + var global = options.global || j$.getGlobal(); + + var totalSpecsDefined = 0; + + var catchExceptions = true; + + var realSetTimeout = j$.getGlobal().setTimeout; + var realClearTimeout = j$.getGlobal().clearTimeout; + this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global)); + + var runnableLookupTable = {}; + var runnableResources = {}; + + var currentSpec = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + var throwOnExpectationFailure = false; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + var currentRunnable = function() { + return currentSpec || currentSuite(); + }; + + var reporter = new j$.ReportDispatcher([ + 'jasmineStarted', + 'jasmineDone', + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone' + ]); + + this.specFilter = function() { + return true; + }; + + this.addCustomEqualityTester = function(tester) { + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } + }; + + j$.Expectation.addCoreMatchers(j$.matchers); + + var nextSpecId = 0; + var getNextSpecId = function() { + return 'spec' + nextSpecId++; + }; + + var nextSuiteId = 0; + var getNextSuiteId = function() { + return 'suite' + nextSuiteId++; + }; + + var expectationFactory = function(actual, spec) { + return j$.Expectation.Factory({ + util: j$.matchersUtil, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, + actual: actual, + addExpectationResult: addExpectationResult + }); + + function addExpectationResult(passed, result) { + return spec.addExpectationResult(passed, result); + } + }; + + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; + }; + + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + var beforeAndAfterFns = function(suite) { + return function() { + var befores = [], + afters = []; + + while(suite) { + befores = befores.concat(suite.beforeFns); + afters = afters.concat(suite.afterFns); + + suite = suite.parentSuite; + } + + return { + befores: befores.reverse(), + afters: afters + }; + }; + }; + + var getSpecName = function(spec, suite) { + return suite.getFullName() + ' ' + spec.description; + }; + + // TODO: we may just be able to pass in the fn instead of wrapping here + var buildExpectationResult = j$.buildExpectationResult, + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; + + return buildExpectationResult(attrs); + }; + + // TODO: fix this naming, and here's where the value comes in + this.catchExceptions = function(value) { + catchExceptions = !!value; + return catchExceptions; + }; + + this.catchingExceptions = function() { + return catchExceptions; + }; + + var maximumSpecCallbackDepth = 20; + var currentSpecCallbackDepth = 0; + + function clearStack(fn) { + currentSpecCallbackDepth++; + if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { + currentSpecCallbackDepth = 0; + realSetTimeout(fn, 0); + } else { + fn(); + } + } + + var catchException = function(e) { + return j$.Spec.isPendingSpecException(e) || catchExceptions; + }; + + this.throwOnExpectationFailure = function(value) { + throwOnExpectationFailure = !!value; + }; + + this.throwingExpectationFailures = function() { + return throwOnExpectationFailure; + }; + + var queueRunnerFactory = function(options) { + options.catchException = catchException; + options.clearStack = options.clearStack || clearStack; + options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; + options.fail = self.fail; + + new j$.QueueRunner(options).execute(); + }; + + var topSuite = new j$.Suite({ + env: this, + id: getNextSuiteId(), + description: 'Jasmine__TopLevel__Suite', + queueRunner: queueRunnerFactory + }); + runnableLookupTable[topSuite.id] = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; + + this.topSuite = function() { + return topSuite; + }; + + this.execute = function(runnablesToRun) { + if(!runnablesToRun) { + if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } + } + var processor = new j$.TreeProcessor({ + tree: topSuite, + runnableIds: runnablesToRun, + queueRunnerFactory: queueRunnerFactory, + nodeStart: function(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + }, + nodeComplete: function(suite, result) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + } + currentlyExecutingSuites.pop(); + reporter.suiteDone(result); + } + }); + + if(!processor.processTree().valid) { + throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times'); + } + + reporter.jasmineStarted({ + totalSpecsDefined: totalSpecsDefined + }); + + processor.execute(reporter.jasmineDone); + }; + + this.addReporter = function(reporterToAdd) { + reporter.addReporter(reporterToAdd); + }; + + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } + return runnableResources[currentRunnable().id].spies; + }}); + + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); + }; + + var suiteFactory = function(description) { + var suite = new j$.Suite({ + env: self, + id: getNextSuiteId(), + description: description, + parentSuite: currentDeclarationSuite, + expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, + throwOnExpectationFailure: throwOnExpectationFailure + }); + + runnableLookupTable[suite.id] = suite; + return suite; + }; + + this.describe = function(description, specDefinitions) { + var suite = suiteFactory(description); + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + + this.xdescribe = function(description, specDefinitions) { + var suite = this.describe(description, specDefinitions); + suite.disable(); + return suite; + }; + + var focusedRunnables = []; + + this.fdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { + var parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + self.it('encountered a declaration exception', function() { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + } + + function findFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return suite.id; + } + suite = suite.parentSuite; + } + + return null; + } + + function unfocusAncestor() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } + } + + var specFactory = function(description, fn, suite, timeout) { + totalSpecsDefined++; + var spec = new j$.Spec({ + id: getNextSpecId(), + beforeAndAfterFns: beforeAndAfterFns(suite), + expectationFactory: expectationFactory, + resultCallback: specResultCallback, + getSpecName: function(spec) { + return getSpecName(spec, suite); + }, + onStart: specStarted, + description: description, + expectationResultFactory: expectationResultFactory, + queueRunnerFactory: queueRunnerFactory, + userContext: function() { return suite.clonedSharedUserContext(); }, + queueableFn: { + fn: fn, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }, + throwOnExpectationFailure: throwOnExpectationFailure + }); + + runnableLookupTable[spec.id] = spec; + + if (!self.specFilter(spec)) { + spec.disable(); + } + + return spec; + + function specResultCallback(result) { + clearResourcesForRunnable(spec.id); + currentSpec = null; + reporter.specDone(result); + } + + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } + }; + + this.it = function(description, fn, timeout) { + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); + currentDeclarationSuite.addChild(spec); + return spec; + }; + + this.xit = function() { + var spec = this.it.apply(this, arguments); + spec.pend(); + return spec; + }; + + this.fit = function(){ + var spec = this.it.apply(this, arguments); + + focusedRunnables.push(spec.id); + unfocusAncestor(); + return spec; + }; + + this.expect = function(actual) { + if (!currentRunnable()) { + throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); + } + + return currentRunnable().expect(actual); + }; + + this.beforeEach = function(beforeEachFunction, timeout) { + currentDeclarationSuite.beforeEach({ + fn: beforeEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.beforeAll = function(beforeAllFunction, timeout) { + currentDeclarationSuite.beforeAll({ + fn: beforeAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterEach = function(afterEachFunction, timeout) { + currentDeclarationSuite.afterEach({ + fn: afterEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterAll = function(afterAllFunction, timeout) { + currentDeclarationSuite.afterAll({ + fn: afterAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.pending = function(message) { + var fullMessage = j$.Spec.pendingSpecExceptionMessage; + if(message) { + fullMessage += message; + } + throw fullMessage; + }; + + this.fail = function(error) { + var message = 'Failed'; + if (error) { + message += ': '; + message += error.message || error; + } + + currentRunnable().addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + message: message, + error: error && error.message ? error : null + }); + }; + } + + return Env; +}; + +getJasmineRequireObj().JsApiReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function JsApiReporter(options) { + var timer = options.timer || noopTimer, + status = 'loaded'; + + this.started = false; + this.finished = false; + + this.jasmineStarted = function() { + this.started = true; + status = 'started'; + timer.start(); + }; + + var executionTime; + + this.jasmineDone = function() { + this.finished = true; + executionTime = timer.elapsed(); + status = 'done'; + }; + + this.status = function() { + return status; + }; + + var suites = [], + suites_hash = {}; + + this.suiteStarted = function(result) { + suites_hash[result.id] = result; + }; + + this.suiteDone = function(result) { + storeSuite(result); + }; + + this.suiteResults = function(index, length) { + return suites.slice(index, index + length); + }; + + function storeSuite(result) { + suites.push(result); + suites_hash[result.id] = result; + } + + this.suites = function() { + return suites_hash; + }; + + var specs = []; + + this.specDone = function(result) { + specs.push(result); + }; + + this.specResults = function(index, length) { + return specs.slice(index, index + length); + }; + + this.specs = function() { + return specs; + }; + + this.executionTime = function() { + return executionTime; + }; + + } + + return JsApiReporter; +}; + +getJasmineRequireObj().CallTracker = function() { + + function CallTracker() { + var calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + var call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + var callArgs = []; + for(var i = 0; i < calls.length; i++){ + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } + + return CallTracker; +}; + +getJasmineRequireObj().Clock = function() { + function Clock(global, delayedFunctionSchedulerFactory, mockDate) { + var self = this, + realTimingFunctions = { + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval + }, + fakeTimingFunctions = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval + }, + installed = false, + delayedFunctionScheduler, + timer; + + + self.install = function() { + if(!originalTimingFunctionsIntact()) { + throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?'); + } + replace(global, fakeTimingFunctions); + timer = fakeTimingFunctions; + delayedFunctionScheduler = delayedFunctionSchedulerFactory(); + installed = true; + + return self; + }; + + self.uninstall = function() { + delayedFunctionScheduler = null; + mockDate.uninstall(); + replace(global, realTimingFunctions); + + timer = realTimingFunctions; + installed = false; + }; + + self.withMock = function(closure) { + this.install(); + try { + closure(); + } finally { + this.uninstall(); + } + }; + + self.mockDate = function(initialDate) { + mockDate.install(initialDate); + }; + + self.setTimeout = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); + } + return timer.setTimeout(fn, delay); + } + return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); + }; + + self.setInterval = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); + } + return timer.setInterval(fn, delay); + } + return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); + }; + + self.clearTimeout = function(id) { + return Function.prototype.call.apply(timer.clearTimeout, [global, id]); + }; + + self.clearInterval = function(id) { + return Function.prototype.call.apply(timer.clearInterval, [global, id]); + }; + + self.tick = function(millis) { + if (installed) { + mockDate.tick(millis); + delayedFunctionScheduler.tick(millis); + } else { + throw new Error('Mock clock is not installed, use jasmine.clock().install()'); + } + }; + + return self; + + function originalTimingFunctionsIntact() { + return global.setTimeout === realTimingFunctions.setTimeout && + global.clearTimeout === realTimingFunctions.clearTimeout && + global.setInterval === realTimingFunctions.setInterval && + global.clearInterval === realTimingFunctions.clearInterval; + } + + function legacyIE() { + //if these methods are polyfilled, apply will be present + return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; + } + + function replace(dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } + } + + function setTimeout(fn, delay) { + return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); + } + + function clearTimeout(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function setInterval(fn, interval) { + return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); + } + + function clearInterval(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function argSlice(argsObj, n) { + return Array.prototype.slice.call(argsObj, n); + } + } + + return Clock; +}; + +getJasmineRequireObj().DelayedFunctionScheduler = function() { + function DelayedFunctionScheduler() { + var self = this; + var scheduledLookup = []; + var scheduledFunctions = {}; + var currentTime = 0; + var delayedFnCount = 0; + + self.tick = function(millis) { + millis = millis || 0; + var endTime = currentTime + millis; + + runScheduledFunctions(endTime); + currentTime = endTime; + }; + + self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { + var f; + if (typeof(funcToCall) === 'string') { + /* jshint evil: true */ + f = function() { return eval(funcToCall); }; + /* jshint evil: false */ + } else { + f = funcToCall; + } + + millis = millis || 0; + timeoutKey = timeoutKey || ++delayedFnCount; + runAtMillis = runAtMillis || (currentTime + millis); + + var funcToSchedule = { + runAtMillis: runAtMillis, + funcToCall: f, + recurring: recurring, + params: params, + timeoutKey: timeoutKey, + millis: millis + }; + + if (runAtMillis in scheduledFunctions) { + scheduledFunctions[runAtMillis].push(funcToSchedule); + } else { + scheduledFunctions[runAtMillis] = [funcToSchedule]; + scheduledLookup.push(runAtMillis); + scheduledLookup.sort(function (a, b) { + return a - b; + }); + } + + return timeoutKey; + }; + + self.removeFunctionWithId = function(timeoutKey) { + for (var runAtMillis in scheduledFunctions) { + var funcs = scheduledFunctions[runAtMillis]; + var i = indexOfFirstToPass(funcs, function (func) { + return func.timeoutKey === timeoutKey; + }); + + if (i > -1) { + if (funcs.length === 1) { + delete scheduledFunctions[runAtMillis]; + deleteFromLookup(runAtMillis); + } else { + funcs.splice(i, 1); + } + + // intervals get rescheduled when executed, so there's never more + // than a single scheduled function with a given timeoutKey + break; + } + } + }; + + return self; + + function indexOfFirstToPass(array, testFn) { + var index = -1; + + for (var i = 0; i < array.length; ++i) { + if (testFn(array[i])) { + index = i; + break; + } + } + + return index; + } + + function deleteFromLookup(key) { + var value = Number(key); + var i = indexOfFirstToPass(scheduledLookup, function (millis) { + return millis === value; + }); + + if (i > -1) { + scheduledLookup.splice(i, 1); + } + } + + function reschedule(scheduledFn) { + self.scheduleFunction(scheduledFn.funcToCall, + scheduledFn.millis, + scheduledFn.params, + true, + scheduledFn.timeoutKey, + scheduledFn.runAtMillis + scheduledFn.millis); + } + + function forEachFunction(funcsToRun, callback) { + for (var i = 0; i < funcsToRun.length; ++i) { + callback(funcsToRun[i]); + } + } + + function runScheduledFunctions(endTime) { + if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { + return; + } + + do { + currentTime = scheduledLookup.shift(); + + var funcsToRun = scheduledFunctions[currentTime]; + delete scheduledFunctions[currentTime]; + + forEachFunction(funcsToRun, function(funcToRun) { + if (funcToRun.recurring) { + reschedule(funcToRun); + } + }); + + forEachFunction(funcsToRun, function(funcToRun) { + funcToRun.funcToCall.apply(null, funcToRun.params || []); + }); + } while (scheduledLookup.length > 0 && + // checking first if we're out of time prevents setTimeout(0) + // scheduled in a funcToRun from forcing an extra iteration + currentTime !== endTime && + scheduledLookup[0] <= endTime); + } + } + + return DelayedFunctionScheduler; +}; + +getJasmineRequireObj().ExceptionFormatter = function() { + function ExceptionFormatter() { + this.message = function(error) { + var message = ''; + + if (error.name && error.message) { + message += error.name + ': ' + error.message; + } else { + message += error.toString() + ' thrown'; + } + + if (error.fileName || error.sourceURL) { + message += ' in ' + (error.fileName || error.sourceURL); + } + + if (error.line || error.lineNumber) { + message += ' (line ' + (error.line || error.lineNumber) + ')'; + } + + return message; + }; + + this.stack = function(error) { + return error ? error.stack : null; + }; + } + + return ExceptionFormatter; +}; + +getJasmineRequireObj().Expectation = function() { + + function Expectation(options) { + this.util = options.util || { buildFailureMessage: function() {} }; + this.customEqualityTesters = options.customEqualityTesters || []; + this.actual = options.actual; + this.addExpectationResult = options.addExpectationResult || function(){}; + this.isNot = options.isNot; + + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); + } + } + + Expectation.prototype.wrapCompare = function(name, matcherFactory) { + return function() { + var args = Array.prototype.slice.call(arguments, 0), + expected = args.slice(0), + message = ''; + + args.unshift(this.actual); + + var matcher = matcherFactory(this.util, this.customEqualityTesters), + matcherCompare = matcher.compare; + + function defaultNegativeCompare() { + var result = matcher.compare.apply(null, args); + result.pass = !result.pass; + return result; + } + + if (this.isNot) { + matcherCompare = matcher.negativeCompare || defaultNegativeCompare; + } + + var result = matcherCompare.apply(null, args); + + if (!result.pass) { + if (!result.message) { + args.unshift(this.isNot); + args.unshift(name); + message = this.util.buildFailureMessage.apply(null, args); + } else { + if (Object.prototype.toString.apply(result.message) === '[object Function]') { + message = result.message(); + } else { + message = result.message; + } + } + } + + if (expected.length == 1) { + expected = expected[0]; + } + + // TODO: how many of these params are needed? + this.addExpectationResult( + result.pass, + { + matcherName: name, + passed: result.pass, + message: message, + actual: this.actual, + expected: expected // TODO: this may need to be arrayified/sliced + } + ); + }; + }; + + Expectation.addCoreMatchers = function(matchers) { + var prototype = Expectation.prototype; + for (var matcherName in matchers) { + var matcher = matchers[matcherName]; + prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); + } + }; + + Expectation.Factory = function(options) { + options = options || {}; + + var expect = new Expectation(options); + + // TODO: this would be nice as its own Object - NegativeExpectation + // TODO: copy instead of mutate options + options.isNot = true; + expect.not = new Expectation(options); + + return expect; + }; + + return Expectation; +}; + +//TODO: expectation result may make more sense as a presentation of an expectation. +getJasmineRequireObj().buildExpectationResult = function() { + function buildExpectationResult(options) { + var messageFormatter = options.messageFormatter || function() {}, + stackFormatter = options.stackFormatter || function() {}; + + var result = { + matcherName: options.matcherName, + message: message(), + stack: stack(), + passed: options.passed + }; + + if(!result.passed) { + result.expected = options.expected; + result.actual = options.actual; + } + + return result; + + function message() { + if (options.passed) { + return 'Passed.'; + } else if (options.message) { + return options.message; + } else if (options.error) { + return messageFormatter(options.error); + } + return ''; + } + + function stack() { + if (options.passed) { + return ''; + } + + var error = options.error; + if (!error) { + try { + throw new Error(message()); + } catch (e) { + error = e; + } + } + return stackFormatter(error); + } + } + + return buildExpectationResult; +}; + +getJasmineRequireObj().MockDate = function() { + function MockDate(global) { + var self = this; + var currentTime = 0; + + if (!global || !global.Date) { + self.install = function() {}; + self.tick = function() {}; + self.uninstall = function() {}; + return self; + } + + var GlobalDate = global.Date; + + self.install = function(mockDate) { + if (mockDate instanceof GlobalDate) { + currentTime = mockDate.getTime(); + } else { + currentTime = new GlobalDate().getTime(); + } + + global.Date = FakeDate; + }; + + self.tick = function(millis) { + millis = millis || 0; + currentTime = currentTime + millis; + }; + + self.uninstall = function() { + currentTime = 0; + global.Date = GlobalDate; + }; + + createDateProperties(); + + return self; + + function FakeDate() { + switch(arguments.length) { + case 0: + return new GlobalDate(currentTime); + case 1: + return new GlobalDate(arguments[0]); + case 2: + return new GlobalDate(arguments[0], arguments[1]); + case 3: + return new GlobalDate(arguments[0], arguments[1], arguments[2]); + case 4: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); + case 5: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4]); + case 6: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5]); + default: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5], arguments[6]); + } + } + + function createDateProperties() { + FakeDate.prototype = GlobalDate.prototype; + + FakeDate.now = function() { + if (GlobalDate.now) { + return currentTime; + } else { + throw new Error('Browser does not support Date.now()'); + } + }; + + FakeDate.toSource = GlobalDate.toSource; + FakeDate.toString = GlobalDate.toString; + FakeDate.parse = GlobalDate.parse; + FakeDate.UTC = GlobalDate.UTC; + } + } + + return MockDate; +}; + +getJasmineRequireObj().pp = function(j$) { + + function PrettyPrinter() { + this.ppNestLevel_ = 0; + this.seen = []; + } + + PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (j$.util.isUndefined(value)) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === 0 && 1/value === -Infinity) { + this.emitScalar('-0'); + } else if (value === j$.getGlobal()) { + this.emitScalar(''); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (j$.isSpy(value)) { + this.emitScalar('spy on ' + value.and.identity()); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (j$.util.arrayContains(this.seen, value)) { + this.emitScalar(''); + } else if (j$.isArray_(value) || j$.isA_('Object', value)) { + this.seen.push(value); + if (j$.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + this.seen.pop(); + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } + }; + + PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } + fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && + obj.__lookupGetter__(property) !== null) : false); + } + }; + + PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; + + function StringPrettyPrinter() { + PrettyPrinter.call(this); + + this.string = ''; + } + + j$.util.inherit(StringPrettyPrinter, PrettyPrinter); + + StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); + }; + + StringPrettyPrinter.prototype.emitString = function(value) { + this.append('\'' + value + '\''); + }; + + StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + this.append('Array'); + return; + } + var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); + this.append('[ '); + for (var i = 0; i < length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + if(array.length > length){ + this.append(', ...'); + } + + var self = this; + var first = array.length === 0; + this.iterateObject(array, function(property, isGetter) { + if (property.match(/^\d+$/)) { + return; + } + + if (first) { + first = false; + } else { + self.append(', '); + } + + self.formatProperty(array, property, isGetter); + }); + + this.append(' ]'); + }; + + StringPrettyPrinter.prototype.emitObject = function(obj) { + var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null'; + this.append(constructorName); + + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + return; + } + + var self = this; + this.append('({ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.formatProperty(obj, property, isGetter); + }); + + this.append(' })'); + }; + + StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { + this.append(property); + this.append(': '); + if (isGetter) { + this.append(''); + } else { + this.format(obj[property]); + } + }; + + StringPrettyPrinter.prototype.append = function(value) { + this.string += value; + }; + + return function(value) { + var stringPrettyPrinter = new StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; + }; +}; + +getJasmineRequireObj().QueueRunner = function(j$) { + + function once(fn) { + var called = false; + return function() { + if (!called) { + called = true; + fn(); + } + }; + } + + function QueueRunner(attrs) { + this.queueableFns = attrs.queueableFns || []; + this.onComplete = attrs.onComplete || function() {}; + this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.onException = attrs.onException || function() {}; + this.catchException = attrs.catchException || function() { return true; }; + this.userContext = attrs.userContext || {}; + this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; + this.fail = attrs.fail || function() {}; + } + + QueueRunner.prototype.execute = function() { + this.run(this.queueableFns, 0); + }; + + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, + self = this, + iterativeIndex; + + + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + attemptAsync(queueableFn); + return; + } else { + attemptSync(queueableFn); + } + } + + var runnerDone = iterativeIndex >= length; + + if (runnerDone) { + this.clearStack(this.onComplete); + } + + function attemptSync(queueableFn) { + try { + queueableFn.fn.call(self.userContext); + } catch (e) { + handleException(e, queueableFn); + } + } + + function attemptAsync(queueableFn) { + var clearTimeout = function () { + Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); + }, + next = once(function () { + clearTimeout(timeoutId); + self.run(queueableFns, iterativeIndex + 1); + }), + timeoutId; + + next.fail = function() { + self.fail.apply(null, arguments); + next(); + }; + + if (queueableFn.timeout) { + timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() { + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error, queueableFn); + next(); + }, queueableFn.timeout()]]); + } + + try { + queueableFn.fn.call(self.userContext, next); + } catch (e) { + handleException(e, queueableFn); + next(); + } + } + + function onException(e, queueableFn) { + self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e, queueableFn); + if (!self.catchException(e)) { + //TODO: set a var when we catch an exception and + //use a finally block to close the loop in a nice way.. + throw e; + } + } + }; + + return QueueRunner; +}; + +getJasmineRequireObj().ReportDispatcher = function() { + function ReportDispatcher(methods) { + + var dispatchedMethods = methods || []; + + for (var i = 0; i < dispatchedMethods.length; i++) { + var method = dispatchedMethods[i]; + this[method] = (function(m) { + return function() { + dispatch(m, arguments); + }; + }(method)); + } + + var reporters = []; + + this.addReporter = function(reporter) { + reporters.push(reporter); + }; + + return this; + + function dispatch(method, args) { + for (var i = 0; i < reporters.length; i++) { + var reporter = reporters[i]; + if (reporter[method]) { + reporter[method].apply(reporter, args); + } + } + } + } + + return ReportDispatcher; +}; + + +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(methodName)) { + throw new Error('No method name supplied'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; + + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } + + return SpyRegistry; +}; + +getJasmineRequireObj().SpyStrategy = function() { + + function SpyStrategy(options) { + options = options || {}; + + var identity = options.name || 'unknown', + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; + + this.identity = function() { + return identity; + }; + + this.exec = function() { + return plan.apply(this, arguments); + }; + + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + + this.returnValue = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; + + this.returnValues = function() { + var values = Array.prototype.slice.call(arguments); + plan = function () { + return values.shift(); + }; + return getSpy(); + }; + + this.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + plan = function() { + throw error; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + plan = fn; + return getSpy(); + }; + + this.stub = function(fn) { + plan = function() {}; + return getSpy(); + }; + } + + return SpyStrategy; +}; + +getJasmineRequireObj().Suite = function(j$) { + function Suite(attrs) { + this.env = attrs.env; + this.id = attrs.id; + this.parentSuite = attrs.parentSuite; + this.description = attrs.description; + this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + this.beforeFns = []; + this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; + this.disabled = false; + + this.children = []; + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [] + }; + } + + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + if (parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + } + return fullName; + }; + + Suite.prototype.disable = function() { + this.disabled = true; + }; + + Suite.prototype.beforeEach = function(fn) { + this.beforeFns.unshift(fn); + }; + + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + + Suite.prototype.afterEach = function(fn) { + this.afterFns.unshift(fn); + }; + + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + + Suite.prototype.addChild = function(child) { + this.children.push(child); + }; + + Suite.prototype.status = function() { + if (this.disabled) { + return 'disabled'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'finished'; + } + }; + + Suite.prototype.isExecutable = function() { + return !this.disabled; + }; + + Suite.prototype.canBeReentered = function() { + return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; + }; + + Suite.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + Suite.prototype.onException = function() { + if (arguments[0] instanceof j$.errors.ExpectationFailed) { + return; + } + + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } + } + }; + + Suite.prototype.addExpectationResult = function () { + if(isAfterAll(this.children) && isFailure(arguments)){ + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + if(this.throwOnExpectationFailure) { + throw new j$.errors.ExpectationFailed(); + } + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + try { + child.addExpectationResult.apply(child, arguments); + } catch(e) { + // keep going + } + } + } + }; + + function isAfterAll(children) { + return children && children[0].result.status; + } + + function isFailure(args) { + return !args[0]; + } + + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; + } + } + + return clonedObj; + } + + return Suite; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Suite = jasmineRequire.Suite; +} + +getJasmineRequireObj().Timer = function() { + var defaultNow = (function(Date) { + return function() { return new Date().getTime(); }; + })(Date); + + function Timer(options) { + options = options || {}; + + var now = options.now || defaultNow, + startTime; + + this.start = function() { + startTime = now(); + }; + + this.elapsed = function() { + return now() - startTime; + }; + } + + return Timer; +}; + +getJasmineRequireObj().TreeProcessor = function() { + function TreeProcessor(attrs) { + var tree = attrs.tree, + runnableIds = attrs.runnableIds, + queueRunnerFactory = attrs.queueRunnerFactory, + nodeStart = attrs.nodeStart || function() {}, + nodeComplete = attrs.nodeComplete || function() {}, + stats = { valid: true }, + processed = false, + defaultMin = Infinity, + defaultMax = 1 - Infinity; + + this.processTree = function() { + processNode(tree, false); + processed = true; + return stats; + }; + + this.execute = function(done) { + if (!processed) { + this.processTree(); + } + + if (!stats.valid) { + throw 'invalid order'; + } + + var childFns = wrapChildren(tree, 0); + + queueRunnerFactory({ + queueableFns: childFns, + userContext: tree.sharedUserContext(), + onException: function() { + tree.onException.apply(tree, arguments); + }, + onComplete: done + }); + }; + + function runnableIndex(id) { + for (var i = 0; i < runnableIds.length; i++) { + if (runnableIds[i] === id) { + return i; + } + } + } + + function processNode(node, parentEnabled) { + var executableIndex = runnableIndex(node.id); + + if (executableIndex !== undefined) { + parentEnabled = true; + } + + parentEnabled = parentEnabled && node.isExecutable(); + + if (!node.children) { + stats[node.id] = { + executable: parentEnabled && node.isExecutable(), + segments: [{ + index: 0, + owner: node, + nodes: [node], + min: startingMin(executableIndex), + max: startingMax(executableIndex) + }] + }; + } else { + var hasExecutableChild = false; + + for (var i = 0; i < node.children.length; i++) { + var child = node.children[i]; + + processNode(child, parentEnabled); + + if (!stats.valid) { + return; + } + + var childStats = stats[child.id]; + + hasExecutableChild = hasExecutableChild || childStats.executable; + } + + stats[node.id] = { + executable: hasExecutableChild + }; + + segmentChildren(node, stats[node.id], executableIndex); + + if (!node.canBeReentered() && stats[node.id].segments.length > 1) { + stats = { valid: false }; + } + } + } + + function startingMin(executableIndex) { + return executableIndex === undefined ? defaultMin : executableIndex; + } + + function startingMax(executableIndex) { + return executableIndex === undefined ? defaultMax : executableIndex; + } + + function segmentChildren(node, nodeStats, executableIndex) { + var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, + result = [currentSegment], + lastMax = defaultMax, + orderedChildSegments = orderChildSegments(node.children); + + function isSegmentBoundary(minIndex) { + return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1; + } + + for (var i = 0; i < orderedChildSegments.length; i++) { + var childSegment = orderedChildSegments[i], + maxIndex = childSegment.max, + minIndex = childSegment.min; + + if (isSegmentBoundary(minIndex)) { + currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax}; + result.push(currentSegment); + } + + currentSegment.nodes.push(childSegment); + currentSegment.min = Math.min(currentSegment.min, minIndex); + currentSegment.max = Math.max(currentSegment.max, maxIndex); + lastMax = maxIndex; + } + + nodeStats.segments = result; + } + + function orderChildSegments(children) { + var specifiedOrder = [], + unspecifiedOrder = []; + + for (var i = 0; i < children.length; i++) { + var child = children[i], + segments = stats[child.id].segments; + + for (var j = 0; j < segments.length; j++) { + var seg = segments[j]; + + if (seg.min === defaultMin) { + unspecifiedOrder.push(seg); + } else { + specifiedOrder.push(seg); + } + } + } + + specifiedOrder.sort(function(a, b) { + return a.min - b.min; + }); + + return specifiedOrder.concat(unspecifiedOrder); + } + + function executeNode(node, segmentNumber) { + if (node.children) { + return { + fn: function(done) { + nodeStart(node); + + queueRunnerFactory({ + onComplete: function() { + nodeComplete(node, node.getResult()); + done(); + }, + queueableFns: wrapChildren(node, segmentNumber), + userContext: node.sharedUserContext(), + onException: function() { + node.onException.apply(node, arguments); + } + }); + } + }; + } else { + return { + fn: function(done) { node.execute(done, stats[node.id].executable); } + }; + } + } + + function wrapChildren(node, segmentNumber) { + var result = [], + segmentChildren = stats[node.id].segments[segmentNumber].nodes; + + for (var i = 0; i < segmentChildren.length; i++) { + result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); + } + + if (!stats[node.id].executable) { + return result; + } + + return node.beforeAllFns.concat(result).concat(node.afterAllFns); + } + } + + return TreeProcessor; +}; + +getJasmineRequireObj().Any = function(j$) { + + function Any(expectedObject) { + this.expectedObject = expectedObject; + } + + Any.prototype.asymmetricMatch = function(other) { + if (this.expectedObject == String) { + return typeof other == 'string' || other instanceof String; + } + + if (this.expectedObject == Number) { + return typeof other == 'number' || other instanceof Number; + } + + if (this.expectedObject == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedObject == Object) { + return typeof other == 'object'; + } + + if (this.expectedObject == Boolean) { + return typeof other == 'boolean'; + } + + return other instanceof this.expectedObject; + }; + + Any.prototype.jasmineToString = function() { + return ''; + }; + + return Any; +}; + +getJasmineRequireObj().Anything = function(j$) { + + function Anything() {} + + Anything.prototype.asymmetricMatch = function(other) { + return !j$.util.isUndefined(other) && other !== null; + }; + + Anything.prototype.jasmineToString = function() { + return ''; + }; + + return Anything; +}; + +getJasmineRequireObj().ArrayContaining = function(j$) { + function ArrayContaining(sample) { + this.sample = sample; + } + + ArrayContaining.prototype.asymmetricMatch = function(other) { + var className = Object.prototype.toString.call(this.sample); + if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); } + + for (var i = 0; i < this.sample.length; i++) { + var item = this.sample[i]; + if (!j$.matchersUtil.contains(other, item)) { + return false; + } + } + + return true; + }; + + ArrayContaining.prototype.jasmineToString = function () { + return ''; + }; + + return ArrayContaining; +}; + +getJasmineRequireObj().ObjectContaining = function(j$) { + + function ObjectContaining(sample) { + this.sample = sample; + } + + function getPrototype(obj) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(obj); + } + + if (obj.constructor.prototype == obj) { + return null; + } + + return obj.constructor.prototype; + } + + function hasProperty(obj, property) { + if (!obj) { + return false; + } + + if (Object.prototype.hasOwnProperty.call(obj, property)) { + return true; + } + + return hasProperty(getPrototype(obj), property); + } + + ObjectContaining.prototype.asymmetricMatch = function(other) { + if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } + + for (var property in this.sample) { + if (!hasProperty(other, property) || + !j$.matchersUtil.equals(this.sample[property], other[property])) { + return false; + } + } + + return true; + }; + + ObjectContaining.prototype.jasmineToString = function() { + return ''; + }; + + return ObjectContaining; +}; + +getJasmineRequireObj().StringMatching = function(j$) { + + function StringMatching(expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } + + this.regexp = new RegExp(expected); + } + + StringMatching.prototype.asymmetricMatch = function(other) { + return this.regexp.test(other); + }; + + StringMatching.prototype.jasmineToString = function() { + return ''; + }; + + return StringMatching; +}; + +getJasmineRequireObj().errors = function() { + function ExpectationFailed() {} + + ExpectationFailed.prototype = new Error(); + ExpectationFailed.prototype.constructor = ExpectationFailed; + + return { + ExpectationFailed: ExpectationFailed + }; +}; +getJasmineRequireObj().matchersUtil = function(j$) { + // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? + + return { + equals: function(a, b, customTesters) { + customTesters = customTesters || []; + + return eq(a, b, [], [], customTesters); + }, + + contains: function(haystack, needle, customTesters) { + customTesters = customTesters || []; + + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { + for (var i = 0; i < haystack.length; i++) { + if (eq(haystack[i], needle, [], [], customTesters)) { + return true; + } + } + return false; + } + + return !!haystack && haystack.indexOf(needle) >= 0; + }, + + buildFailureMessage: function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; + } + }; + + function isAsymmetric(obj) { + return obj && j$.isA_('Function', obj.asymmetricMatch); + } + + function asymmetricMatch(a, b) { + var asymmetricA = isAsymmetric(a), + asymmetricB = isAsymmetric(b); + + if (asymmetricA && asymmetricB) { + return undefined; + } + + if (asymmetricA) { + return a.asymmetricMatch(b); + } + + if (asymmetricB) { + return b.asymmetricMatch(a); + } + } + + // Equality function lovingly adapted from isEqual in + // [Underscore](http://underscorejs.org) + function eq(a, b, aStack, bStack, customTesters) { + var result = true; + + var asymmetricResult = asymmetricMatch(a, b); + if (!j$.util.isUndefined(asymmetricResult)) { + return asymmetricResult; + } + + for (var i = 0; i < customTesters.length; i++) { + var customTesterResult = customTesters[i](a, b); + if (!j$.util.isUndefined(customTesterResult)) { + return customTesterResult; + } + } + + if (a instanceof Error && b instanceof Error) { + return a.message == b.message; + } + + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) { return a !== 0 || 1 / a == 1 / b; } + // A strict comparison is necessary because `null == undefined`. + if (a === null || b === null) { return a === b; } + var className = Object.prototype.toString.call(a); + if (className != Object.prototype.toString.call(b)) { return false; } + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') { return false; } + + var aIsDomNode = j$.isDomNode(a); + var bIsDomNode = j$.isDomNode(b); + if (aIsDomNode && bIsDomNode) { + // At first try to use DOM3 method isEqualNode + if (a.isEqualNode) { + return a.isEqualNode(b); + } + // IE8 doesn't support isEqualNode, try to use outerHTML && innerText + var aIsElement = a instanceof Element; + var bIsElement = b instanceof Element; + if (aIsElement && bIsElement) { + return a.outerHTML == b.outerHTML; + } + if (aIsElement || bIsElement) { + return false; + } + return a.innerText == b.innerText && a.textContent == b.textContent; + } + if (aIsDomNode || bIsDomNode) { + return false; + } + + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) { return bStack[length] == b; } + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0; + // Recursively compare objects and arrays. + // Compare array lengths to determine if a deep comparison is necessary. + if (className == '[object Array]' && a.length !== b.length) { + result = false; + } + + if (result) { + // Objects with different constructors are not equivalent, but `Object`s + // or `Array`s from different frames are. + if (className !== '[object Array]') { + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && + isFunction(bCtor) && bCtor instanceof bCtor)) { + return false; + } + } + // Deep compare objects. + for (var key in a) { + if (has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (has(b, key) && !(size--)) { break; } + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + + return result; + + function has(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); + } + + function isFunction(obj) { + return typeof obj === 'function'; + } + } +}; + +getJasmineRequireObj().toBe = function() { + function toBe() { + return { + compare: function(actual, expected) { + return { + pass: actual === expected + }; + } + }; + } + + return toBe; +}; + +getJasmineRequireObj().toBeCloseTo = function() { + + function toBeCloseTo() { + return { + compare: function(actual, expected, precision) { + if (precision !== 0) { + precision = precision || 2; + } + + return { + pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) + }; + } + }; + } + + return toBeCloseTo; +}; + +getJasmineRequireObj().toBeDefined = function() { + function toBeDefined() { + return { + compare: function(actual) { + return { + pass: (void 0 !== actual) + }; + } + }; + } + + return toBeDefined; +}; + +getJasmineRequireObj().toBeFalsy = function() { + function toBeFalsy() { + return { + compare: function(actual) { + return { + pass: !!!actual + }; + } + }; + } + + return toBeFalsy; +}; + +getJasmineRequireObj().toBeGreaterThan = function() { + + function toBeGreaterThan() { + return { + compare: function(actual, expected) { + return { + pass: actual > expected + }; + } + }; + } + + return toBeGreaterThan; +}; + + +getJasmineRequireObj().toBeLessThan = function() { + function toBeLessThan() { + return { + + compare: function(actual, expected) { + return { + pass: actual < expected + }; + } + }; + } + + return toBeLessThan; +}; +getJasmineRequireObj().toBeNaN = function(j$) { + + function toBeNaN() { + return { + compare: function(actual) { + var result = { + pass: (actual !== actual) + }; + + if (result.pass) { + result.message = 'Expected actual not to be NaN.'; + } else { + result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; + } + + return result; + } + }; + } + + return toBeNaN; +}; + +getJasmineRequireObj().toBeNull = function() { + + function toBeNull() { + return { + compare: function(actual) { + return { + pass: actual === null + }; + } + }; + } + + return toBeNull; +}; + +getJasmineRequireObj().toBeTruthy = function() { + + function toBeTruthy() { + return { + compare: function(actual) { + return { + pass: !!actual + }; + } + }; + } + + return toBeTruthy; +}; + +getJasmineRequireObj().toBeUndefined = function() { + + function toBeUndefined() { + return { + compare: function(actual) { + return { + pass: void 0 === actual + }; + } + }; + } + + return toBeUndefined; +}; + +getJasmineRequireObj().toContain = function() { + function toContain(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + + return { + pass: util.contains(actual, expected, customEqualityTesters) + }; + } + }; + } + + return toContain; +}; + +getJasmineRequireObj().toEqual = function() { + + function toEqual(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + var result = { + pass: false + }; + + result.pass = util.equals(actual, expected, customEqualityTesters); + + return result; + } + }; + } + + return toEqual; +}; + +getJasmineRequireObj().toHaveBeenCalled = function(j$) { + + function toHaveBeenCalled() { + return { + compare: function(actual) { + var result = {}; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (arguments.length > 1) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } + + result.pass = actual.calls.any(); + + result.message = result.pass ? + 'Expected spy ' + actual.and.identity() + ' not to have been called.' : + 'Expected spy ' + actual.and.identity() + ' to have been called.'; + + return result; + } + }; + } + + return toHaveBeenCalled; +}; + +getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { + + function toHaveBeenCalledWith(util, customEqualityTesters) { + return { + compare: function() { + var args = Array.prototype.slice.call(arguments, 0), + actual = args[0], + expectedArgs = args.slice(1), + result = { pass: false }; + + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } + + if (!actual.calls.any()) { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; + return result; + } + + if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + result.pass = true; + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; + } else { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; + } + + return result; + } + }; + } + + return toHaveBeenCalledWith; +}; + +getJasmineRequireObj().toMatch = function(j$) { + + function toMatch() { + return { + compare: function(actual, expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } + + var regexp = new RegExp(expected); + + return { + pass: regexp.test(actual) + }; + } + }; + } + + return toMatch; +}; + +getJasmineRequireObj().toThrow = function(j$) { + + function toThrow(util) { + return { + compare: function(actual, expected) { + var result = { pass: false }, + threw = false, + thrown; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + result.message = 'Expected function to throw an exception.'; + return result; + } + + if (arguments.length == 1) { + result.pass = true; + result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; + + return result; + } + + if (util.equals(thrown, expected)) { + result.pass = true; + result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; + } else { + result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; + } + + return result; + } + }; + } + + return toThrow; +}; + +getJasmineRequireObj().toThrowError = function(j$) { + function toThrowError (util) { + return { + compare: function(actual) { + var threw = false, + pass = {pass: true}, + fail = {pass: false}, + thrown; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } + + var errorMatcher = getMatcher.apply(null, arguments); + + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } + + if (!threw) { + fail.message = 'Expected function to throw an Error.'; + return fail; + } + + if (!(thrown instanceof Error)) { + fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; + return fail; + } + + if (errorMatcher.hasNoSpecifics()) { + pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; + return pass; + } + + if (errorMatcher.matches(thrown)) { + pass.message = function() { + return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; + }; + return pass; + } else { + fail.message = function() { + return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + + ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; + }; + return fail; + } + } + }; + + function getMatcher() { + var expected = null, + errorType = null; + + if (arguments.length == 2) { + expected = arguments[1]; + if (isAnErrorType(expected)) { + errorType = expected; + expected = null; + } + } else if (arguments.length > 2) { + errorType = arguments[1]; + expected = arguments[2]; + if (!isAnErrorType(errorType)) { + throw new Error('Expected error type is not an Error.'); + } + } + + if (expected && !isStringOrRegExp(expected)) { + if (errorType) { + throw new Error('Expected error message is not a string or RegExp.'); + } else { + throw new Error('Expected is not an Error, string, or RegExp.'); + } + } + + function messageMatch(message) { + if (typeof expected == 'string') { + return expected == message; + } else { + return expected.test(message); + } + } + + return { + errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', + thrownDescription: function(thrown) { + var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', + thrownMessage = ''; + + if (expected) { + thrownMessage = ' with message ' + j$.pp(thrown.message); + } + + return thrownName + thrownMessage; + }, + messageDescription: function() { + if (expected === null) { + return ''; + } else if (expected instanceof RegExp) { + return ' with a message matching ' + j$.pp(expected); + } else { + return ' with message ' + j$.pp(expected); + } + }, + hasNoSpecifics: function() { + return expected === null && errorType === null; + }, + matches: function(error) { + return (errorType === null || error instanceof errorType) && + (expected === null || messageMatch(error.message)); + } + }; + } + + function isStringOrRegExp(potential) { + return potential instanceof RegExp || (typeof potential == 'string'); + } + + function isAnErrorType(type) { + if (typeof type !== 'function') { + return false; + } + + var Surrogate = function() {}; + Surrogate.prototype = type.prototype; + return (new Surrogate()) instanceof Error; + } + } + + return toThrowError; +}; + +getJasmineRequireObj().interface = function(jasmine, env) { + var jasmineInterface = { + describe: function(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, + + xdescribe: function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, + + fdescribe: function(description, specDefinitions) { + return env.fdescribe(description, specDefinitions); + }, + + it: function() { + return env.it.apply(env, arguments); + }, + + xit: function() { + return env.xit.apply(env, arguments); + }, + + fit: function() { + return env.fit.apply(env, arguments); + }, + + beforeEach: function() { + return env.beforeEach.apply(env, arguments); + }, + + afterEach: function() { + return env.afterEach.apply(env, arguments); + }, + + beforeAll: function() { + return env.beforeAll.apply(env, arguments); + }, + + afterAll: function() { + return env.afterAll.apply(env, arguments); + }, + + expect: function(actual) { + return env.expect(actual); + }, + + pending: function() { + return env.pending.apply(env, arguments); + }, + + fail: function() { + return env.fail.apply(env, arguments); + }, + + spyOn: function(obj, methodName) { + return env.spyOn(obj, methodName); + }, + + jsApiReporter: new jasmine.JsApiReporter({ + timer: new jasmine.Timer() + }), + + jasmine: jasmine + }; + + jasmine.addCustomEqualityTester = function(tester) { + env.addCustomEqualityTester(tester); + }; + + jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); + }; + + jasmine.clock = function() { + return env.clock; + }; + + return jasmineInterface; +}; + +getJasmineRequireObj().version = function() { + return '2.3.4'; +}; diff --git a/src/assets/js/vendor/vendor.js b/src/assets/js/vendor/vendor.js index 061ff20..1384d06 100644 --- a/src/assets/js/vendor/vendor.js +++ b/src/assets/js/vendor/vendor.js @@ -38118,754 +38118,3224 @@ * (c) 2010-2015 Google, Inc. http://angularjs.org * License: MIT */ -(function(window, angular, undefined) {'use strict'; +(function (window, angular, undefined) { - var $resourceMinErr = angular.$$minErr('$resource'); + 'use strict'; -// Helper functions and regex to lookup a dotted path on an object -// stopping at undefined/null. The path must be composed of ASCII -// identifiers (just like $parse) - var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/; + /** + * @ngdoc object + * @name angular.mock + * @description + * + * Namespace from 'angular-mocks.js' which contains testing related code. + */ + angular.mock = {}; - function isValidDottedPath(path) { - return (path != null && path !== '' && path !== 'hasOwnProperty' && - MEMBER_NAME_REGEX.test('.' + path)); - } + /** + * ! This is a private undocumented service ! + * + * @name $browser + * + * @description + * This service is a mock implementation of {@link ng.$browser}. It provides fake + * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, + * cookies, etc... + * + * The api of this service is the same as that of the real {@link ng.$browser $browser}, except + * that there are several helper methods available which can be used in tests. + */ + angular.mock.$BrowserProvider = function () { + this.$get = function () { + return new angular.mock.$Browser(); + }; + }; - function lookupDottedPath(obj, path) { - if (!isValidDottedPath(path)) { - throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); - } - var keys = path.split('.'); - for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) { - var key = keys[i]; - obj = (obj !== null) ? obj[key] : undefined; - } - return obj; - } + angular.mock.$Browser = function () { + var self = this; - /** - * Create a shallow copy of an object and clear other fields from the destination - */ - function shallowClearAndCopy(src, dst) { - dst = dst || {}; + this.isMock = true; + self.$$url = "http://server/"; + self.$$lastUrl = self.$$url; // used by url polling fn + self.pollFns = []; - angular.forEach(dst, function(value, key) { - delete dst[key]; - }); + // TODO(vojta): remove this temporary api + self.$$completeOutstandingRequest = angular.noop; + self.$$incOutstandingRequestCount = angular.noop; - for (var key in src) { - if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - return dst; - } + // register url polling fn - /** - * @ngdoc module - * @name ngResource - * @description - * - * # ngResource - * - * The `ngResource` module provides interaction support with RESTful services - * via the $resource service. - * - * - *
- * - * See {@link ngResource.$resource `$resource`} for usage. - */ + self.onUrlChange = function (listener) { + self.pollFns.push( + function () { + if (self.$$lastUrl !== self.$$url || self.$$state !== self.$$lastState) { + self.$$lastUrl = self.$$url; + self.$$lastState = self.$$state; + listener(self.$$url, self.$$state); + } + } + ); - /** - * @ngdoc service - * @name $resource - * @requires $http - * - * @description - * A factory which creates a resource object that lets you interact with - * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. - * - * The returned resource object has action methods which provide high-level behaviors without - * the need to interact with the low level {@link ng.$http $http} service. - * - * Requires the {@link ngResource `ngResource`} module to be installed. - * - * By default, trailing slashes will be stripped from the calculated URLs, - * which can pose problems with server backends that do not expect that - * behavior. This can be disabled by configuring the `$resourceProvider` like - * this: - * - * ```js - app.config(['$resourceProvider', function($resourceProvider) { - // Don't strip trailing slashes from calculated URLs - $resourceProvider.defaults.stripTrailingSlashes = false; - }]); - * ``` - * - * @param {string} url A parameterized URL template with parameters prefixed by `:` as in - * `/user/:username`. If you are using a URL with a port number (e.g. - * `http://example.com:8080/api`), it will be respected. - * - * If you are using a url with a suffix, just add the suffix, like this: - * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` - * or even `$resource('http://example.com/resource/:resource_id.:format')` - * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be - * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you - * can escape it with `/\.`. - * - * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in - * `actions` methods. If any of the parameter value is a function, it will be executed every time - * when a param value needs to be obtained for a request (unless the param was overridden). - * - * Each key value in the parameter object is first bound to url template if present and then any - * excess keys are appended to the url search query after the `?`. - * - * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in - * URL `/path/greet?salutation=Hello`. - * - * If the parameter value is prefixed with `@` then the value for that parameter will be extracted - * from the corresponding property on the `data` object (provided when calling an action method). For - * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam` - * will be `data.someProp`. - * - * @param {Object.=} actions Hash with declaration of custom actions that should extend - * the default set of resource actions. The declaration should be created in the format of {@link - * ng.$http#usage $http.config}: - * - * {action1: {method:?, params:?, isArray:?, headers:?, ...}, - * action2: {method:?, params:?, isArray:?, headers:?, ...}, - * ...} - * - * Where: - * - * - **`action`** – {string} – The name of action. This name becomes the name of the method on - * your resource object. - * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, - * `DELETE`, `JSONP`, etc). - * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of - * the parameter value is a function, it will be executed every time when a param value needs to - * be obtained for a request (unless the param was overridden). - * - **`url`** – {string} – action specific `url` override. The url templating is supported just - * like for the resource-level urls. - * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, - * see `returns` section. - * - **`transformRequest`** – - * `{function(data, headersGetter)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * request body and headers and returns its transformed (typically serialized) version. - * By default, transformRequest will contain one function that checks if the request data is - * an object and serializes to using `angular.toJson`. To prevent this behavior, set - * `transformRequest` to an empty array: `transformRequest: []` - * - **`transformResponse`** – - * `{function(data, headersGetter)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * response body and headers and returns its transformed (typically deserialized) version. - * By default, transformResponse will contain one function that checks if the response looks like - * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set - * `transformResponse` to an empty array: `transformResponse: []` - * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the - * GET request, otherwise if a cache instance built with - * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for - * caching. - * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that - * should abort the request when resolved. - * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the - * XHR object. See - * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) - * for more information. - * - **`responseType`** - `{string}` - see - * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). - * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - - * `response` and `responseError`. Both `response` and `responseError` interceptors get called - * with `http response` object. See {@link ng.$http $http interceptors}. - * - * @param {Object} options Hash with custom settings that should extend the - * default `$resourceProvider` behavior. The only supported option is - * - * Where: - * - * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing - * slashes from any calculated URL will be stripped. (Defaults to true.) - * - * @returns {Object} A resource "class" object with methods for the default set of resource actions - * optionally extended with custom `actions`. The default set contains these actions: - * ```js - * { 'get': {method:'GET'}, - * 'save': {method:'POST'}, - * 'query': {method:'GET', isArray:true}, - * 'remove': {method:'DELETE'}, - * 'delete': {method:'DELETE'} }; - * ``` - * - * Calling these methods invoke an {@link ng.$http} with the specified http method, - * destination and parameters. When the data is returned from the server then the object is an - * instance of the resource class. The actions `save`, `remove` and `delete` are available on it - * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, - * read, update, delete) on server-side data like this: - * ```js - * var User = $resource('/user/:userId', {userId:'@id'}); - * var user = User.get({userId:123}, function() { - * user.abc = true; - * user.$save(); - * }); - * ``` - * - * It is important to realize that invoking a $resource object method immediately returns an - * empty reference (object or array depending on `isArray`). Once the data is returned from the - * server the existing reference is populated with the actual data. This is a useful trick since - * usually the resource is assigned to a model which is then rendered by the view. Having an empty - * object results in no rendering, once the data arrives from the server then the object is - * populated with the data and the view automatically re-renders itself showing the new data. This - * means that in most cases one never has to write a callback function for the action methods. - * - * The action methods on the class object or instance object can be invoked with the following - * parameters: - * - * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` - * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` - * - non-GET instance actions: `instance.$action([parameters], [success], [error])` - * - * - * Success callback is called with (value, responseHeaders) arguments, where the value is - * the populated resource instance or collection object. The error callback is called - * with (httpResponse) argument. - * - * Class actions return empty instance (with additional properties below). - * Instance actions return promise of the action. - * - * The Resource instances and collection have these additional properties: - * - * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this - * instance or collection. - * - * On success, the promise is resolved with the same resource instance or collection object, - * updated with data from server. This makes it easy to use in - * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view - * rendering until the resource(s) are loaded. - * - * On failure, the promise is resolved with the {@link ng.$http http response} object, without - * the `resource` property. - * - * If an interceptor object was provided, the promise will instead be resolved with the value - * returned by the interceptor. - * - * - `$resolved`: `true` after first server interaction is completed (either with success or - * rejection), `false` before that. Knowing if the Resource has been resolved is useful in - * data-binding. - * - * @example - * - * # Credit card resource - * - * ```js - // Define CreditCard class - var CreditCard = $resource('/user/:userId/card/:cardId', - {userId:123, cardId:'@id'}, { - charge: {method:'POST', params:{charge:true}} - }); + return listener; + }; - // We can retrieve a collection from the server - var cards = CreditCard.query(function() { - // GET: /user/123/card - // server returns: [ {id:456, number:'1234', name:'Smith'} ]; + self.$$applicationDestroyed = angular.noop; + self.$$checkUrlChange = angular.noop; - var card = cards[0]; - // each item is an instance of CreditCard - expect(card instanceof CreditCard).toEqual(true); - card.name = "J. Smith"; - // non GET methods are mapped onto the instances - card.$save(); - // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} - // server returns: {id:456, number:'1234', name: 'J. Smith'}; + self.deferredFns = []; + self.deferredNextId = 0; - // our custom method is mapped as well. - card.$charge({amount:9.99}); - // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} - }); + self.defer = function (fn, delay) { + delay = delay || 0; + self.deferredFns.push({ time: (self.defer.now + delay), fn: fn, id: self.deferredNextId }); + self.deferredFns.sort(function (a, b) { return a.time - b.time; }); + return self.deferredNextId++; + }; - // we can create an instance as well - var newCard = new CreditCard({number:'0123'}); - newCard.name = "Mike Smith"; - newCard.$save(); - // POST: /user/123/card {number:'0123', name:'Mike Smith'} - // server returns: {id:789, number:'0123', name: 'Mike Smith'}; - expect(newCard.id).toEqual(789); - * ``` - * - * The object returned from this function execution is a resource "class" which has "static" method - * for each action in the definition. - * - * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and - * `headers`. - * When the data is returned from the server then the object is an instance of the resource type and - * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD - * operations (create, read, update, delete) on server-side data. - ```js - var User = $resource('/user/:userId', {userId:'@id'}); - User.get({userId:123}, function(user) { - user.abc = true; - user.$save(); - }); - ``` - * - * It's worth noting that the success callback for `get`, `query` and other methods gets passed - * in the response that came from the server as well as $http header getter function, so one - * could rewrite the above example and get access to http headers as: - * - ```js - var User = $resource('/user/:userId', {userId:'@id'}); - User.get({userId:123}, function(u, getResponseHeaders){ - u.abc = true; - u.$save(function(u, putResponseHeaders) { - //u => saved user object - //putResponseHeaders => $http header getter - }); - }); - ``` - * - * You can also access the raw `$http` promise via the `$promise` property on the object returned - * - ``` - var User = $resource('/user/:userId', {userId:'@id'}); - User.get({userId:123}) - .$promise.then(function(user) { - $scope.user = user; - }); - ``` + /** + * @name $browser#defer.now + * + * @description + * Current milliseconds mock time. + */ + self.defer.now = 0; - * # Creating a custom 'PUT' request - * In this example we create a custom method on our resource to make a PUT request - * ```js - * var app = angular.module('app', ['ngResource', 'ngRoute']); - * - * // Some APIs expect a PUT request in the format URL/object/ID - * // Here we are creating an 'update' method - * app.factory('Notes', ['$resource', function($resource) { - * return $resource('/notes/:id', null, - * { - * 'update': { method:'PUT' } - * }); - * }]); - * - * // In our controller we get the ID from the URL using ngRoute and $routeParams - * // We pass in $routeParams and our Notes factory along with $scope - * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', - function($scope, $routeParams, Notes) { - * // First get a note object from the factory - * var note = Notes.get({ id:$routeParams.id }); - * $id = note.id; - * - * // Now call update passing in the ID first then the object you are updating - * Notes.update({ id:$id }, note); - * - * // This will PUT /notes/ID with the note object in the request payload - * }]); - * ``` - */ - angular.module('ngResource', ['ng']). - provider('$resource', function() { - var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/; - var provider = this; - this.defaults = { - // Strip slashes by default - stripTrailingSlashes: true, + self.defer.cancel = function (deferId) { + var fnIndex; - // Default actions configuration - actions: { - 'get': {method: 'GET'}, - 'save': {method: 'POST'}, - 'query': {method: 'GET', isArray: true}, - 'remove': {method: 'DELETE'}, - 'delete': {method: 'DELETE'} - } - }; + angular.forEach(self.deferredFns, function (fn, index) { + if (fn.id === deferId) fnIndex = index; + }); - this.$get = ['$http', '$q', function($http, $q) { + if (angular.isDefined(fnIndex)) { + self.deferredFns.splice(fnIndex, 1); + return true; + } - var noop = angular.noop, - forEach = angular.forEach, - extend = angular.extend, - copy = angular.copy, - isFunction = angular.isFunction; + return false; + }; - /** - * We need our custom method because encodeURIComponent is too aggressive and doesn't follow - * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set - * (pchar) allowed in path segments: - * segment = *pchar - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * pct-encoded = "%" HEXDIG HEXDIG - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ - function encodeUriSegment(val) { - return encodeUriQuery(val, true). - replace(/%26/gi, '&'). - replace(/%3D/gi, '='). - replace(/%2B/gi, '+'); - } + /** + * @name $browser#defer.flush + * + * @description + * Flushes all pending requests and executes the defer callbacks. + * + * @param {number=} number of milliseconds to flush. See {@link #defer.now} + */ + self.defer.flush = function (delay) { + if (angular.isDefined(delay)) { + self.defer.now += delay; + } else { + if (self.deferredFns.length) { + self.defer.now = self.deferredFns[self.deferredFns.length - 1].time; + } else { + throw new Error('No deferred tasks to be flushed'); + } + } - /** - * This method is intended for encoding *key* or *value* parts of query component. We need a - * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't - * have to be encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * pct-encoded = "%" HEXDIG HEXDIG - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ - function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); - } + while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { + self.deferredFns.shift().fn(); + } + }; - function Route(template, defaults) { - this.template = template; - this.defaults = extend({}, provider.defaults, defaults); - this.urlParams = {}; - } + self.$$baseHref = '/'; + self.baseHref = function () { + return this.$$baseHref; + }; + }; + angular.mock.$Browser.prototype = { + + /** + * @name $browser#poll + * + * @description + * run all fns in pollFns + */ + poll: function poll() { + angular.forEach(this.pollFns, function (pollFn) { + pollFn(); + }); + }, - Route.prototype = { - setUrlParams: function(config, params, actionUrl) { - var self = this, - url = actionUrl || self.template, - val, - encodedVal, - protocolAndDomain = ''; + url: function (url, replace, state) { + if (angular.isUndefined(state)) { + state = null; + } + if (url) { + this.$$url = url; + // Native pushState serializes & copies the object; simulate it. + this.$$state = angular.copy(state); + return this; + } - var urlParams = self.urlParams = {}; - forEach(url.split(/\W/), function(param) { - if (param === 'hasOwnProperty') { - throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); - } - if (!(new RegExp("^\\d+$").test(param)) && param && - (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { - urlParams[param] = true; - } - }); - url = url.replace(/\\:/g, ':'); - url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) { - protocolAndDomain = match; - return ''; - }); + return this.$$url; + }, - params = params || {}; - forEach(self.urlParams, function(_, urlParam) { - val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; - if (angular.isDefined(val) && val !== null) { - encodedVal = encodeUriSegment(val); - url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { - return encodedVal + p1; - }); - } else { - url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, - leadingSlashes, tail) { - if (tail.charAt(0) == '/') { - return tail; - } else { - return leadingSlashes + tail; - } - }); - } - }); + state: function () { + return this.$$state; + }, - // strip trailing slashes and set the url (unless this behavior is specifically disabled) - if (self.defaults.stripTrailingSlashes) { - url = url.replace(/\/+$/, '') || '/'; - } + notifyWhenNoOutstandingRequests: function (fn) { + fn(); + } + }; - // then replace collapse `/.` if found in the last URL path segment before the query - // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` - url = url.replace(/\/\.(?=\w+($|\?))/, '.'); - // replace escaped `/\.` with `/.` - config.url = protocolAndDomain + url.replace(/\/\\\./, '/.'); + /** + * @ngdoc provider + * @name $exceptionHandlerProvider + * + * @description + * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors + * passed to the `$exceptionHandler`. + */ + + /** + * @ngdoc service + * @name $exceptionHandler + * + * @description + * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed + * to it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration + * information. + * + * + * ```js + * describe('$exceptionHandlerProvider', function() { + * + * it('should capture log messages and exceptions', function() { + * + * module(function($exceptionHandlerProvider) { + * $exceptionHandlerProvider.mode('log'); + * }); + * + * inject(function($log, $exceptionHandler, $timeout) { + * $timeout(function() { $log.log(1); }); + * $timeout(function() { $log.log(2); throw 'banana peel'; }); + * $timeout(function() { $log.log(3); }); + * expect($exceptionHandler.errors).toEqual([]); + * expect($log.assertEmpty()); + * $timeout.flush(); + * expect($exceptionHandler.errors).toEqual(['banana peel']); + * expect($log.log.logs).toEqual([[1], [2], [3]]); + * }); + * }); + * }); + * ``` + */ + + angular.mock.$ExceptionHandlerProvider = function () { + var handler; + + /** + * @ngdoc method + * @name $exceptionHandlerProvider#mode + * + * @description + * Sets the logging mode. + * + * @param {string} mode Mode of operation, defaults to `rethrow`. + * + * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` + * mode stores an array of errors in `$exceptionHandler.errors`, to allow later + * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and + * {@link ngMock.$log#reset reset()} + * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there + * is a bug in the application or test, so this mock will make these tests fail. + * For any implementations that expect exceptions to be thrown, the `rethrow` mode + * will also maintain a log of thrown errors. + */ + this.mode = function (mode) { + + switch (mode) { + case 'log': + case 'rethrow': + var errors = []; + handler = function (e) { + if (arguments.length == 1) { + errors.push(e); + } else { + errors.push([].slice.call(arguments, 0)); + } + if (mode === "rethrow") { + throw e; + } + }; + handler.errors = errors; + break; + default: + throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); + } + }; - // set params - delegate param encoding to $http - forEach(params, function(value, key) { - if (!self.urlParams[key]) { - config.params = config.params || {}; - config.params[key] = value; - } - }); - } - }; + this.$get = function () { + return handler; + }; + this.mode('rethrow'); + }; - function resourceFactory(url, paramDefaults, actions, options) { - var route = new Route(url, options); - actions = extend({}, provider.defaults.actions, actions); + /** + * @ngdoc service + * @name $log + * + * @description + * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays + * (one array per logging level). These arrays are exposed as `logs` property of each of the + * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. + * + */ + angular.mock.$LogProvider = function () { + var debug = true; - function extractParams(data, actionParams) { - var ids = {}; - actionParams = extend({}, paramDefaults, actionParams); - forEach(actionParams, function(value, key) { - if (isFunction(value)) { value = value(); } - ids[key] = value && value.charAt && value.charAt(0) == '@' ? - lookupDottedPath(data, value.substr(1)) : value; - }); - return ids; - } + function concat(array1, array2, index) { + return array1.concat(Array.prototype.slice.call(array2, index)); + } - function defaultResponseInterceptor(response) { - return response.resource; - } + this.debugEnabled = function (flag) { + if (angular.isDefined(flag)) { + debug = flag; + return this; + } else { + return debug; + } + }; - function Resource(value) { - shallowClearAndCopy(value || {}, this); - } + this.$get = function () { + var $log = { + log: function () { $log.log.logs.push(concat([], arguments, 0)); }, + warn: function () { $log.warn.logs.push(concat([], arguments, 0)); }, + info: function () { $log.info.logs.push(concat([], arguments, 0)); }, + error: function () { $log.error.logs.push(concat([], arguments, 0)); }, + debug: function () { + if (debug) { + $log.debug.logs.push(concat([], arguments, 0)); + } + } + }; - Resource.prototype.toJSON = function() { - var data = extend({}, this); - delete data.$promise; - delete data.$resolved; - return data; - }; + /** + * @ngdoc method + * @name $log#reset + * + * @description + * Reset all of the logging arrays to empty. + */ + $log.reset = function () { + /** + * @ngdoc property + * @name $log#log.logs + * + * @description + * Array of messages logged using {@link ng.$log#log `log()`}. + * + * @example + * ```js + * $log.log('Some Log'); + * var first = $log.log.logs.unshift(); + * ``` + */ + $log.log.logs = []; + /** + * @ngdoc property + * @name $log#info.logs + * + * @description + * Array of messages logged using {@link ng.$log#info `info()`}. + * + * @example + * ```js + * $log.info('Some Info'); + * var first = $log.info.logs.unshift(); + * ``` + */ + $log.info.logs = []; + /** + * @ngdoc property + * @name $log#warn.logs + * + * @description + * Array of messages logged using {@link ng.$log#warn `warn()`}. + * + * @example + * ```js + * $log.warn('Some Warning'); + * var first = $log.warn.logs.unshift(); + * ``` + */ + $log.warn.logs = []; + /** + * @ngdoc property + * @name $log#error.logs + * + * @description + * Array of messages logged using {@link ng.$log#error `error()`}. + * + * @example + * ```js + * $log.error('Some Error'); + * var first = $log.error.logs.unshift(); + * ``` + */ + $log.error.logs = []; + /** + * @ngdoc property + * @name $log#debug.logs + * + * @description + * Array of messages logged using {@link ng.$log#debug `debug()`}. + * + * @example + * ```js + * $log.debug('Some Error'); + * var first = $log.debug.logs.unshift(); + * ``` + */ + $log.debug.logs = []; + }; - forEach(actions, function(action, name) { - var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); + /** + * @ngdoc method + * @name $log#assertEmpty + * + * @description + * Assert that all of the logging methods have no logged messages. If any messages are present, + * an exception is thrown. + */ + $log.assertEmpty = function () { + var errors = []; + angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function (logLevel) { + angular.forEach($log[logLevel].logs, function (log) { + angular.forEach(log, function (logItem) { + errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + + (logItem.stack || '')); + }); + }); + }); + if (errors.length) { + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " + + "an expected log message was not checked and removed:"); + errors.push(''); + throw new Error(errors.join('\n---------\n')); + } + }; - Resource[name] = function(a1, a2, a3, a4) { - var params = {}, data, success, error; + $log.reset(); + return $log; + }; + }; - /* jshint -W086 */ /* (purposefully fall through case statements) */ - switch (arguments.length) { - case 4: - error = a4; - success = a3; - //fallthrough - case 3: - case 2: - if (isFunction(a2)) { - if (isFunction(a1)) { - success = a1; - error = a2; - break; - } - success = a2; - error = a3; - //fallthrough - } else { - params = a1; - data = a2; - success = a3; - break; - } - case 1: - if (isFunction(a1)) success = a1; - else if (hasBody) data = a1; - else params = a1; - break; - case 0: break; - default: - throw $resourceMinErr('badargs', - "Expected up to 4 arguments [params, data, success, error], got {0} arguments", - arguments.length); - } - /* jshint +W086 */ /* (purposefully fall through case statements) */ + /** + * @ngdoc service + * @name $interval + * + * @description + * Mock implementation of the $interval service. + * + * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to + * move forward by `millis` milliseconds and trigger any functions scheduled to run in that + * time. + * + * @param {function()} fn A function that should be called repeatedly. + * @param {number} delay Number of milliseconds between each function call. + * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat + * indefinitely. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @param {...*=} Pass additional parameters to the executed function. + * @returns {promise} A promise which will be notified on each iteration. + */ + angular.mock.$IntervalProvider = function () { + this.$get = ['$browser', '$rootScope', '$q', '$$q', + function ($browser, $rootScope, $q, $$q) { + var repeatFns = [], + nextRepeatId = 0, + now = 0; + + var $interval = function (fn, delay, count, invokeApply) { + var hasParams = arguments.length > 4, + args = hasParams ? Array.prototype.slice.call(arguments, 4) : [], + iteration = 0, + skipApply = (angular.isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), + promise = deferred.promise; + + count = (angular.isDefined(count)) ? count : 0; + promise.then(null, null, (!hasParams) ? fn : function () { + fn.apply(null, args); + }); + + promise.$$intervalId = nextRepeatId; + + function tick() { + deferred.notify(iteration++); + + if (count > 0 && iteration >= count) { + var fnIndex; + deferred.resolve(iteration); + + angular.forEach(repeatFns, function (fn, index) { + if (fn.id === promise.$$intervalId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + repeatFns.splice(fnIndex, 1); + } + } + + if (skipApply) { + $browser.defer.flush(); + } else { + $rootScope.$apply(); + } + } + + repeatFns.push({ + nextTime: (now + delay), + delay: delay, + fn: tick, + id: nextRepeatId, + deferred: deferred + }); + repeatFns.sort(function (a, b) { return a.nextTime - b.nextTime; }); + + nextRepeatId++; + return promise; + }; + /** + * @ngdoc method + * @name $interval#cancel + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {promise} promise A promise from calling the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully cancelled. + */ + $interval.cancel = function (promise) { + if (!promise) return false; + var fnIndex; + + angular.forEach(repeatFns, function (fn, index) { + if (fn.id === promise.$$intervalId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + repeatFns[fnIndex].deferred.reject('canceled'); + repeatFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + /** + * @ngdoc method + * @name $interval#flush + * @description + * + * Runs interval tasks scheduled to be run in the next `millis` milliseconds. + * + * @param {number=} millis maximum timeout amount to flush up until. + * + * @return {number} The amount of time moved forward. + */ + $interval.flush = function (millis) { + now += millis; + while (repeatFns.length && repeatFns[0].nextTime <= now) { + var task = repeatFns[0]; + task.fn(); + task.nextTime += task.delay; + repeatFns.sort(function (a, b) { return a.nextTime - b.nextTime; }); + } + return millis; + }; + + return $interval; + }]; + }; + + + /* jshint -W101 */ + /* The R_ISO8061_STR regex is never going to fit into the 100 char limit! + * This directive should go inside the anonymous function but a bug in JSHint means that it would + * not be enacted early enough to prevent the warning. + */ + var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; + + function jsonStringToDate(string) { + var match; + if (match = string.match(R_ISO8061_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0; + if (match[9]) { + tzHour = toInt(match[9] + match[10]); + tzMin = toInt(match[9] + match[11]); + } + date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); + date.setUTCHours(toInt(match[4] || 0) - tzHour, + toInt(match[5] || 0) - tzMin, + toInt(match[6] || 0), + toInt(match[7] || 0)); + return date; + } + return string; + } + + function toInt(str) { + return parseInt(str, 10); + } + + function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while (num.length < digits) num = '0' + num; + if (trim) { + num = num.substr(num.length - digits); + } + return neg + num; + } - var isInstanceCall = this instanceof Resource; - var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); - var httpConfig = {}; - var responseInterceptor = action.interceptor && action.interceptor.response || - defaultResponseInterceptor; - var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || - undefined; - forEach(action, function(value, key) { - if (key != 'params' && key != 'isArray' && key != 'interceptor') { - httpConfig[key] = copy(value); - } - }); + /** + * @ngdoc type + * @name angular.mock.TzDate + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. + * + * Mock of the Date type which has its timezone specified via constructor arg. + * + * The main purpose is to create Date-like instances with timezone fixed to the specified timezone + * offset, so that we can test code that depends on local timezone settings without dependency on + * the time zone settings of the machine where the code is running. + * + * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) + * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* + * + * @example + * !!!! WARNING !!!!! + * This is not a complete Date object so only methods that were implemented can be called safely. + * To make matters worse, TzDate instances inherit stuff from Date via a prototype. + * + * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is + * incomplete we might be missing some non-standard methods. This can result in errors like: + * "Date.prototype.foo called on incompatible Object". + * + * ```js + * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); + * newYearInBratislava.getTimezoneOffset() => -60; + * newYearInBratislava.getFullYear() => 2010; + * newYearInBratislava.getMonth() => 0; + * newYearInBratislava.getDate() => 1; + * newYearInBratislava.getHours() => 0; + * newYearInBratislava.getMinutes() => 0; + * newYearInBratislava.getSeconds() => 0; + * ``` + * + */ + angular.mock.TzDate = function (offset, timestamp) { + var self = new Date(0); + if (angular.isString(timestamp)) { + var tsStr = timestamp; + + self.origDate = jsonStringToDate(timestamp); + + timestamp = self.origDate.getTime(); + if (isNaN(timestamp)) { + throw { + name: "Illegal Argument", + message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" + }; + } + } else { + self.origDate = new Date(timestamp); + } - if (hasBody) httpConfig.data = data; - route.setUrlParams(httpConfig, - extend({}, extractParams(data, action.params || {}), params), - action.url); + var localOffset = new Date(timestamp).getTimezoneOffset(); + self.offsetDiff = localOffset * 60 * 1000 - offset * 1000 * 60 * 60; + self.date = new Date(timestamp + self.offsetDiff); - var promise = $http(httpConfig).then(function(response) { - var data = response.data, - promise = value.$promise; + self.getTime = function () { + return self.date.getTime() - self.offsetDiff; + }; - if (data) { - // Need to convert action.isArray to boolean in case it is undefined - // jshint -W018 - if (angular.isArray(data) !== (!!action.isArray)) { - throw $resourceMinErr('badcfg', - 'Error in resource configuration for action `{0}`. Expected response to ' + - 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object', - angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url); - } - // jshint +W018 - if (action.isArray) { - value.length = 0; - forEach(data, function(item) { - if (typeof item === "object") { - value.push(new Resource(item)); - } else { - // Valid JSON values may be string literals, and these should not be converted - // into objects. These items will not have access to the Resource prototype - // methods, but unfortunately there - value.push(item); - } - }); - } else { - shallowClearAndCopy(data, value); - value.$promise = promise; - } - } + self.toLocaleDateString = function () { + return self.date.toLocaleDateString(); + }; - value.$resolved = true; + self.getFullYear = function () { + return self.date.getFullYear(); + }; - response.resource = value; + self.getMonth = function () { + return self.date.getMonth(); + }; - return response; - }, function(response) { - value.$resolved = true; + self.getDate = function () { + return self.date.getDate(); + }; - (error || noop)(response); + self.getHours = function () { + return self.date.getHours(); + }; - return $q.reject(response); - }); + self.getMinutes = function () { + return self.date.getMinutes(); + }; - promise = promise.then( - function(response) { - var value = responseInterceptor(response); - (success || noop)(value, response.headers); - return value; - }, - responseErrorInterceptor); + self.getSeconds = function () { + return self.date.getSeconds(); + }; - if (!isInstanceCall) { - // we are creating instance / collection - // - set the initial promise - // - return the instance / collection - value.$promise = promise; - value.$resolved = false; + self.getMilliseconds = function () { + return self.date.getMilliseconds(); + }; - return value; - } + self.getTimezoneOffset = function () { + return offset * 60; + }; - // instance call - return promise; - }; + self.getUTCFullYear = function () { + return self.origDate.getUTCFullYear(); + }; + self.getUTCMonth = function () { + return self.origDate.getUTCMonth(); + }; - Resource.prototype['$' + name] = function(params, success, error) { - if (isFunction(params)) { - error = success; success = params; params = {}; - } - var result = Resource[name].call(this, params, this, success, error); - return result.$promise || result; - }; - }); + self.getUTCDate = function () { + return self.origDate.getUTCDate(); + }; - Resource.bind = function(additionalParamDefaults) { - return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); - }; + self.getUTCHours = function () { + return self.origDate.getUTCHours(); + }; - return Resource; - } + self.getUTCMinutes = function () { + return self.origDate.getUTCMinutes(); + }; - return resourceFactory; - }]; - }); + self.getUTCSeconds = function () { + return self.origDate.getUTCSeconds(); + }; + self.getUTCMilliseconds = function () { + return self.origDate.getUTCMilliseconds(); + }; -})(window, window.angular); + self.getDay = function () { + return self.date.getDay(); + }; -/** - * @license AngularJS v1.4.7 - * (c) 2010-2015 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; + // provide this method only on browsers that already have it + if (self.toISOString) { + self.toISOString = function () { + return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + + padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + + padNumber(self.origDate.getUTCDate(), 2) + 'T' + + padNumber(self.origDate.getUTCHours(), 2) + ':' + + padNumber(self.origDate.getUTCMinutes(), 2) + ':' + + padNumber(self.origDate.getUTCSeconds(), 2) + '.' + + padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'; + }; + } - /** - * @ngdoc module - * @name ngRoute - * @description - * - * # ngRoute - * - * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. - * - * ## Example - * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. - * - * - *
- */ - /* global -ngRouteModule */ - var ngRouteModule = angular.module('ngRoute', ['ng']). - provider('$route', $RouteProvider), - $routeMinErr = angular.$$minErr('ngRoute'); + //hide all methods not implemented in this mock that the Date prototype exposes + var unimplementedMethods = ['getUTCDay', + 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', + 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', + 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', + 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', + 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; + + angular.forEach(unimplementedMethods, function (methodName) { + self[methodName] = function () { + throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock"); + }; + }); - /** - * @ngdoc provider - * @name $routeProvider - * - * @description - * - * Used for configuring routes. - * - * ## Example - * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. - * - * ## Dependencies - * Requires the {@link ngRoute `ngRoute`} module to be installed. - */ - function $RouteProvider() { - function inherit(parent, extra) { - return angular.extend(Object.create(parent), extra); - } + return self; + }; - var routes = {}; + //make "tzDateInstance instanceof Date" return true + angular.mock.TzDate.prototype = Date.prototype; + /* jshint +W101 */ - /** - * @ngdoc method - * @name $routeProvider#when - * - * @param {string} path Route path (matched against `$location.path`). If `$location.path` - * contains redundant trailing slash or is missing one, the route will still match and the - * `$location.path` will be updated to add or drop the trailing slash to exactly match the - * route definition. - * - * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up - * to the next slash are matched and stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain named groups starting with a colon and ending with a star: - * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain optional named groups with a question mark: e.g.`:name?`. - * - * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match - * `/color/brown/largecode/code/with/slashes/edit` and extract: - * - * * `color: brown` - * * `largecode: code/with/slashes`. - * - * - * @param {Object} route Mapping information to be assigned to `$route.current` on route - * match. - * - * Object properties: - * + angular.mock.animate = angular.module('ngAnimateMock', ['ng']) + + .config(['$provide', function ($provide) { + + $provide.factory('$$forceReflow', function () { + function reflowFn() { + reflowFn.totalReflows++; + } + reflowFn.totalReflows = 0; + return reflowFn; + }); + + $provide.factory('$$animateAsyncRun', function () { + var queue = []; + var queueFn = function () { + return function (fn) { + queue.push(fn); + }; + }; + queueFn.flush = function () { + if (queue.length === 0) return false; + + for (var i = 0; i < queue.length; i++) { + queue[i](); + } + queue = []; + + return true; + }; + return queueFn; + }); + + $provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', + '$$forceReflow', '$$animateAsyncRun', '$rootScope', + function ($delegate, $timeout, $browser, $$rAF, + $$forceReflow, $$animateAsyncRun, $rootScope) { + var animate = { + queue: [], + cancel: $delegate.cancel, + on: $delegate.on, + off: $delegate.off, + pin: $delegate.pin, + get reflows() { + return $$forceReflow.totalReflows; + }, + enabled: $delegate.enabled, + flush: function () { + $rootScope.$digest(); + + var doNextRun, somethingFlushed = false; + do { + doNextRun = false; + + if ($$rAF.queue.length) { + $$rAF.flush(); + doNextRun = somethingFlushed = true; + } + + if ($$animateAsyncRun.flush()) { + doNextRun = somethingFlushed = true; + } + } while (doNextRun); + + if (!somethingFlushed) { + throw new Error('No pending animations ready to be closed or flushed'); + } + + $rootScope.$digest(); + } + }; + + angular.forEach( + ['animate', 'enter', 'leave', 'move', 'addClass', 'removeClass', 'setClass'], function (method) { + animate[method] = function () { + animate.queue.push({ + event: method, + element: arguments[0], + options: arguments[arguments.length - 1], + args: arguments + }); + return $delegate[method].apply($delegate, arguments); + }; + }); + + return animate; + }]); + + }]); + + + /** + * @ngdoc function + * @name angular.mock.dump + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available function. + * + * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for + * debugging. + * + * This method is also available on window, where it can be used to display objects on debug + * console. + * + * @param {*} object - any object to turn into string. + * @return {string} a serialized string of the argument + */ + angular.mock.dump = function (object) { + return serialize(object); + + function serialize(object) { + var out; + + if (angular.isElement(object)) { + object = angular.element(object); + out = angular.element('
'); + angular.forEach(object, function (element) { + out.append(angular.element(element).clone()); + }); + out = out.html(); + } else if (angular.isArray(object)) { + out = []; + angular.forEach(object, function (o) { + out.push(serialize(o)); + }); + out = '[ ' + out.join(', ') + ' ]'; + } else if (angular.isObject(object)) { + if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { + out = serializeScope(object); + } else if (object instanceof Error) { + out = object.stack || ('' + object.name + ': ' + object.message); + } else { + // TODO(i): this prevents methods being logged, + // we should have a better way to serialize objects + out = angular.toJson(object, true); + } + } else { + out = String(object); + } + + return out; + } + + function serializeScope(scope, offset) { + offset = offset || ' '; + var log = [offset + 'Scope(' + scope.$id + '): {']; + for (var key in scope) { + if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) { + log.push(' ' + key + ': ' + angular.toJson(scope[key])); + } + } + var child = scope.$$childHead; + while (child) { + log.push(serializeScope(child, offset + ' ')); + child = child.$$nextSibling; + } + log.push('}'); + return log.join('\n' + offset); + } + }; + + /** + * @ngdoc service + * @name $httpBackend + * @description + * Fake HTTP backend implementation suitable for unit testing applications that use the + * {@link ng.$http $http service}. + * + * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less + * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. + * + * During unit testing, we want our unit tests to run quickly and have no external dependencies so + * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or + * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is + * to verify whether a certain request has been sent or not, or alternatively just let the + * application make requests, respond with pre-trained responses and assert that the end result is + * what we expect it to be. + * + * This mock implementation can be used to respond with static or dynamic responses via the + * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). + * + * When an Angular application needs some data from a server, it calls the $http service, which + * sends the request to a real server using $httpBackend service. With dependency injection, it is + * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify + * the requests and respond with some testing data without sending a request to a real server. + * + * There are two ways to specify what test data should be returned as http responses by the mock + * backend when the code under test makes http requests: + * + * - `$httpBackend.expect` - specifies a request expectation + * - `$httpBackend.when` - specifies a backend definition + * + * + * # Request Expectations vs Backend Definitions + * + * Request expectations provide a way to make assertions about requests made by the application and + * to define responses for those requests. The test will fail if the expected requests are not made + * or they are made in the wrong order. + * + * Backend definitions allow you to define a fake backend for your application which doesn't assert + * if a particular request was made or not, it just returns a trained response if a request is made. + * The test will pass whether or not the request gets made during testing. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Request expectationsBackend definitions
Syntax.expect(...).respond(...).when(...).respond(...)
Typical usagestrict unit testsloose (black-box) unit testing
Fulfills multiple requestsNOYES
Order of requests mattersYESNO
Request requiredYESNO
Response requiredoptional (see below)YES
+ * + * In cases where both backend definitions and request expectations are specified during unit + * testing, the request expectations are evaluated first. + * + * If a request expectation has no response specified, the algorithm will search your backend + * definitions for an appropriate response. + * + * If a request didn't match any expectation or if the expectation doesn't have the response + * defined, the backend definitions are evaluated in sequential order to see if any of them match + * the request. The response from the first matched definition is returned. + * + * + * # Flushing HTTP requests + * + * The $httpBackend used in production always responds to requests asynchronously. If we preserved + * this behavior in unit testing, we'd have to create async unit tests, which are hard to write, + * to follow and to maintain. But neither can the testing mock respond synchronously; that would + * change the execution of the code under test. For this reason, the mock $httpBackend has a + * `flush()` method, which allows the test to explicitly flush pending requests. This preserves + * the async api of the backend, while allowing the test to execute synchronously. + * + * + * # Unit testing with mock $httpBackend + * The following code shows how to setup and use the mock backend when unit testing a controller. + * First we create the controller under test: + * + ```js + // The module code + angular + .module('MyApp', []) + .controller('MyController', MyController); + + // The controller code + function MyController($scope, $http) { + var authToken; + + $http.get('/auth.py').success(function(data, status, headers) { + authToken = headers('A-Token'); + $scope.user = data; + }); + + $scope.saveMessage = function(message) { + var headers = { 'Authorization': authToken }; + $scope.status = 'Saving...'; + + $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) { + $scope.status = ''; + }).error(function() { + $scope.status = 'Failed...'; + }); + }; + } + ``` + * + * Now we setup the mock backend and create the test specs: + * + ```js + // testing controller + describe('MyController', function() { + var $httpBackend, $rootScope, createController, authRequestHandler; + + // Set up the module + beforeEach(module('MyApp')); + + beforeEach(inject(function($injector) { + // Set up the mock http service responses + $httpBackend = $injector.get('$httpBackend'); + // backend definition common for all tests + authRequestHandler = $httpBackend.when('GET', '/auth.py') + .respond({userId: 'userX'}, {'A-Token': 'xxx'}); + + // Get hold of a scope (i.e. the root scope) + $rootScope = $injector.get('$rootScope'); + // The $controller service is used to create instances of controllers + var $controller = $injector.get('$controller'); + + createController = function() { + return $controller('MyController', {'$scope' : $rootScope }); + }; + })); + + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + + it('should fetch authentication token', function() { + $httpBackend.expectGET('/auth.py'); + var controller = createController(); + $httpBackend.flush(); + }); + + + it('should fail authentication', function() { + + // Notice how you can change the response even after it was set + authRequestHandler.respond(401, ''); + + $httpBackend.expectGET('/auth.py'); + var controller = createController(); + $httpBackend.flush(); + expect($rootScope.status).toBe('Failed...'); + }); + + + it('should send msg to server', function() { + var controller = createController(); + $httpBackend.flush(); + + // now you don’t care about the authentication, but + // the controller will still send the request and + // $httpBackend will respond without you having to + // specify the expectation and response for this request + + $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); + $rootScope.saveMessage('message content'); + expect($rootScope.status).toBe('Saving...'); + $httpBackend.flush(); + expect($rootScope.status).toBe(''); + }); + + + it('should send auth header', function() { + var controller = createController(); + $httpBackend.flush(); + + $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { + // check if the header was sent, if it wasn't the expectation won't + // match the request and the test will fail + return headers['Authorization'] == 'xxx'; + }).respond(201, ''); + + $rootScope.saveMessage('whatever'); + $httpBackend.flush(); + }); + }); + ``` + */ + angular.mock.$HttpBackendProvider = function () { + this.$get = ['$rootScope', '$timeout', createHttpBackendMock]; + }; + + /** + * General factory function for $httpBackend mock. + * Returns instance for unit testing (when no arguments specified): + * - passing through is disabled + * - auto flushing is disabled + * + * Returns instance for e2e testing (when `$delegate` and `$browser` specified): + * - passing through (delegating request to real backend) is enabled + * - auto flushing is enabled + * + * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) + * @param {Object=} $browser Auto-flushing enabled if specified + * @return {Object} Instance of $httpBackend mock + */ + function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { + var definitions = [], + expectations = [], + responses = [], + responsesPush = angular.bind(responses, responses.push), + copy = angular.copy; + + function createResponse(status, data, headers, statusText) { + if (angular.isFunction(status)) return status; + + return function () { + return angular.isNumber(status) + ? [status, data, headers, statusText] + : [200, status, data, headers]; + }; + } + + // TODO(vojta): change params to: method, url, data, headers, callback + function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) { + var xhr = new MockXhr(), + expectation = expectations[0], + wasExpected = false; + + function prettyPrint(data) { + return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) + ? data + : angular.toJson(data); + } + + function wrapResponse(wrapped) { + if (!$browser && timeout) { + timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout); + } + + return handleResponse; + + function handleResponse() { + var response = wrapped.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(), + copy(response[3] || '')); + } + + function handleTimeout() { + for (var i = 0, ii = responses.length; i < ii; i++) { + if (responses[i] === handleResponse) { + responses.splice(i, 1); + callback(-1, undefined, ''); + break; + } + } + } + } + + if (expectation && expectation.match(method, url)) { + if (!expectation.matchData(data)) { + throw new Error('Expected ' + expectation + ' with different data\n' + + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); + } + + if (!expectation.matchHeaders(headers)) { + throw new Error('Expected ' + expectation + ' with different headers\n' + + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + + prettyPrint(headers)); + } + + expectations.shift(); + + if (expectation.response) { + responses.push(wrapResponse(expectation)); + return; + } + wasExpected = true; + } + + var i = -1, definition; + while ((definition = definitions[++i])) { + if (definition.match(method, url, data, headers || {})) { + if (definition.response) { + // if $browser specified, we do auto flush all requests + ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); + } else if (definition.passThrough) { + $delegate(method, url, data, callback, headers, timeout, withCredentials); + } else throw new Error('No response defined !'); + return; + } + } + throw wasExpected ? + new Error('No response defined !') : + new Error('Unexpected request: ' + method + ' ' + url + '\n' + + (expectation ? 'Expected ' + expectation : 'No more request expected')); + } + + /** + * @ngdoc method + * @name $httpBackend#when + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + * + * - respond – + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can + * return an array containing response status (number), response data (string), response + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. + */ + $httpBackend.when = function (method, url, data, headers) { + var definition = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function (status, data, headers, statusText) { + definition.passThrough = undefined; + definition.response = createResponse(status, data, headers, statusText); + return chain; + } + }; + + if ($browser) { + chain.passThrough = function () { + definition.response = undefined; + definition.passThrough = true; + return chain; + }; + } + + definitions.push(definition); + return chain; + }; + + /** + * @ngdoc method + * @name $httpBackend#whenGET + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenHEAD + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenDELETE + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPOST + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPUT + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenJSONP + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + createShortMethods('when'); + + + /** + * @ngdoc method + * @name $httpBackend#expect + * @description + * Creates a new request expectation. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current expectation. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + * + * - respond – + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can + * return an array containing response status (number), response data (string), response + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. + */ + $httpBackend.expect = function (method, url, data, headers) { + var expectation = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function (status, data, headers, statusText) { + expectation.response = createResponse(status, data, headers, statusText); + return chain; + } + }; + + expectations.push(expectation); + return chain; + }; + + + /** + * @ngdoc method + * @name $httpBackend#expectGET + * @description + * Creates a new request expectation for GET requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. See #expect for more info. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectHEAD + * @description + * Creates a new request expectation for HEAD requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectDELETE + * @description + * Creates a new request expectation for DELETE requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPOST + * @description + * Creates a new request expectation for POST requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPUT + * @description + * Creates a new request expectation for PUT requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPATCH + * @description + * Creates a new request expectation for PATCH requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectJSONP + * @description + * Creates a new request expectation for JSONP requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives an url + * and returns true if the url matches the current definition. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + createShortMethods('expect'); + + + /** + * @ngdoc method + * @name $httpBackend#flush + * @description + * Flushes all pending requests using the trained responses. + * + * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, + * all pending requests will be flushed. If there are no pending requests when the flush method + * is called an exception is thrown (as this typically a sign of programming error). + */ + $httpBackend.flush = function (count, digest) { + if (digest !== false) $rootScope.$digest(); + if (!responses.length) throw new Error('No pending request to flush !'); + + if (angular.isDefined(count) && count !== null) { + while (count--) { + if (!responses.length) throw new Error('No more pending request to flush !'); + responses.shift()(); + } + } else { + while (responses.length) { + responses.shift()(); + } + } + $httpBackend.verifyNoOutstandingExpectation(digest); + }; + + + /** + * @ngdoc method + * @name $httpBackend#verifyNoOutstandingExpectation + * @description + * Verifies that all of the requests defined via the `expect` api were made. If any of the + * requests were not made, verifyNoOutstandingExpectation throws an exception. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * ```js + * afterEach($httpBackend.verifyNoOutstandingExpectation); + * ``` + */ + $httpBackend.verifyNoOutstandingExpectation = function (digest) { + if (digest !== false) $rootScope.$digest(); + if (expectations.length) { + throw new Error('Unsatisfied requests: ' + expectations.join(', ')); + } + }; + + + /** + * @ngdoc method + * @name $httpBackend#verifyNoOutstandingRequest + * @description + * Verifies that there are no outstanding requests that need to be flushed. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * ```js + * afterEach($httpBackend.verifyNoOutstandingRequest); + * ``` + */ + $httpBackend.verifyNoOutstandingRequest = function () { + if (responses.length) { + throw new Error('Unflushed requests: ' + responses.length); + } + }; + + + /** + * @ngdoc method + * @name $httpBackend#resetExpectations + * @description + * Resets all request expectations, but preserves all backend definitions. Typically, you would + * call resetExpectations during a multiple-phase test when you want to reuse the same instance of + * $httpBackend mock. + */ + $httpBackend.resetExpectations = function () { + expectations.length = 0; + responses.length = 0; + }; + + return $httpBackend; + + + function createShortMethods(prefix) { + angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function (method) { + $httpBackend[prefix + method] = function (url, headers) { + return $httpBackend[prefix](method, url, undefined, headers); + }; + }); + + angular.forEach(['PUT', 'POST', 'PATCH'], function (method) { + $httpBackend[prefix + method] = function (url, data, headers) { + return $httpBackend[prefix](method, url, data, headers); + }; + }); + } + } + + function MockHttpExpectation(method, url, data, headers) { + + this.data = data; + this.headers = headers; + + this.match = function (m, u, d, h) { + if (method != m) return false; + if (!this.matchUrl(u)) return false; + if (angular.isDefined(d) && !this.matchData(d)) return false; + if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + return true; + }; + + this.matchUrl = function (u) { + if (!url) return true; + if (angular.isFunction(url.test)) return url.test(u); + if (angular.isFunction(url)) return url(u); + return url == u; + }; + + this.matchHeaders = function (h) { + if (angular.isUndefined(headers)) return true; + if (angular.isFunction(headers)) return headers(h); + return angular.equals(headers, h); + }; + + this.matchData = function (d) { + if (angular.isUndefined(data)) return true; + if (data && angular.isFunction(data.test)) return data.test(d); + if (data && angular.isFunction(data)) return data(d); + if (data && !angular.isString(data)) { + return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d)); + } + return data == d; + }; + + this.toString = function () { + return method + ' ' + url; + }; + } + + function createMockXhr() { + return new MockXhr(); + } + + function MockXhr() { + + // hack for testing $http, $httpBackend + MockXhr.$$lastInstance = this; + + this.open = function (method, url, async) { + this.$$method = method; + this.$$url = url; + this.$$async = async; + this.$$reqHeaders = {}; + this.$$respHeaders = {}; + }; + + this.send = function (data) { + this.$$data = data; + }; + + this.setRequestHeader = function (key, value) { + this.$$reqHeaders[key] = value; + }; + + this.getResponseHeader = function (name) { + // the lookup must be case insensitive, + // that's why we try two quick lookups first and full scan last + var header = this.$$respHeaders[name]; + if (header) return header; + + name = angular.lowercase(name); + header = this.$$respHeaders[name]; + if (header) return header; + + header = undefined; + angular.forEach(this.$$respHeaders, function (headerVal, headerName) { + if (!header && angular.lowercase(headerName) == name) header = headerVal; + }); + return header; + }; + + this.getAllResponseHeaders = function () { + var lines = []; + + angular.forEach(this.$$respHeaders, function (value, key) { + lines.push(key + ': ' + value); + }); + return lines.join('\n'); + }; + + this.abort = angular.noop; + } + + + /** + * @ngdoc service + * @name $timeout + * @description + * + * This service is just a simple decorator for {@link ng.$timeout $timeout} service + * that adds a "flush" and "verifyNoPendingTasks" methods. + */ + + angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function ($delegate, $browser) { + + /** + * @ngdoc method + * @name $timeout#flush + * @description + * + * Flushes the queue of pending tasks. + * + * @param {number=} delay maximum timeout amount to flush up until + */ + $delegate.flush = function (delay) { + $browser.defer.flush(delay); + }; + + /** + * @ngdoc method + * @name $timeout#verifyNoPendingTasks + * @description + * + * Verifies that there are no pending tasks that need to be flushed. + */ + $delegate.verifyNoPendingTasks = function () { + if ($browser.deferredFns.length) { + throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + + formatPendingTasksAsString($browser.deferredFns)); + } + }; + + function formatPendingTasksAsString(tasks) { + var result = []; + angular.forEach(tasks, function (task) { + result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); + }); + + return result.join(', '); + } + + return $delegate; + }]; + + angular.mock.$RAFDecorator = ['$delegate', function ($delegate) { + var rafFn = function (fn) { + var index = rafFn.queue.length; + rafFn.queue.push(fn); + return function () { + rafFn.queue.splice(index, 1); + }; + }; + + rafFn.queue = []; + rafFn.supported = $delegate.supported; + + rafFn.flush = function () { + if (rafFn.queue.length === 0) { + throw new Error('No rAF callbacks present'); + } + + var length = rafFn.queue.length; + for (var i = 0; i < length; i++) { + rafFn.queue[i](); + } + + rafFn.queue = rafFn.queue.slice(i); + }; + + return rafFn; + }]; + + /** + * + */ + angular.mock.$RootElementProvider = function () { + this.$get = function () { + return angular.element('
'); + }; + }; + + /** + * @ngdoc service + * @name $controller + * @description + * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing + * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}. + * + * + * ## Example + * + * ```js + * + * // Directive definition ... + * + * myMod.directive('myDirective', { + * controller: 'MyDirectiveController', + * bindToController: { + * name: '@' + * } + * }); + * + * + * // Controller definition ... + * + * myMod.controller('MyDirectiveController', ['log', function($log) { + * $log.info(this.name); + * })]; + * + * + * // In a test ... + * + * describe('myDirectiveController', function() { + * it('should write the bound name to the log', inject(function($controller, $log) { + * var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' }); + * expect(ctrl.name).toEqual('Clark Kent'); + * expect($log.info.logs).toEqual(['Clark Kent']); + * }); + * }); + * + * ``` + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global + * `window` object (not recommended) + * + * The string can use the `controller as property` syntax, where the controller instance is published + * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this + * to work correctly. + * + * @param {Object} locals Injection locals for Controller. + * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used + * to simulate the `bindToController` feature and simplify certain kinds of tests. + * @return {Object} Instance of given controller. + */ + angular.mock.$ControllerDecorator = ['$delegate', function ($delegate) { + return function (expression, locals, later, ident) { + if (later && typeof later === 'object') { + var create = $delegate(expression, locals, true, ident); + angular.extend(create.instance, later); + return create(); + } + return $delegate(expression, locals, later, ident); + }; + }]; + + + /** + * @ngdoc module + * @name ngMock + * @packageName angular-mocks + * @description + * + * # ngMock + * + * The `ngMock` module provides support to inject and mock Angular services into unit tests. + * In addition, ngMock also extends various core ng services such that they can be + * inspected and controlled in a synchronous manner within test code. + * + * + *
+ * + */ + angular.module('ngMock', ['ng']).provider({ + $browser: angular.mock.$BrowserProvider, + $exceptionHandler: angular.mock.$ExceptionHandlerProvider, + $log: angular.mock.$LogProvider, + $interval: angular.mock.$IntervalProvider, + $httpBackend: angular.mock.$HttpBackendProvider, + $rootElement: angular.mock.$RootElementProvider + }).config(['$provide', function ($provide) { + $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); + $provide.decorator('$$rAF', angular.mock.$RAFDecorator); + $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); + $provide.decorator('$controller', angular.mock.$ControllerDecorator); + }]); + + /** + * @ngdoc module + * @name ngMockE2E + * @module ngMockE2E + * @packageName angular-mocks + * @description + * + * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. + * Currently there is only one mock present in this module - + * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. + */ + angular.module('ngMockE2E', ['ng']).config(['$provide', function ($provide) { + $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); + }]); + + /** + * @ngdoc service + * @name $httpBackend + * @module ngMockE2E + * @description + * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of + * applications that use the {@link ng.$http $http service}. + * + * *Note*: For fake http backend implementation suitable for unit testing please see + * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. + * + * This implementation can be used to respond with static or dynamic responses via the `when` api + * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the + * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch + * templates from a webserver). + * + * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application + * is being developed with the real backend api replaced with a mock, it is often desirable for + * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch + * templates or static files from the webserver). To configure the backend with this behavior + * use the `passThrough` request handler of `when` instead of `respond`. + * + * Additionally, we don't want to manually have to flush mocked out requests like we do during unit + * testing. For this reason the e2e $httpBackend flushes mocked out requests + * automatically, closely simulating the behavior of the XMLHttpRequest object. + * + * To setup the application to run with this http backend, you have to create a module that depends + * on the `ngMockE2E` and your application modules and defines the fake backend: + * + * ```js + * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); + * myAppDev.run(function($httpBackend) { + * phones = [{name: 'phone1'}, {name: 'phone2'}]; + * + * // returns the current list of phones + * $httpBackend.whenGET('/phones').respond(phones); + * + * // adds a new phone to the phones array + * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { + * var phone = angular.fromJson(data); + * phones.push(phone); + * return [200, phone, {}]; + * }); + * $httpBackend.whenGET(/^\/templates\//).passThrough(); + * //... + * }); + * ``` + * + * Afterwards, bootstrap your app with this new module. + */ + + /** + * @ngdoc method + * @name $httpBackend#when + * @module ngMockE2E + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + * + * - respond – + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string), response headers + * (Object), and the text for the status (string). + * - passThrough – `{function()}` – Any request matching a backend definition with + * `passThrough` handler will be passed through to the real backend (an XHR request will be made + * to the server.) + * - Both methods return the `requestHandler` object for possible overrides. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenGET + * @module ngMockE2E + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenHEAD + * @module ngMockE2E + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenDELETE + * @module ngMockE2E + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPOST + * @module ngMockE2E + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPUT + * @module ngMockE2E + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPATCH + * @module ngMockE2E + * @description + * Creates a new backend definition for PATCH requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenJSONP + * @module ngMockE2E + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + angular.mock.e2e = {}; + angular.mock.e2e.$httpBackendDecorator = + ['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock]; + + + /** + * @ngdoc type + * @name $rootScope.Scope + * @module ngMock + * @description + * {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These + * methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when + * `ngMock` module is loaded. + * + * In addition to all the regular `Scope` methods, the following helper methods are available: + */ + angular.mock.$RootScopeDecorator = ['$delegate', function ($delegate) { + + var $rootScopePrototype = Object.getPrototypeOf($delegate); + + $rootScopePrototype.$countChildScopes = countChildScopes; + $rootScopePrototype.$countWatchers = countWatchers; + + return $delegate; + + // ------------------------------------------------------------------------------------------ // + + /** + * @ngdoc method + * @name $rootScope.Scope#$countChildScopes + * @module ngMock + * @description + * Counts all the direct and indirect child scopes of the current scope. + * + * The current scope is excluded from the count. The count includes all isolate child scopes. + * + * @returns {number} Total number of child scopes. + */ + function countChildScopes() { + // jshint validthis: true + var count = 0; // exclude the current scope + var pendingChildHeads = [this.$$childHead]; + var currentScope; + + while (pendingChildHeads.length) { + currentScope = pendingChildHeads.shift(); + + while (currentScope) { + count += 1; + pendingChildHeads.push(currentScope.$$childHead); + currentScope = currentScope.$$nextSibling; + } + } + + return count; + } + + + /** + * @ngdoc method + * @name $rootScope.Scope#$countWatchers + * @module ngMock + * @description + * Counts all the watchers of direct and indirect child scopes of the current scope. + * + * The watchers of the current scope are included in the count and so are all the watchers of + * isolate child scopes. + * + * @returns {number} Total number of watchers. + */ + function countWatchers() { + // jshint validthis: true + var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope + var pendingChildHeads = [this.$$childHead]; + var currentScope; + + while (pendingChildHeads.length) { + currentScope = pendingChildHeads.shift(); + + while (currentScope) { + count += currentScope.$$watchers ? currentScope.$$watchers.length : 0; + pendingChildHeads.push(currentScope.$$childHead); + currentScope = currentScope.$$nextSibling; + } + } + + return count; + } + }]; + + + if (window.jasmine || window.mocha) { + + var currentSpec = null, + annotatedFunctions = [], + isSpecRunning = function () { + return !!currentSpec; + }; + + angular.mock.$$annotate = angular.injector.$$annotate; + angular.injector.$$annotate = function (fn) { + if (typeof fn === 'function' && !fn.$inject) { + annotatedFunctions.push(fn); + } + return angular.mock.$$annotate.apply(this, arguments); + }; + + + (window.beforeEach || window.setup)(function () { + annotatedFunctions = []; + currentSpec = this; + }); + + (window.afterEach || window.teardown)(function () { + var injector = currentSpec.$injector; + + annotatedFunctions.forEach(function (fn) { + delete fn.$inject; + }); + + angular.forEach(currentSpec.$modules, function (module) { + if (module && module.$$hashKey) { + module.$$hashKey = undefined; + } + }); + + currentSpec.$injector = null; + currentSpec.$modules = null; + currentSpec = null; + + if (injector) { + injector.get('$rootElement').off(); + } + + // clean up jquery's fragment cache + angular.forEach(angular.element.fragments, function (val, key) { + delete angular.element.fragments[key]; + }); + + MockXhr.$$lastInstance = null; + + angular.forEach(angular.callbacks, function (val, key) { + delete angular.callbacks[key]; + }); + angular.callbacks.counter = 0; + }); + + /** + * @ngdoc function + * @name angular.mock.module + * @description + * + * *NOTE*: This function is also published on window for easy access.
+ * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * This function registers a module configuration code. It collects the configuration information + * which will be used when the injector is created by {@link angular.mock.inject inject}. + * + * See {@link angular.mock.inject inject} for usage example + * + * @param {...(string|Function|Object)} fns any number of modules which are represented as string + * aliases or as anonymous module initialization functions. The modules are used to + * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an + * object literal is passed they will be registered as values in the module, the key being + * the module name and the value being what is returned. + */ + window.module = angular.mock.module = function () { + var moduleFns = Array.prototype.slice.call(arguments, 0); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + if (currentSpec.$injector) { + throw new Error('Injector already created, can not register a module!'); + } else { + var modules = currentSpec.$modules || (currentSpec.$modules = []); + angular.forEach(moduleFns, function (module) { + if (angular.isObject(module) && !angular.isArray(module)) { + modules.push(function ($provide) { + angular.forEach(module, function (value, key) { + $provide.value(key, value); + }); + }); + } else { + modules.push(module); + } + }); + } + } + }; + + /** + * @ngdoc function + * @name angular.mock.inject + * @description + * + * *NOTE*: This function is also published on window for easy access.
+ * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * The inject function wraps a function into an injectable function. The inject() creates new + * instance of {@link auto.$injector $injector} per test, which is then used for + * resolving references. + * + * + * ## Resolving References (Underscore Wrapping) + * Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this + * in multiple `it()` clauses. To be able to do this we must assign the reference to a variable + * that is declared in the scope of the `describe()` block. Since we would, most likely, want + * the variable to have the same name of the reference we have a problem, since the parameter + * to the `inject()` function would hide the outer variable. + * + * To help with this, the injected parameters can, optionally, be enclosed with underscores. + * These are ignored by the injector when the reference name is resolved. + * + * For example, the parameter `_myService_` would be resolved as the reference `myService`. + * Since it is available in the function body as _myService_, we can then assign it to a variable + * defined in an outer scope. + * + * ``` + * // Defined out reference variable outside + * var myService; + * + * // Wrap the parameter in underscores + * beforeEach( inject( function(_myService_){ + * myService = _myService_; + * })); + * + * // Use myService in a series of tests. + * it('makes use of myService', function() { + * myService.doStuff(); + * }); + * + * ``` + * + * See also {@link angular.mock.module angular.mock.module} + * + * ## Example + * Example of what a typical jasmine tests looks like with the inject method. + * ```js + * + * angular.module('myApplicationModule', []) + * .value('mode', 'app') + * .value('version', 'v1.0.1'); + * + * + * describe('MyApp', function() { + * + * // You need to load modules that you want to test, + * // it loads only the "ng" module by default. + * beforeEach(module('myApplicationModule')); + * + * + * // inject() is used to inject arguments of all given functions + * it('should provide a version', inject(function(mode, version) { + * expect(version).toEqual('v1.0.1'); + * expect(mode).toEqual('app'); + * })); + * + * + * // The inject and module method can also be used inside of the it or beforeEach + * it('should override a version and test the new version is injected', function() { + * // module() takes functions or strings (module aliases) + * module(function($provide) { + * $provide.value('version', 'overridden'); // override version here + * }); + * + * inject(function(version) { + * expect(version).toEqual('overridden'); + * }); + * }); + * }); + * + * ``` + * + * @param {...Function} fns any number of functions which will be injected using the injector. + */ + + + + var ErrorAddingDeclarationLocationStack = function (e, errorForStack) { + this.message = e.message; + this.name = e.name; + if (e.line) this.line = e.line; + if (e.sourceId) this.sourceId = e.sourceId; + if (e.stack && errorForStack) + this.stack = e.stack + '\n' + errorForStack.stack; + if (e.stackArray) this.stackArray = e.stackArray; + }; + ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString; + + window.inject = angular.mock.inject = function () { + var blockFns = Array.prototype.slice.call(arguments, 0); + var errorForStack = new Error('Declaration Location'); + return isSpecRunning() ? workFn.call(currentSpec) : workFn; + ///////////////////// + function workFn() { + var modules = currentSpec.$modules || []; + var strictDi = !!currentSpec.$injectorStrict; + modules.unshift('ngMock'); + modules.unshift('ng'); + var injector = currentSpec.$injector; + if (!injector) { + if (strictDi) { + // If strictDi is enabled, annotate the providerInjector blocks + angular.forEach(modules, function (moduleFn) { + if (typeof moduleFn === "function") { + angular.injector.$$annotate(moduleFn); + } + }); + } + injector = currentSpec.$injector = angular.injector(modules, strictDi); + currentSpec.$injectorStrict = strictDi; + } + for (var i = 0, ii = blockFns.length; i < ii; i++) { + if (currentSpec.$injectorStrict) { + // If the injector is strict / strictDi, and the spec wants to inject using automatic + // annotation, then annotate the function here. + injector.annotate(blockFns[i]); + } + try { + /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */ + injector.invoke(blockFns[i] || angular.noop, this); + /* jshint +W040 */ + } catch (e) { + if (e.stack && errorForStack) { + throw new ErrorAddingDeclarationLocationStack(e, errorForStack); + } + throw e; + } finally { + errorForStack = null; + } + } + } + }; + + + angular.mock.inject.strictDi = function (value) { + value = arguments.length ? !!value : true; + return isSpecRunning() ? workFn() : workFn; + + function workFn() { + if (value !== currentSpec.$injectorStrict) { + if (currentSpec.$injector) { + throw new Error('Injector already created, can not modify strict annotations'); + } else { + currentSpec.$injectorStrict = value; + } + } + } + }; + } + + +})(window, window.angular); +/** + * @license AngularJS v1.4.7 + * (c) 2010-2015 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + + var $resourceMinErr = angular.$$minErr('$resource'); + +// Helper functions and regex to lookup a dotted path on an object +// stopping at undefined/null. The path must be composed of ASCII +// identifiers (just like $parse) + var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/; + + function isValidDottedPath(path) { + return (path != null && path !== '' && path !== 'hasOwnProperty' && + MEMBER_NAME_REGEX.test('.' + path)); + } + + function lookupDottedPath(obj, path) { + if (!isValidDottedPath(path)) { + throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); + } + var keys = path.split('.'); + for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) { + var key = keys[i]; + obj = (obj !== null) ? obj[key] : undefined; + } + return obj; + } + + /** + * Create a shallow copy of an object and clear other fields from the destination + */ + function shallowClearAndCopy(src, dst) { + dst = dst || {}; + + angular.forEach(dst, function(value, key) { + delete dst[key]; + }); + + for (var key in src) { + if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } + } + + return dst; + } + + /** + * @ngdoc module + * @name ngResource + * @description + * + * # ngResource + * + * The `ngResource` module provides interaction support with RESTful services + * via the $resource service. + * + * + *
+ * + * See {@link ngResource.$resource `$resource`} for usage. + */ + + /** + * @ngdoc service + * @name $resource + * @requires $http + * + * @description + * A factory which creates a resource object that lets you interact with + * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. + * + * The returned resource object has action methods which provide high-level behaviors without + * the need to interact with the low level {@link ng.$http $http} service. + * + * Requires the {@link ngResource `ngResource`} module to be installed. + * + * By default, trailing slashes will be stripped from the calculated URLs, + * which can pose problems with server backends that do not expect that + * behavior. This can be disabled by configuring the `$resourceProvider` like + * this: + * + * ```js + app.config(['$resourceProvider', function($resourceProvider) { + // Don't strip trailing slashes from calculated URLs + $resourceProvider.defaults.stripTrailingSlashes = false; + }]); + * ``` + * + * @param {string} url A parameterized URL template with parameters prefixed by `:` as in + * `/user/:username`. If you are using a URL with a port number (e.g. + * `http://example.com:8080/api`), it will be respected. + * + * If you are using a url with a suffix, just add the suffix, like this: + * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` + * or even `$resource('http://example.com/resource/:resource_id.:format')` + * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be + * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you + * can escape it with `/\.`. + * + * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in + * `actions` methods. If any of the parameter value is a function, it will be executed every time + * when a param value needs to be obtained for a request (unless the param was overridden). + * + * Each key value in the parameter object is first bound to url template if present and then any + * excess keys are appended to the url search query after the `?`. + * + * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in + * URL `/path/greet?salutation=Hello`. + * + * If the parameter value is prefixed with `@` then the value for that parameter will be extracted + * from the corresponding property on the `data` object (provided when calling an action method). For + * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam` + * will be `data.someProp`. + * + * @param {Object.=} actions Hash with declaration of custom actions that should extend + * the default set of resource actions. The declaration should be created in the format of {@link + * ng.$http#usage $http.config}: + * + * {action1: {method:?, params:?, isArray:?, headers:?, ...}, + * action2: {method:?, params:?, isArray:?, headers:?, ...}, + * ...} + * + * Where: + * + * - **`action`** – {string} – The name of action. This name becomes the name of the method on + * your resource object. + * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, + * `DELETE`, `JSONP`, etc). + * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of + * the parameter value is a function, it will be executed every time when a param value needs to + * be obtained for a request (unless the param was overridden). + * - **`url`** – {string} – action specific `url` override. The url templating is supported just + * like for the resource-level urls. + * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, + * see `returns` section. + * - **`transformRequest`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * By default, transformRequest will contain one function that checks if the request data is + * an object and serializes to using `angular.toJson`. To prevent this behavior, set + * `transformRequest` to an empty array: `transformRequest: []` + * - **`transformResponse`** – + * `{function(data, headersGetter)|Array.}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * By default, transformResponse will contain one function that checks if the response looks like + * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set + * `transformResponse` to an empty array: `transformResponse: []` + * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that + * should abort the request when resolved. + * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the + * XHR object. See + * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) + * for more information. + * - **`responseType`** - `{string}` - see + * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). + * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - + * `response` and `responseError`. Both `response` and `responseError` interceptors get called + * with `http response` object. See {@link ng.$http $http interceptors}. + * + * @param {Object} options Hash with custom settings that should extend the + * default `$resourceProvider` behavior. The only supported option is + * + * Where: + * + * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing + * slashes from any calculated URL will be stripped. (Defaults to true.) + * + * @returns {Object} A resource "class" object with methods for the default set of resource actions + * optionally extended with custom `actions`. The default set contains these actions: + * ```js + * { 'get': {method:'GET'}, + * 'save': {method:'POST'}, + * 'query': {method:'GET', isArray:true}, + * 'remove': {method:'DELETE'}, + * 'delete': {method:'DELETE'} }; + * ``` + * + * Calling these methods invoke an {@link ng.$http} with the specified http method, + * destination and parameters. When the data is returned from the server then the object is an + * instance of the resource class. The actions `save`, `remove` and `delete` are available on it + * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, + * read, update, delete) on server-side data like this: + * ```js + * var User = $resource('/user/:userId', {userId:'@id'}); + * var user = User.get({userId:123}, function() { + * user.abc = true; + * user.$save(); + * }); + * ``` + * + * It is important to realize that invoking a $resource object method immediately returns an + * empty reference (object or array depending on `isArray`). Once the data is returned from the + * server the existing reference is populated with the actual data. This is a useful trick since + * usually the resource is assigned to a model which is then rendered by the view. Having an empty + * object results in no rendering, once the data arrives from the server then the object is + * populated with the data and the view automatically re-renders itself showing the new data. This + * means that in most cases one never has to write a callback function for the action methods. + * + * The action methods on the class object or instance object can be invoked with the following + * parameters: + * + * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` + * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` + * - non-GET instance actions: `instance.$action([parameters], [success], [error])` + * + * + * Success callback is called with (value, responseHeaders) arguments, where the value is + * the populated resource instance or collection object. The error callback is called + * with (httpResponse) argument. + * + * Class actions return empty instance (with additional properties below). + * Instance actions return promise of the action. + * + * The Resource instances and collection have these additional properties: + * + * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this + * instance or collection. + * + * On success, the promise is resolved with the same resource instance or collection object, + * updated with data from server. This makes it easy to use in + * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view + * rendering until the resource(s) are loaded. + * + * On failure, the promise is resolved with the {@link ng.$http http response} object, without + * the `resource` property. + * + * If an interceptor object was provided, the promise will instead be resolved with the value + * returned by the interceptor. + * + * - `$resolved`: `true` after first server interaction is completed (either with success or + * rejection), `false` before that. Knowing if the Resource has been resolved is useful in + * data-binding. + * + * @example + * + * # Credit card resource + * + * ```js + // Define CreditCard class + var CreditCard = $resource('/user/:userId/card/:cardId', + {userId:123, cardId:'@id'}, { + charge: {method:'POST', params:{charge:true}} + }); + + // We can retrieve a collection from the server + var cards = CreditCard.query(function() { + // GET: /user/123/card + // server returns: [ {id:456, number:'1234', name:'Smith'} ]; + + var card = cards[0]; + // each item is an instance of CreditCard + expect(card instanceof CreditCard).toEqual(true); + card.name = "J. Smith"; + // non GET methods are mapped onto the instances + card.$save(); + // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} + // server returns: {id:456, number:'1234', name: 'J. Smith'}; + + // our custom method is mapped as well. + card.$charge({amount:9.99}); + // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} + }); + + // we can create an instance as well + var newCard = new CreditCard({number:'0123'}); + newCard.name = "Mike Smith"; + newCard.$save(); + // POST: /user/123/card {number:'0123', name:'Mike Smith'} + // server returns: {id:789, number:'0123', name: 'Mike Smith'}; + expect(newCard.id).toEqual(789); + * ``` + * + * The object returned from this function execution is a resource "class" which has "static" method + * for each action in the definition. + * + * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and + * `headers`. + * When the data is returned from the server then the object is an instance of the resource type and + * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD + * operations (create, read, update, delete) on server-side data. + + ```js + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}, function(user) { + user.abc = true; + user.$save(); + }); + ``` + * + * It's worth noting that the success callback for `get`, `query` and other methods gets passed + * in the response that came from the server as well as $http header getter function, so one + * could rewrite the above example and get access to http headers as: + * + ```js + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}, function(u, getResponseHeaders){ + u.abc = true; + u.$save(function(u, putResponseHeaders) { + //u => saved user object + //putResponseHeaders => $http header getter + }); + }); + ``` + * + * You can also access the raw `$http` promise via the `$promise` property on the object returned + * + ``` + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}) + .$promise.then(function(user) { + $scope.user = user; + }); + ``` + + * # Creating a custom 'PUT' request + * In this example we create a custom method on our resource to make a PUT request + * ```js + * var app = angular.module('app', ['ngResource', 'ngRoute']); + * + * // Some APIs expect a PUT request in the format URL/object/ID + * // Here we are creating an 'update' method + * app.factory('Notes', ['$resource', function($resource) { + * return $resource('/notes/:id', null, + * { + * 'update': { method:'PUT' } + * }); + * }]); + * + * // In our controller we get the ID from the URL using ngRoute and $routeParams + * // We pass in $routeParams and our Notes factory along with $scope + * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', + function($scope, $routeParams, Notes) { + * // First get a note object from the factory + * var note = Notes.get({ id:$routeParams.id }); + * $id = note.id; + * + * // Now call update passing in the ID first then the object you are updating + * Notes.update({ id:$id }, note); + * + * // This will PUT /notes/ID with the note object in the request payload + * }]); + * ``` + */ + angular.module('ngResource', ['ng']). + provider('$resource', function() { + var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/; + var provider = this; + + this.defaults = { + // Strip slashes by default + stripTrailingSlashes: true, + + // Default actions configuration + actions: { + 'get': {method: 'GET'}, + 'save': {method: 'POST'}, + 'query': {method: 'GET', isArray: true}, + 'remove': {method: 'DELETE'}, + 'delete': {method: 'DELETE'} + } + }; + + this.$get = ['$http', '$q', function($http, $q) { + + var noop = angular.noop, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + isFunction = angular.isFunction; + + /** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set + * (pchar) allowed in path segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); + } + + + /** + * This method is intended for encoding *key* or *value* parts of query component. We need a + * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't + * have to be encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } + + function Route(template, defaults) { + this.template = template; + this.defaults = extend({}, provider.defaults, defaults); + this.urlParams = {}; + } + + Route.prototype = { + setUrlParams: function(config, params, actionUrl) { + var self = this, + url = actionUrl || self.template, + val, + encodedVal, + protocolAndDomain = ''; + + var urlParams = self.urlParams = {}; + forEach(url.split(/\W/), function(param) { + if (param === 'hasOwnProperty') { + throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); + } + if (!(new RegExp("^\\d+$").test(param)) && param && + (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { + urlParams[param] = true; + } + }); + url = url.replace(/\\:/g, ':'); + url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) { + protocolAndDomain = match; + return ''; + }); + + params = params || {}; + forEach(self.urlParams, function(_, urlParam) { + val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; + if (angular.isDefined(val) && val !== null) { + encodedVal = encodeUriSegment(val); + url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { + return encodedVal + p1; + }); + } else { + url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, + leadingSlashes, tail) { + if (tail.charAt(0) == '/') { + return tail; + } else { + return leadingSlashes + tail; + } + }); + } + }); + + // strip trailing slashes and set the url (unless this behavior is specifically disabled) + if (self.defaults.stripTrailingSlashes) { + url = url.replace(/\/+$/, '') || '/'; + } + + // then replace collapse `/.` if found in the last URL path segment before the query + // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` + url = url.replace(/\/\.(?=\w+($|\?))/, '.'); + // replace escaped `/\.` with `/.` + config.url = protocolAndDomain + url.replace(/\/\\\./, '/.'); + + + // set params - delegate param encoding to $http + forEach(params, function(value, key) { + if (!self.urlParams[key]) { + config.params = config.params || {}; + config.params[key] = value; + } + }); + } + }; + + + function resourceFactory(url, paramDefaults, actions, options) { + var route = new Route(url, options); + + actions = extend({}, provider.defaults.actions, actions); + + function extractParams(data, actionParams) { + var ids = {}; + actionParams = extend({}, paramDefaults, actionParams); + forEach(actionParams, function(value, key) { + if (isFunction(value)) { value = value(); } + ids[key] = value && value.charAt && value.charAt(0) == '@' ? + lookupDottedPath(data, value.substr(1)) : value; + }); + return ids; + } + + function defaultResponseInterceptor(response) { + return response.resource; + } + + function Resource(value) { + shallowClearAndCopy(value || {}, this); + } + + Resource.prototype.toJSON = function() { + var data = extend({}, this); + delete data.$promise; + delete data.$resolved; + return data; + }; + + forEach(actions, function(action, name) { + var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); + + Resource[name] = function(a1, a2, a3, a4) { + var params = {}, data, success, error; + + /* jshint -W086 */ /* (purposefully fall through case statements) */ + switch (arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (hasBody) data = a1; + else params = a1; + break; + case 0: break; + default: + throw $resourceMinErr('badargs', + "Expected up to 4 arguments [params, data, success, error], got {0} arguments", + arguments.length); + } + /* jshint +W086 */ /* (purposefully fall through case statements) */ + + var isInstanceCall = this instanceof Resource; + var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); + var httpConfig = {}; + var responseInterceptor = action.interceptor && action.interceptor.response || + defaultResponseInterceptor; + var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || + undefined; + + forEach(action, function(value, key) { + if (key != 'params' && key != 'isArray' && key != 'interceptor') { + httpConfig[key] = copy(value); + } + }); + + if (hasBody) httpConfig.data = data; + route.setUrlParams(httpConfig, + extend({}, extractParams(data, action.params || {}), params), + action.url); + + var promise = $http(httpConfig).then(function(response) { + var data = response.data, + promise = value.$promise; + + if (data) { + // Need to convert action.isArray to boolean in case it is undefined + // jshint -W018 + if (angular.isArray(data) !== (!!action.isArray)) { + throw $resourceMinErr('badcfg', + 'Error in resource configuration for action `{0}`. Expected response to ' + + 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object', + angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url); + } + // jshint +W018 + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + if (typeof item === "object") { + value.push(new Resource(item)); + } else { + // Valid JSON values may be string literals, and these should not be converted + // into objects. These items will not have access to the Resource prototype + // methods, but unfortunately there + value.push(item); + } + }); + } else { + shallowClearAndCopy(data, value); + value.$promise = promise; + } + } + + value.$resolved = true; + + response.resource = value; + + return response; + }, function(response) { + value.$resolved = true; + + (error || noop)(response); + + return $q.reject(response); + }); + + promise = promise.then( + function(response) { + var value = responseInterceptor(response); + (success || noop)(value, response.headers); + return value; + }, + responseErrorInterceptor); + + if (!isInstanceCall) { + // we are creating instance / collection + // - set the initial promise + // - return the instance / collection + value.$promise = promise; + value.$resolved = false; + + return value; + } + + // instance call + return promise; + }; + + + Resource.prototype['$' + name] = function(params, success, error) { + if (isFunction(params)) { + error = success; success = params; params = {}; + } + var result = Resource[name].call(this, params, this, success, error); + return result.$promise || result; + }; + }); + + Resource.bind = function(additionalParamDefaults) { + return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + return Resource; + } + + return resourceFactory; + }]; + }); + + +})(window, window.angular); + +/** + * @license AngularJS v1.4.7 + * (c) 2010-2015 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + + /** + * @ngdoc module + * @name ngRoute + * @description + * + * # ngRoute + * + * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. + * + * ## Example + * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. + * + * + *
+ */ + /* global -ngRouteModule */ + var ngRouteModule = angular.module('ngRoute', ['ng']). + provider('$route', $RouteProvider), + $routeMinErr = angular.$$minErr('ngRoute'); + + /** + * @ngdoc provider + * @name $routeProvider + * + * @description + * + * Used for configuring routes. + * + * ## Example + * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. + * + * ## Dependencies + * Requires the {@link ngRoute `ngRoute`} module to be installed. + */ + function $RouteProvider() { + function inherit(parent, extra) { + return angular.extend(Object.create(parent), extra); + } + + var routes = {}; + + /** + * @ngdoc method + * @name $routeProvider#when + * + * @param {string} path Route path (matched against `$location.path`). If `$location.path` + * contains redundant trailing slash or is missing one, the route will still match and the + * `$location.path` will be updated to add or drop the trailing slash to exactly match the + * route definition. + * + * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up + * to the next slash are matched and stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain named groups starting with a colon and ending with a star: + * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain optional named groups with a question mark: e.g.`:name?`. + * + * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match + * `/color/brown/largecode/code/with/slashes/edit` and extract: + * + * * `color: brown` + * * `largecode: code/with/slashes`. + * + * + * @param {Object} route Mapping information to be assigned to `$route.current` on route + * match. + * + * Object properties: + * * - `controller` – `{(string|function()=}` – Controller fn that should be associated with * newly created scope or the name of a {@link angular.Module#controller registered * controller} if passed as a string. @@ -38920,1550 +41390,4849 @@ * The custom `redirectTo` function is expected to return a string which will be used * to update `$location.path()` and `$location.search()`. * - * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` - * or `$location.hash()` changes. + * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` + * or `$location.hash()` changes. + * + * If the option is set to `false` and url in the browser changes, then + * `$routeUpdate` event is broadcasted on the root scope. + * + * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive + * + * If the option is set to `true`, then the particular route can be matched without being + * case sensitive + * + * @returns {Object} self + * + * @description + * Adds a new route definition to the `$route` service. + */ + this.when = function(path, route) { + //copy original route object to preserve params inherited from proto chain + var routeCopy = angular.copy(route); + if (angular.isUndefined(routeCopy.reloadOnSearch)) { + routeCopy.reloadOnSearch = true; + } + if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) { + routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch; + } + routes[path] = angular.extend( + routeCopy, + path && pathRegExp(path, routeCopy) + ); + + // create redirection for trailing slashes + if (path) { + var redirectPath = (path[path.length - 1] == '/') + ? path.substr(0, path.length - 1) + : path + '/'; + + routes[redirectPath] = angular.extend( + {redirectTo: path}, + pathRegExp(redirectPath, routeCopy) + ); + } + + return this; + }; + + /** + * @ngdoc property + * @name $routeProvider#caseInsensitiveMatch + * @description + * + * A boolean property indicating if routes defined + * using this provider should be matched using a case insensitive + * algorithm. Defaults to `false`. + */ + this.caseInsensitiveMatch = false; + + /** + * @param path {string} path + * @param opts {Object} options + * @return {?Object} * - * If the option is set to `false` and url in the browser changes, then - * `$routeUpdate` event is broadcasted on the root scope. + * @description + * Normalizes the given path, returning a regular expression + * and the original path. * - * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive + * Inspired by pathRexp in visionmedia/express/lib/utils.js. + */ + function pathRegExp(path, opts) { + var insensitive = opts.caseInsensitiveMatch, + ret = { + originalPath: path, + regexp: path + }, + keys = ret.keys = []; + + path = path + .replace(/([().])/g, '\\$1') + .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { + var optional = option === '?' ? option : null; + var star = option === '*' ? option : null; + keys.push({ name: key, optional: !!optional }); + slash = slash || ''; + return '' + + (optional ? '' : slash) + + '(?:' + + (optional ? slash : '') + + (star && '(.+?)' || '([^/]+)') + + (optional || '') + + ')' + + (optional || ''); + }) + .replace(/([\/$\*])/g, '\\$1'); + + ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); + return ret; + } + + /** + * @ngdoc method + * @name $routeProvider#otherwise * - * If the option is set to `true`, then the particular route can be matched without being - * case sensitive + * @description + * Sets route definition that will be used on route change when no other route definition + * is matched. * + * @param {Object|string} params Mapping information to be assigned to `$route.current`. + * If called with a string, the value maps to `redirectTo`. * @returns {Object} self - * - * @description - * Adds a new route definition to the `$route` service. */ - this.when = function(path, route) { - //copy original route object to preserve params inherited from proto chain - var routeCopy = angular.copy(route); - if (angular.isUndefined(routeCopy.reloadOnSearch)) { - routeCopy.reloadOnSearch = true; + this.otherwise = function(params) { + if (typeof params === 'string') { + params = {redirectTo: params}; + } + this.when(null, params); + return this; + }; + + + this.$get = ['$rootScope', + '$location', + '$routeParams', + '$q', + '$injector', + '$templateRequest', + '$sce', + function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { + + /** + * @ngdoc service + * @name $route + * @requires $location + * @requires $routeParams + * + * @property {Object} current Reference to the current route definition. + * The route definition contains: + * + * - `controller`: The controller constructor as define in route definition. + * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for + * controller instantiation. The `locals` contain + * the resolved values of the `resolve` map. Additionally the `locals` also contain: + * + * - `$scope` - The current route scope. + * - `$template` - The current route template HTML. + * + * @property {Object} routes Object with all route configuration Objects as its properties. + * + * @description + * `$route` is used for deep-linking URLs to controllers and views (HTML partials). + * It watches `$location.url()` and tries to map the path to an existing route definition. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. + * + * The `$route` service is typically used in conjunction with the + * {@link ngRoute.directive:ngView `ngView`} directive and the + * {@link ngRoute.$routeParams `$routeParams`} service. + * + * @example + * This example shows how changing the URL hash causes the `$route` to match a route against the + * URL, and the `ngView` pulls in the partial. + * + * + * + *
+ * Choose: + * Moby | + * Moby: Ch1 | + * Gatsby | + * Gatsby: Ch4 | + * Scarlet Letter
+ * + *
+ * + *
+ * + *
$location.path() = {{$location.path()}}
+ *
$route.current.templateUrl = {{$route.current.templateUrl}}
+ *
$route.current.params = {{$route.current.params}}
+ *
$route.current.scope.name = {{$route.current.scope.name}}
+ *
$routeParams = {{$routeParams}}
+ *
+ *
+ * + * + * controller: {{name}}
+ * Book Id: {{params.bookId}}
+ *
+ * + * + * controller: {{name}}
+ * Book Id: {{params.bookId}}
+ * Chapter Id: {{params.chapterId}} + *
+ * + * + * angular.module('ngRouteExample', ['ngRoute']) + * + * .controller('MainController', function($scope, $route, $routeParams, $location) { + * $scope.$route = $route; + * $scope.$location = $location; + * $scope.$routeParams = $routeParams; + * }) + * + * .controller('BookController', function($scope, $routeParams) { + * $scope.name = "BookController"; + * $scope.params = $routeParams; + * }) + * + * .controller('ChapterController', function($scope, $routeParams) { + * $scope.name = "ChapterController"; + * $scope.params = $routeParams; + * }) + * + * .config(function($routeProvider, $locationProvider) { + * $routeProvider + * .when('/Book/:bookId', { + * templateUrl: 'book.html', + * controller: 'BookController', + * resolve: { + * // I will cause a 1 second delay + * delay: function($q, $timeout) { + * var delay = $q.defer(); + * $timeout(delay.resolve, 1000); + * return delay.promise; + * } + * } + * }) + * .when('/Book/:bookId/ch/:chapterId', { + * templateUrl: 'chapter.html', + * controller: 'ChapterController' + * }); + * + * // configure html5 to get links working on jsfiddle + * $locationProvider.html5Mode(true); + * }); + * + * + * + * + * it('should load and compile correct template', function() { + * element(by.linkText('Moby: Ch1')).click(); + * var content = element(by.css('[ng-view]')).getText(); + * expect(content).toMatch(/controller\: ChapterController/); + * expect(content).toMatch(/Book Id\: Moby/); + * expect(content).toMatch(/Chapter Id\: 1/); + * + * element(by.partialLinkText('Scarlet')).click(); + * + * content = element(by.css('[ng-view]')).getText(); + * expect(content).toMatch(/controller\: BookController/); + * expect(content).toMatch(/Book Id\: Scarlet/); + * }); + * + *
+ */ + + /** + * @ngdoc event + * @name $route#$routeChangeStart + * @eventType broadcast on root scope + * @description + * Broadcasted before a route change. At this point the route services starts + * resolving all of the dependencies needed for the route change to occur. + * Typically this involves fetching the view template as well as any dependencies + * defined in `resolve` route property. Once all of the dependencies are resolved + * `$routeChangeSuccess` is fired. + * + * The route change (and the `$location` change that triggered it) can be prevented + * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} + * for more details about event object. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} next Future route information. + * @param {Route} current Current route information. + */ + + /** + * @ngdoc event + * @name $route#$routeChangeSuccess + * @eventType broadcast on root scope + * @description + * Broadcasted after a route change has happened successfully. + * The `resolve` dependencies are now available in the `current.locals` property. + * + * {@link ngRoute.directive:ngView ngView} listens for the directive + * to instantiate the controller and render the view. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} current Current route information. + * @param {Route|Undefined} previous Previous route information, or undefined if current is + * first route entered. + */ + + /** + * @ngdoc event + * @name $route#$routeChangeError + * @eventType broadcast on root scope + * @description + * Broadcasted if any of the resolve promises are rejected. + * + * @param {Object} angularEvent Synthetic event object + * @param {Route} current Current route information. + * @param {Route} previous Previous route information. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. + */ + + /** + * @ngdoc event + * @name $route#$routeUpdate + * @eventType broadcast on root scope + * @description + * The `reloadOnSearch` property has been set to false, and we are reusing the same + * instance of the Controller. + * + * @param {Object} angularEvent Synthetic event object + * @param {Route} current Current/previous route information. + */ + + var forceReload = false, + preparedRoute, + preparedRouteIsUpdateOnly, + $route = { + routes: routes, + + /** + * @ngdoc method + * @name $route#reload + * + * @description + * Causes `$route` service to reload the current route even if + * {@link ng.$location $location} hasn't changed. + * + * As a result of that, {@link ngRoute.directive:ngView ngView} + * creates new scope and reinstantiates the controller. + */ + reload: function() { + forceReload = true; + $rootScope.$evalAsync(function() { + // Don't support cancellation of a reload for now... + prepareRoute(); + commitRoute(); + }); + }, + + /** + * @ngdoc method + * @name $route#updateParams + * + * @description + * Causes `$route` service to update the current URL, replacing + * current route parameters with those specified in `newParams`. + * Provided property names that match the route's path segment + * definitions will be interpolated into the location's path, while + * remaining properties will be treated as query params. + * + * @param {!Object} newParams mapping of URL parameter names to values + */ + updateParams: function(newParams) { + if (this.current && this.current.$$route) { + newParams = angular.extend({}, this.current.params, newParams); + $location.path(interpolate(this.current.$$route.originalPath, newParams)); + // interpolate modifies newParams, only query params are left + $location.search(newParams); + } else { + throw $routeMinErr('norout', 'Tried updating route when with no current route'); + } + } + }; + + $rootScope.$on('$locationChangeStart', prepareRoute); + $rootScope.$on('$locationChangeSuccess', commitRoute); + + return $route; + + ///////////////////////////////////////////////////// + + /** + * @param on {string} current url + * @param route {Object} route regexp to match the url against + * @return {?Object} + * + * @description + * Check if the route matches the current url. + * + * Inspired by match in + * visionmedia/express/lib/router/router.js. + */ + function switchRouteMatcher(on, route) { + var keys = route.keys, + params = {}; + + if (!route.regexp) return null; + + var m = route.regexp.exec(on); + if (!m) return null; + + for (var i = 1, len = m.length; i < len; ++i) { + var key = keys[i - 1]; + + var val = m[i]; + + if (key && val) { + params[key.name] = val; + } + } + return params; + } + + function prepareRoute($locationEvent) { + var lastRoute = $route.current; + + preparedRoute = parseRoute(); + preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route + && angular.equals(preparedRoute.pathParams, lastRoute.pathParams) + && !preparedRoute.reloadOnSearch && !forceReload; + + if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { + if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { + if ($locationEvent) { + $locationEvent.preventDefault(); + } + } + } + } + + function commitRoute() { + var lastRoute = $route.current; + var nextRoute = preparedRoute; + + if (preparedRouteIsUpdateOnly) { + lastRoute.params = nextRoute.params; + angular.copy(lastRoute.params, $routeParams); + $rootScope.$broadcast('$routeUpdate', lastRoute); + } else if (nextRoute || lastRoute) { + forceReload = false; + $route.current = nextRoute; + if (nextRoute) { + if (nextRoute.redirectTo) { + if (angular.isString(nextRoute.redirectTo)) { + $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) + .replace(); + } else { + $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) + .replace(); + } + } + } + + $q.when(nextRoute). + then(function() { + if (nextRoute) { + var locals = angular.extend({}, nextRoute.resolve), + template, templateUrl; + + angular.forEach(locals, function(value, key) { + locals[key] = angular.isString(value) ? + $injector.get(value) : $injector.invoke(value, null, null, key); + }); + + if (angular.isDefined(template = nextRoute.template)) { + if (angular.isFunction(template)) { + template = template(nextRoute.params); + } + } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { + if (angular.isFunction(templateUrl)) { + templateUrl = templateUrl(nextRoute.params); + } + if (angular.isDefined(templateUrl)) { + nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl); + template = $templateRequest(templateUrl); + } + } + if (angular.isDefined(template)) { + locals['$template'] = template; + } + return $q.all(locals); + } + }). + then(function(locals) { + // after route change + if (nextRoute == $route.current) { + if (nextRoute) { + nextRoute.locals = locals; + angular.copy(nextRoute.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); + } + }, function(error) { + if (nextRoute == $route.current) { + $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); + } + }); + } + } + + + /** + * @returns {Object} the current active route, by matching it against the URL + */ + function parseRoute() { + // Match a route + var params, match; + angular.forEach(routes, function(route, path) { + if (!match && (params = switchRouteMatcher($location.path(), route))) { + match = inherit(route, { + params: angular.extend({}, $location.search(), params), + pathParams: params}); + match.$$route = route; + } + }); + // No route matched; fallback to "otherwise" route + return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); + } + + /** + * @returns {string} interpolation of the redirect path with the parameters + */ + function interpolate(string, params) { + var result = []; + angular.forEach((string || '').split(':'), function(segment, i) { + if (i === 0) { + result.push(segment); + } else { + var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/); + var key = segmentMatch[1]; + result.push(params[key]); + result.push(segmentMatch[2] || ''); + delete params[key]; + } + }); + return result.join(''); + } + }]; + } + + ngRouteModule.provider('$routeParams', $RouteParamsProvider); + + + /** + * @ngdoc service + * @name $routeParams + * @requires $route + * + * @description + * The `$routeParams` service allows you to retrieve the current set of route parameters. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * The route parameters are a combination of {@link ng.$location `$location`}'s + * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. + * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. + * + * In case of parameter name collision, `path` params take precedence over `search` params. + * + * The service guarantees that the identity of the `$routeParams` object will remain unchanged + * (but its properties will likely change) even when a route change occurs. + * + * Note that the `$routeParams` are only updated *after* a route change completes successfully. + * This means that you cannot rely on `$routeParams` being correct in route resolve functions. + * Instead you can use `$route.current.params` to access the new route's parameters. + * + * @example + * ```js + * // Given: + * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby + * // Route: /Chapter/:chapterId/Section/:sectionId + * // + * // Then + * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} + * ``` + */ + function $RouteParamsProvider() { + this.$get = function() { return {}; }; + } + + ngRouteModule.directive('ngView', ngViewFactory); + ngRouteModule.directive('ngView', ngViewFillContentFactory); + + + /** + * @ngdoc directive + * @name ngView + * @restrict ECA + * + * @description + * # Overview + * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by + * including the rendered template of the current route into the main layout (`index.html`) file. + * Every time the current route changes, the included view changes with it according to the + * configuration of the `$route` service. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * @animations + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently. + * + * @scope + * @priority 400 + * @param {string=} onload Expression to evaluate whenever the view updates. + * + * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll + * $anchorScroll} to scroll the viewport after the view is updated. + * + * - If the attribute is not set, disable scrolling. + * - If the attribute is set without value, enable scrolling. + * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated + * as an expression yields a truthy value. + * @example + + +
+ Choose: + Moby | + Moby: Ch1 | + Gatsby | + Gatsby: Ch4 | + Scarlet Letter
+ +
+
+
+
+ +
$location.path() = {{main.$location.path()}}
+
$route.current.templateUrl = {{main.$route.current.templateUrl}}
+
$route.current.params = {{main.$route.current.params}}
+
$routeParams = {{main.$routeParams}}
+
+
+ + +
+ controller: {{book.name}}
+ Book Id: {{book.params.bookId}}
+
+
+ + +
+ controller: {{chapter.name}}
+ Book Id: {{chapter.params.bookId}}
+ Chapter Id: {{chapter.params.chapterId}} +
+
+ + + .view-animate-container { + position:relative; + height:100px!important; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .view-animate { + padding:10px; + } + + .view-animate.ng-enter, .view-animate.ng-leave { + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + + display:block; + width:100%; + border-left:1px solid black; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + padding:10px; + } + + .view-animate.ng-enter { + left:100%; + } + .view-animate.ng-enter.ng-enter-active { + left:0; + } + .view-animate.ng-leave.ng-leave-active { + left:-100%; + } + + + + angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) + .config(['$routeProvider', '$locationProvider', + function($routeProvider, $locationProvider) { + $routeProvider + .when('/Book/:bookId', { + templateUrl: 'book.html', + controller: 'BookCtrl', + controllerAs: 'book' + }) + .when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: 'ChapterCtrl', + controllerAs: 'chapter' + }); + + $locationProvider.html5Mode(true); + }]) + .controller('MainCtrl', ['$route', '$routeParams', '$location', + function($route, $routeParams, $location) { + this.$route = $route; + this.$location = $location; + this.$routeParams = $routeParams; + }]) + .controller('BookCtrl', ['$routeParams', function($routeParams) { + this.name = "BookCtrl"; + this.params = $routeParams; + }]) + .controller('ChapterCtrl', ['$routeParams', function($routeParams) { + this.name = "ChapterCtrl"; + this.params = $routeParams; + }]); + + + + + it('should load and compile correct template', function() { + element(by.linkText('Moby: Ch1')).click(); + var content = element(by.css('[ng-view]')).getText(); + expect(content).toMatch(/controller\: ChapterCtrl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element(by.partialLinkText('Scarlet')).click(); + + content = element(by.css('[ng-view]')).getText(); + expect(content).toMatch(/controller\: BookCtrl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + +
+ */ + + + /** + * @ngdoc event + * @name ngView#$viewContentLoaded + * @eventType emit on the current ngView scope + * @description + * Emitted every time the ngView content is reloaded. + */ + ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; + function ngViewFactory($route, $anchorScroll, $animate) { + return { + restrict: 'ECA', + terminal: true, + priority: 400, + transclude: 'element', + link: function(scope, $element, attr, ctrl, $transclude) { + var currentScope, + currentElement, + previousLeaveAnimation, + autoScrollExp = attr.autoscroll, + onloadExp = attr.onload || ''; + + scope.$on('$routeChangeSuccess', update); + update(); + + function cleanupLastView() { + if (previousLeaveAnimation) { + $animate.cancel(previousLeaveAnimation); + previousLeaveAnimation = null; + } + + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if (currentElement) { + previousLeaveAnimation = $animate.leave(currentElement); + previousLeaveAnimation.then(function() { + previousLeaveAnimation = null; + }); + currentElement = null; + } + } + + function update() { + var locals = $route.current && $route.current.locals, + template = locals && locals.$template; + + if (angular.isDefined(template)) { + var newScope = scope.$new(); + var current = $route.current; + + // Note: This will also link all children of ng-view that were contained in the original + // html. If that content contains controllers, ... they could pollute/change the scope. + // However, using ng-view on an element with additional content does not make sense... + // Note: We can't remove them in the cloneAttchFn of $transclude as that + // function is called before linking the content, which would apply child + // directives to non existing elements. + var clone = $transclude(newScope, function(clone) { + $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { + if (angular.isDefined(autoScrollExp) + && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + }); + cleanupLastView(); + }); + + currentElement = clone; + currentScope = current.scope = newScope; + currentScope.$emit('$viewContentLoaded'); + currentScope.$eval(onloadExp); + } else { + cleanupLastView(); + } + } + } + }; + } + +// This directive is called during the $transclude call of the first `ngView` directive. +// It will replace and compile the content of the element with the loaded template. +// We need this directive so that the element content is already filled when +// the link function of another directive on the same element as ngView +// is called. + ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; + function ngViewFillContentFactory($compile, $controller, $route) { + return { + restrict: 'ECA', + priority: -400, + link: function(scope, $element) { + var current = $route.current, + locals = current.locals; + + $element.html(locals.$template); + + var link = $compile($element.contents()); + + if (current.controller) { + locals.$scope = scope; + var controller = $controller(current.controller, locals); + if (current.controllerAs) { + scope[current.controllerAs] = controller; + } + $element.data('$ngControllerController', controller); + $element.children().data('$ngControllerController', controller); + } + + link(scope); + } + }; + } + + +})(window, window.angular); + +/** + * @license AngularJS v1.4.7 + * (c) 2010-2015 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + var $sanitizeMinErr = angular.$$minErr('$sanitize'); + + /** + * @ngdoc module + * @name ngSanitize + * @description + * + * # ngSanitize + * + * The `ngSanitize` module provides functionality to sanitize HTML. + * + * + *
+ * + * See {@link ngSanitize.$sanitize `$sanitize`} for usage. + */ + + /* + * HTML Parser By Misko Hevery (misko@hevery.com) + * based on: HTML Parser By John Resig (ejohn.org) + * Original code by Erik Arvidsson, Mozilla Public License + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + * + * // Use like so: + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + */ + + + /** + * @ngdoc service + * @name $sanitize + * @kind function + * + * @description + * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are + * then serialized back to properly escaped html string. This means that no unsafe input can make + * it into the returned string, however, since our parser is more strict than a typical browser + * parser, it's possible that some obscure input, which would be recognized as valid HTML by a + * browser, won't make it through the sanitizer. The input may also contain SVG markup. + * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and + * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. + * + * @param {string} html HTML input. + * @returns {string} Sanitized HTML. + * + * @example + + + +
+ Snippet: + + + + + + + + + + + + + + + + + + + + + + + + + +
DirectiveHowSourceRendered
ng-bind-htmlAutomatically uses $sanitize
<div ng-bind-html="snippet">
</div>
ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value +
<div ng-bind-html="deliberatelyTrustDangerousSnippet()">
+	 </div>
+
ng-bindAutomatically escapes
<div ng-bind="snippet">
</div>
+
+
+ + it('should sanitize the html snippet by default', function() { + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('

an html\nclick here\nsnippet

'); + }); + + it('should inline raw snippet if bound to a trusted value', function() { + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). + toBe("

an html\n" + + "click here\n" + + "snippet

"); + }); + + it('should escape snippet without any filter', function() { + expect(element(by.css('#bind-default div')).getInnerHtml()). + toBe("<p style=\"color:blue\">an html\n" + + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + + "snippet</p>"); + }); + + it('should update', function() { + element(by.model('snippet')).clear(); + element(by.model('snippet')).sendKeys('new text'); + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('new text'); + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( + 'new text'); + expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( + "new <b onclick=\"alert(1)\">text</b>"); + }); +
+
+ */ + function $SanitizeProvider() { + this.$get = ['$$sanitizeUri', function($$sanitizeUri) { + return function(html) { + var buf = []; + htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { + return !/^unsafe/.test($$sanitizeUri(uri, isImage)); + })); + return buf.join(''); + }; + }]; + } + + function sanitizeText(chars) { + var buf = []; + var writer = htmlSanitizeWriter(buf, angular.noop); + writer.chars(chars); + return buf.join(''); + } + + +// Regular Expressions for parsing tags and attributes + var START_TAG_REGEXP = + /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/, + END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/, + ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, + BEGIN_TAG_REGEXP = /^/g, + DOCTYPE_REGEXP = /]*?)>/i, + CDATA_REGEXP = //g, + SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + // Match everything outside of normal chars and " (quote character) + NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; + + +// Good source of info about elements and attributes +// http://dev.w3.org/html5/spec/Overview.html#semantics +// http://simon.html5.org/html-elements + +// Safe Void Elements - HTML5 +// http://dev.w3.org/html5/spec/Overview.html#void-elements + var voidElements = makeMap("area,br,col,hr,img,wbr"); + +// Elements that you can, intentionally, leave open (and which close themselves) +// http://dev.w3.org/html5/spec/Overview.html#optional-tags + var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), + optionalEndTagInlineElements = makeMap("rp,rt"), + optionalEndTagElements = angular.extend({}, + optionalEndTagInlineElements, + optionalEndTagBlockElements); + +// Safe Block Elements - HTML5 + var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + + "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + + "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); + +// Inline Elements - HTML5 + var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + + "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + + "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); + +// SVG Elements +// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements +// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted. +// They can potentially allow for arbitrary javascript to be executed. See #11290 + var svgElements = makeMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," + + "hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," + + "radialGradient,rect,stop,svg,switch,text,title,tspan,use"); + +// Special Elements (can contain anything) + var specialElements = makeMap("script,style"); + + var validElements = angular.extend({}, + voidElements, + blockElements, + inlineElements, + optionalEndTagElements, + svgElements); + +//Attributes that have href and hence need to be sanitized + var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href"); + + var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + + 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + + 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + + 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' + + 'valign,value,vspace,width'); + +// SVG attributes (without "id" and "name" attributes) +// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes + var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + + 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' + + 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' + + 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' + + 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' + + 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' + + 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' + + 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' + + 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' + + 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' + + 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' + + 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' + + 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' + + 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' + + 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true); + + var validAttrs = angular.extend({}, + uriAttrs, + svgAttrs, + htmlAttrs); + + function makeMap(str, lowercaseKeys) { + var obj = {}, items = str.split(','), i; + for (i = 0; i < items.length; i++) { + obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true; + } + return obj; + } + + + /** + * @example + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + * @param {string} html string + * @param {object} handler + */ + function htmlParser(html, handler) { + if (typeof html !== 'string') { + if (html === null || typeof html === 'undefined') { + html = ''; + } else { + html = '' + html; + } + } + var index, chars, match, stack = [], last = html, text; + stack.last = function() { return stack[stack.length - 1]; }; + + while (html) { + text = ''; + chars = true; + + // Make sure we're not in a script or style element + if (!stack.last() || !specialElements[stack.last()]) { + + // Comment + if (html.indexOf("", index) === index) { + if (handler.comment) handler.comment(html.substring(4, index)); + html = html.substring(index + 3); + chars = false; + } + // DOCTYPE + } else if (DOCTYPE_REGEXP.test(html)) { + match = html.match(DOCTYPE_REGEXP); + + if (match) { + html = html.replace(match[0], ''); + chars = false; + } + // end tag + } else if (BEGING_END_TAGE_REGEXP.test(html)) { + match = html.match(END_TAG_REGEXP); + + if (match) { + html = html.substring(match[0].length); + match[0].replace(END_TAG_REGEXP, parseEndTag); + chars = false; + } + + // start tag + } else if (BEGIN_TAG_REGEXP.test(html)) { + match = html.match(START_TAG_REGEXP); + + if (match) { + // We only have a valid start-tag if there is a '>'. + if (match[4]) { + html = html.substring(match[0].length); + match[0].replace(START_TAG_REGEXP, parseStartTag); + } + chars = false; + } else { + // no ending tag found --- this piece should be encoded as an entity. + text += '<'; + html = html.substring(1); + } + } + + if (chars) { + index = html.indexOf("<"); + + text += index < 0 ? html : html.substring(0, index); + html = index < 0 ? "" : html.substring(index); + + if (handler.chars) handler.chars(decodeEntities(text)); + } + + } else { + // IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w]. + html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), + function(all, text) { + text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); + + if (handler.chars) handler.chars(decodeEntities(text)); + + return ""; + }); + + parseEndTag("", stack.last()); + } + + if (html == last) { + throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + + "of html: {0}", html); + } + last = html; + } + + // Clean up any remaining tags + parseEndTag(); + + function parseStartTag(tag, tagName, rest, unary) { + tagName = angular.lowercase(tagName); + if (blockElements[tagName]) { + while (stack.last() && inlineElements[stack.last()]) { + parseEndTag("", stack.last()); + } + } + + if (optionalEndTagElements[tagName] && stack.last() == tagName) { + parseEndTag("", tagName); + } + + unary = voidElements[tagName] || !!unary; + + if (!unary) { + stack.push(tagName); + } + + var attrs = {}; + + rest.replace(ATTR_REGEXP, + function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { + var value = doubleQuotedValue + || singleQuotedValue + || unquotedValue + || ''; + + attrs[name] = decodeEntities(value); + }); + if (handler.start) handler.start(tagName, attrs, unary); + } + + function parseEndTag(tag, tagName) { + var pos = 0, i; + tagName = angular.lowercase(tagName); + if (tagName) { + // Find the closest opened tag of the same type + for (pos = stack.length - 1; pos >= 0; pos--) { + if (stack[pos] == tagName) break; + } + } + + if (pos >= 0) { + // Close all the open elements, up the stack + for (i = stack.length - 1; i >= pos; i--) + if (handler.end) handler.end(stack[i]); + + // Remove the open elements from the stack + stack.length = pos; + } + } + } + + var hiddenPre=document.createElement("pre"); + /** + * decodes all entities into regular string + * @param value + * @returns {string} A string with decoded entities. + */ + function decodeEntities(value) { + if (!value) { return ''; } + + hiddenPre.innerHTML = value.replace(//g, '>'); + } + + /** + * create an HTML/XML writer which writes to buffer + * @param {Array} buf use buf.jain('') to get out sanitized html string + * @returns {object} in the form of { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * } + */ + function htmlSanitizeWriter(buf, uriValidator) { + var ignore = false; + var out = angular.bind(buf, buf.push); + return { + start: function(tag, attrs, unary) { + tag = angular.lowercase(tag); + if (!ignore && specialElements[tag]) { + ignore = tag; + } + if (!ignore && validElements[tag] === true) { + out('<'); + out(tag); + angular.forEach(attrs, function(value, key) { + var lkey=angular.lowercase(key); + var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); + if (validAttrs[lkey] === true && + (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { + out(' '); + out(key); + out('="'); + out(encodeEntities(value)); + out('"'); + } + }); + out(unary ? '/>' : '>'); + } + }, + end: function(tag) { + tag = angular.lowercase(tag); + if (!ignore && validElements[tag] === true) { + out(''); + } + if (tag == ignore) { + ignore = false; + } + }, + chars: function(chars) { + if (!ignore) { + out(encodeEntities(chars)); + } } - if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) { - routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch; + }; + } + + +// define ngSanitize module and register $sanitize service + angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); + + /* global sanitizeText: false */ + + /** + * @ngdoc filter + * @name linky + * @kind function + * + * @description + * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and + * plain email address links. + * + * Requires the {@link ngSanitize `ngSanitize`} module to be installed. + * + * @param {string} text Input text. + * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. + * @returns {string} Html-linkified text. + * + * @usage + + * + * @example + + + +
+ Snippet: + + + + + + + + + + + + + + + + + + + + + +
FilterSourceRendered
linky filter +
<div ng-bind-html="snippet | linky">
</div>
+
+
+
linky target +
<div ng-bind-html="snippetWithTarget | linky:'_blank'">
</div>
+
+
+
no filter
<div ng-bind="snippet">
</div>
+ + + it('should linkify the snippet with urls', function() { + expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). + toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); + expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); + }); + + it('should not linkify snippet without the linky filter', function() { + expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). + toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); + expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); + }); + + it('should update', function() { + element(by.model('snippet')).clear(); + element(by.model('snippet')).sendKeys('new http://link.'); + expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). + toBe('new http://link.'); + expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); + expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) + .toBe('new http://link.'); + }); + + it('should work with the target property', function() { + expect(element(by.id('linky-target')). + element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). + toBe('http://angularjs.org/'); + expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); + }); + + + */ + angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { + var LINKY_URL_REGEXP = + /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, + MAILTO_REGEXP = /^mailto:/i; + + return function(text, target) { + if (!text) return text; + var match; + var raw = text; + var html = []; + var url; + var i; + while ((match = raw.match(LINKY_URL_REGEXP))) { + // We can not end in these as they are sometimes found at the end of the sentence + url = match[0]; + // if we did not match ftp/http/www/mailto then assume mailto + if (!match[2] && !match[4]) { + url = (match[3] ? 'http://' : 'mailto:') + url; + } + i = match.index; + addText(raw.substr(0, i)); + addLink(url, match[0].replace(MAILTO_REGEXP, '')); + raw = raw.substring(i + match[0].length); } - routes[path] = angular.extend( - routeCopy, - path && pathRegExp(path, routeCopy) - ); + addText(raw); + return $sanitize(html.join('')); + + function addText(text) { + if (!text) { + return; + } + html.push(sanitizeText(text)); + } + + function addLink(url, text) { + html.push(''); + addText(text); + html.push(''); + } + }; + }]); + + +})(window, window.angular); + +/* +Copyright (c) 2008-2015 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +var getJasmineRequireObj = (function (jasmineGlobal) { + var jasmineRequire; + + if (typeof module !== 'undefined' && module.exports) { + jasmineGlobal = global; + jasmineRequire = exports; + } else { + if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') { + jasmineGlobal = window; + } + jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; + } + + function getJasmineRequire() { + return jasmineRequire; + } + + getJasmineRequire().core = function(jRequire) { + var j$ = {}; + + jRequire.base(j$, jasmineGlobal); + j$.util = jRequire.util(); + j$.errors = jRequire.errors(); + j$.Any = jRequire.Any(j$); + j$.Anything = jRequire.Anything(j$); + j$.CallTracker = jRequire.CallTracker(); + j$.MockDate = jRequire.MockDate(); + j$.Clock = jRequire.Clock(); + j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); + j$.Env = jRequire.Env(j$); + j$.ExceptionFormatter = jRequire.ExceptionFormatter(); + j$.Expectation = jRequire.Expectation(); + j$.buildExpectationResult = jRequire.buildExpectationResult(); + j$.JsApiReporter = jRequire.JsApiReporter(); + j$.matchersUtil = jRequire.matchersUtil(j$); + j$.ObjectContaining = jRequire.ObjectContaining(j$); + j$.ArrayContaining = jRequire.ArrayContaining(j$); + j$.pp = jRequire.pp(j$); + j$.QueueRunner = jRequire.QueueRunner(j$); + j$.ReportDispatcher = jRequire.ReportDispatcher(); + j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); + j$.SpyStrategy = jRequire.SpyStrategy(); + j$.StringMatching = jRequire.StringMatching(j$); + j$.Suite = jRequire.Suite(j$); + j$.Timer = jRequire.Timer(); + j$.TreeProcessor = jRequire.TreeProcessor(); + j$.version = jRequire.version(); + + j$.matchers = jRequire.requireMatchers(jRequire, j$); + + return j$; + }; + + return getJasmineRequire; +})(this); + +getJasmineRequireObj().requireMatchers = function(jRequire, j$) { + var availableMatchers = [ + 'toBe', + 'toBeCloseTo', + 'toBeDefined', + 'toBeFalsy', + 'toBeGreaterThan', + 'toBeLessThan', + 'toBeNaN', + 'toBeNull', + 'toBeTruthy', + 'toBeUndefined', + 'toContain', + 'toEqual', + 'toHaveBeenCalled', + 'toHaveBeenCalledWith', + 'toMatch', + 'toThrow', + 'toThrowError' + ], + matchers = {}; + + for (var i = 0; i < availableMatchers.length; i++) { + var name = availableMatchers[i]; + matchers[name] = jRequire[name](j$); + } + + return matchers; +}; + +getJasmineRequireObj().base = function(j$, jasmineGlobal) { + j$.unimplementedMethod_ = function() { + throw new Error('unimplemented method'); + }; + + j$.MAX_PRETTY_PRINT_DEPTH = 40; + j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; + j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + + j$.getGlobal = function() { + return jasmineGlobal; + }; + + j$.getEnv = function(options) { + var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); + //jasmine. singletons in here (setTimeout blah blah). + return env; + }; + + j$.isArray_ = function(value) { + return j$.isA_('Array', value); + }; + + j$.isString_ = function(value) { + return j$.isA_('String', value); + }; + + j$.isNumber_ = function(value) { + return j$.isA_('Number', value); + }; + + j$.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; + }; + + j$.isDomNode = function(obj) { + return obj.nodeType > 0; + }; + + j$.fnNameFor = function(func) { + return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; + }; + + j$.any = function(clazz) { + return new j$.Any(clazz); + }; + + j$.anything = function() { + return new j$.Anything(); + }; + + j$.objectContaining = function(sample) { + return new j$.ObjectContaining(sample); + }; + + j$.stringMatching = function(expected) { + return new j$.StringMatching(expected); + }; + + j$.arrayContaining = function(sample) { + return new j$.ArrayContaining(sample); + }; + + j$.createSpy = function(name, originalFn) { + + var spyStrategy = new j$.SpyStrategy({ + name: name, + fn: originalFn, + getSpy: function() { return spy; } + }), + callTracker = new j$.CallTracker(), + spy = function() { + var callData = { + object: this, + args: Array.prototype.slice.apply(arguments) + }; + + callTracker.track(callData); + var returnValue = spyStrategy.exec.apply(this, arguments); + callData.returnValue = returnValue; + + return returnValue; + }; + + for (var prop in originalFn) { + if (prop === 'and' || prop === 'calls') { + throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); + } + + spy[prop] = originalFn[prop]; + } + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + j$.createSpyObj = function(baseName, methodNames) { + if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + if (!j$.isArray_(methodNames) || methodNames.length === 0) { + throw 'createSpyObj requires a non-empty array of method names to create spies for'; + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + } + return obj; + }; +}; + +getJasmineRequireObj().util = function() { + + var util = {}; + + util.inherit = function(childClass, parentClass) { + var Subclass = function() { + }; + Subclass.prototype = parentClass.prototype; + childClass.prototype = new Subclass(); + }; + + util.htmlEscape = function(str) { + if (!str) { + return str; + } + return str.replace(/&/g, '&') + .replace(//g, '>'); + }; + + util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) { + arrayOfArgs.push(args[i]); + } + return arrayOfArgs; + }; + + util.isUndefined = function(obj) { + return obj === void 0; + }; + + util.arrayContains = function(array, search) { + var i = array.length; + while (i--) { + if (array[i] === search) { + return true; + } + } + return false; + }; + + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + }; + + return util; +}; + +getJasmineRequireObj().Spec = function(j$) { + function Spec(attrs) { + this.expectationFactory = attrs.expectationFactory; + this.resultCallback = attrs.resultCallback || function() {}; + this.id = attrs.id; + this.description = attrs.description || ''; + this.queueableFn = attrs.queueableFn; + this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; + this.userContext = attrs.userContext || function() { return {}; }; + this.onStart = attrs.onStart || function() {}; + this.getSpecName = attrs.getSpecName || function() { return ''; }; + this.expectationResultFactory = attrs.expectationResultFactory || function() { }; + this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; + this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + if (!this.queueableFn.fn) { + this.pend(); + } + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [], + passedExpectations: [], + pendingReason: '' + }; + } + + Spec.prototype.addExpectationResult = function(passed, data, isError) { + var expectationResult = this.expectationResultFactory(data); + if (passed) { + this.result.passedExpectations.push(expectationResult); + } else { + this.result.failedExpectations.push(expectationResult); + + if (this.throwOnExpectationFailure && !isError) { + throw new j$.errors.ExpectationFailed(); + } + } + }; + + Spec.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Spec.prototype.execute = function(onComplete, enabled) { + var self = this; + + this.onStart(this); + + if (!this.isExecutable() || this.markedPending || enabled === false) { + complete(enabled); + return; + } + + var fns = this.beforeAndAfterFns(); + var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); + + this.queueRunnerFactory({ + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, + onComplete: complete, + userContext: this.userContext() + }); + + function complete(enabledAgain) { + self.result.status = self.status(enabledAgain); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + }; + + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(extractCustomPendingMessage(e)); + return; + } + + if (e instanceof j$.errors.ExpectationFailed) { + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }, true); + }; + + Spec.prototype.disable = function() { + this.disabled = true; + }; + + Spec.prototype.pend = function(message) { + this.markedPending = true; + if (message) { + this.result.pendingReason = message; + } + }; + + Spec.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Spec.prototype.status = function(enabled) { + if (this.disabled || enabled === false) { + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'passed'; + } + }; + + Spec.prototype.isExecutable = function() { + return !this.disabled; + }; + + Spec.prototype.getFullName = function() { + return this.getSpecName(this); + }; + + var extractCustomPendingMessage = function(e) { + var fullMessage = e.toString(), + boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), + boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; + + return fullMessage.substr(boilerplateEnd); + }; + + Spec.pendingSpecExceptionMessage = '=> marked Pending'; + + Spec.isPendingSpecException = function(e) { + return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); + }; + + return Spec; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Spec = jasmineRequire.Spec; +} + +getJasmineRequireObj().Env = function(j$) { + function Env(options) { + options = options || {}; + + var self = this; + var global = options.global || j$.getGlobal(); + + var totalSpecsDefined = 0; + + var catchExceptions = true; + + var realSetTimeout = j$.getGlobal().setTimeout; + var realClearTimeout = j$.getGlobal().clearTimeout; + this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global)); + + var runnableLookupTable = {}; + var runnableResources = {}; + + var currentSpec = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + var throwOnExpectationFailure = false; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + var currentRunnable = function() { + return currentSpec || currentSuite(); + }; + + var reporter = new j$.ReportDispatcher([ + 'jasmineStarted', + 'jasmineDone', + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone' + ]); + + this.specFilter = function() { + return true; + }; + + this.addCustomEqualityTester = function(tester) { + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } + }; + + j$.Expectation.addCoreMatchers(j$.matchers); + + var nextSpecId = 0; + var getNextSpecId = function() { + return 'spec' + nextSpecId++; + }; + + var nextSuiteId = 0; + var getNextSuiteId = function() { + return 'suite' + nextSuiteId++; + }; + + var expectationFactory = function(actual, spec) { + return j$.Expectation.Factory({ + util: j$.matchersUtil, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, + actual: actual, + addExpectationResult: addExpectationResult + }); + + function addExpectationResult(passed, result) { + return spec.addExpectationResult(passed, result); + } + }; + + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; + }; + + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + var beforeAndAfterFns = function(suite) { + return function() { + var befores = [], + afters = []; + + while(suite) { + befores = befores.concat(suite.beforeFns); + afters = afters.concat(suite.afterFns); + + suite = suite.parentSuite; + } + + return { + befores: befores.reverse(), + afters: afters + }; + }; + }; + + var getSpecName = function(spec, suite) { + return suite.getFullName() + ' ' + spec.description; + }; + + // TODO: we may just be able to pass in the fn instead of wrapping here + var buildExpectationResult = j$.buildExpectationResult, + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; + + return buildExpectationResult(attrs); + }; + + // TODO: fix this naming, and here's where the value comes in + this.catchExceptions = function(value) { + catchExceptions = !!value; + return catchExceptions; + }; + + this.catchingExceptions = function() { + return catchExceptions; + }; + + var maximumSpecCallbackDepth = 20; + var currentSpecCallbackDepth = 0; + + function clearStack(fn) { + currentSpecCallbackDepth++; + if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { + currentSpecCallbackDepth = 0; + realSetTimeout(fn, 0); + } else { + fn(); + } + } + + var catchException = function(e) { + return j$.Spec.isPendingSpecException(e) || catchExceptions; + }; + + this.throwOnExpectationFailure = function(value) { + throwOnExpectationFailure = !!value; + }; + + this.throwingExpectationFailures = function() { + return throwOnExpectationFailure; + }; + + var queueRunnerFactory = function(options) { + options.catchException = catchException; + options.clearStack = options.clearStack || clearStack; + options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; + options.fail = self.fail; + + new j$.QueueRunner(options).execute(); + }; + + var topSuite = new j$.Suite({ + env: this, + id: getNextSuiteId(), + description: 'Jasmine__TopLevel__Suite', + queueRunner: queueRunnerFactory + }); + runnableLookupTable[topSuite.id] = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; + + this.topSuite = function() { + return topSuite; + }; + + this.execute = function(runnablesToRun) { + if(!runnablesToRun) { + if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } + } + var processor = new j$.TreeProcessor({ + tree: topSuite, + runnableIds: runnablesToRun, + queueRunnerFactory: queueRunnerFactory, + nodeStart: function(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + }, + nodeComplete: function(suite, result) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + } + currentlyExecutingSuites.pop(); + reporter.suiteDone(result); + } + }); + + if(!processor.processTree().valid) { + throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times'); + } + + reporter.jasmineStarted({ + totalSpecsDefined: totalSpecsDefined + }); + + processor.execute(reporter.jasmineDone); + }; + + this.addReporter = function(reporterToAdd) { + reporter.addReporter(reporterToAdd); + }; + + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } + return runnableResources[currentRunnable().id].spies; + }}); + + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); + }; + + var suiteFactory = function(description) { + var suite = new j$.Suite({ + env: self, + id: getNextSuiteId(), + description: description, + parentSuite: currentDeclarationSuite, + expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, + throwOnExpectationFailure: throwOnExpectationFailure + }); + + runnableLookupTable[suite.id] = suite; + return suite; + }; + + this.describe = function(description, specDefinitions) { + var suite = suiteFactory(description); + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + + this.xdescribe = function(description, specDefinitions) { + var suite = this.describe(description, specDefinitions); + suite.disable(); + return suite; + }; + + var focusedRunnables = []; + + this.fdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { + var parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + self.it('encountered a declaration exception', function() { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + } + + function findFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return suite.id; + } + suite = suite.parentSuite; + } + + return null; + } + + function unfocusAncestor() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } + } + + var specFactory = function(description, fn, suite, timeout) { + totalSpecsDefined++; + var spec = new j$.Spec({ + id: getNextSpecId(), + beforeAndAfterFns: beforeAndAfterFns(suite), + expectationFactory: expectationFactory, + resultCallback: specResultCallback, + getSpecName: function(spec) { + return getSpecName(spec, suite); + }, + onStart: specStarted, + description: description, + expectationResultFactory: expectationResultFactory, + queueRunnerFactory: queueRunnerFactory, + userContext: function() { return suite.clonedSharedUserContext(); }, + queueableFn: { + fn: fn, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }, + throwOnExpectationFailure: throwOnExpectationFailure + }); + + runnableLookupTable[spec.id] = spec; + + if (!self.specFilter(spec)) { + spec.disable(); + } + + return spec; + + function specResultCallback(result) { + clearResourcesForRunnable(spec.id); + currentSpec = null; + reporter.specDone(result); + } + + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } + }; + + this.it = function(description, fn, timeout) { + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); + currentDeclarationSuite.addChild(spec); + return spec; + }; + + this.xit = function() { + var spec = this.it.apply(this, arguments); + spec.pend(); + return spec; + }; + + this.fit = function(){ + var spec = this.it.apply(this, arguments); + + focusedRunnables.push(spec.id); + unfocusAncestor(); + return spec; + }; + + this.expect = function(actual) { + if (!currentRunnable()) { + throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); + } + + return currentRunnable().expect(actual); + }; + + this.beforeEach = function(beforeEachFunction, timeout) { + currentDeclarationSuite.beforeEach({ + fn: beforeEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.beforeAll = function(beforeAllFunction, timeout) { + currentDeclarationSuite.beforeAll({ + fn: beforeAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterEach = function(afterEachFunction, timeout) { + currentDeclarationSuite.afterEach({ + fn: afterEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterAll = function(afterAllFunction, timeout) { + currentDeclarationSuite.afterAll({ + fn: afterAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.pending = function(message) { + var fullMessage = j$.Spec.pendingSpecExceptionMessage; + if(message) { + fullMessage += message; + } + throw fullMessage; + }; + + this.fail = function(error) { + var message = 'Failed'; + if (error) { + message += ': '; + message += error.message || error; + } + + currentRunnable().addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + message: message, + error: error && error.message ? error : null + }); + }; + } + + return Env; +}; + +getJasmineRequireObj().JsApiReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function JsApiReporter(options) { + var timer = options.timer || noopTimer, + status = 'loaded'; + + this.started = false; + this.finished = false; + + this.jasmineStarted = function() { + this.started = true; + status = 'started'; + timer.start(); + }; + + var executionTime; + + this.jasmineDone = function() { + this.finished = true; + executionTime = timer.elapsed(); + status = 'done'; + }; + + this.status = function() { + return status; + }; + + var suites = [], + suites_hash = {}; + + this.suiteStarted = function(result) { + suites_hash[result.id] = result; + }; + + this.suiteDone = function(result) { + storeSuite(result); + }; + + this.suiteResults = function(index, length) { + return suites.slice(index, index + length); + }; + + function storeSuite(result) { + suites.push(result); + suites_hash[result.id] = result; + } + + this.suites = function() { + return suites_hash; + }; + + var specs = []; + + this.specDone = function(result) { + specs.push(result); + }; + + this.specResults = function(index, length) { + return specs.slice(index, index + length); + }; + + this.specs = function() { + return specs; + }; + + this.executionTime = function() { + return executionTime; + }; + + } + + return JsApiReporter; +}; + +getJasmineRequireObj().CallTracker = function() { + + function CallTracker() { + var calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + var call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + var callArgs = []; + for(var i = 0; i < calls.length; i++){ + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } + + return CallTracker; +}; + +getJasmineRequireObj().Clock = function() { + function Clock(global, delayedFunctionSchedulerFactory, mockDate) { + var self = this, + realTimingFunctions = { + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval + }, + fakeTimingFunctions = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval + }, + installed = false, + delayedFunctionScheduler, + timer; + + + self.install = function() { + if(!originalTimingFunctionsIntact()) { + throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?'); + } + replace(global, fakeTimingFunctions); + timer = fakeTimingFunctions; + delayedFunctionScheduler = delayedFunctionSchedulerFactory(); + installed = true; + + return self; + }; + + self.uninstall = function() { + delayedFunctionScheduler = null; + mockDate.uninstall(); + replace(global, realTimingFunctions); + + timer = realTimingFunctions; + installed = false; + }; + + self.withMock = function(closure) { + this.install(); + try { + closure(); + } finally { + this.uninstall(); + } + }; + + self.mockDate = function(initialDate) { + mockDate.install(initialDate); + }; + + self.setTimeout = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); + } + return timer.setTimeout(fn, delay); + } + return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); + }; + + self.setInterval = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); + } + return timer.setInterval(fn, delay); + } + return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); + }; + + self.clearTimeout = function(id) { + return Function.prototype.call.apply(timer.clearTimeout, [global, id]); + }; + + self.clearInterval = function(id) { + return Function.prototype.call.apply(timer.clearInterval, [global, id]); + }; + + self.tick = function(millis) { + if (installed) { + mockDate.tick(millis); + delayedFunctionScheduler.tick(millis); + } else { + throw new Error('Mock clock is not installed, use jasmine.clock().install()'); + } + }; + + return self; + + function originalTimingFunctionsIntact() { + return global.setTimeout === realTimingFunctions.setTimeout && + global.clearTimeout === realTimingFunctions.clearTimeout && + global.setInterval === realTimingFunctions.setInterval && + global.clearInterval === realTimingFunctions.clearInterval; + } + + function legacyIE() { + //if these methods are polyfilled, apply will be present + return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; + } + + function replace(dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } + } + + function setTimeout(fn, delay) { + return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); + } + + function clearTimeout(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function setInterval(fn, interval) { + return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); + } + + function clearInterval(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function argSlice(argsObj, n) { + return Array.prototype.slice.call(argsObj, n); + } + } + + return Clock; +}; + +getJasmineRequireObj().DelayedFunctionScheduler = function() { + function DelayedFunctionScheduler() { + var self = this; + var scheduledLookup = []; + var scheduledFunctions = {}; + var currentTime = 0; + var delayedFnCount = 0; + + self.tick = function(millis) { + millis = millis || 0; + var endTime = currentTime + millis; + + runScheduledFunctions(endTime); + currentTime = endTime; + }; + + self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { + var f; + if (typeof(funcToCall) === 'string') { + /* jshint evil: true */ + f = function() { return eval(funcToCall); }; + /* jshint evil: false */ + } else { + f = funcToCall; + } + + millis = millis || 0; + timeoutKey = timeoutKey || ++delayedFnCount; + runAtMillis = runAtMillis || (currentTime + millis); + + var funcToSchedule = { + runAtMillis: runAtMillis, + funcToCall: f, + recurring: recurring, + params: params, + timeoutKey: timeoutKey, + millis: millis + }; + + if (runAtMillis in scheduledFunctions) { + scheduledFunctions[runAtMillis].push(funcToSchedule); + } else { + scheduledFunctions[runAtMillis] = [funcToSchedule]; + scheduledLookup.push(runAtMillis); + scheduledLookup.sort(function (a, b) { + return a - b; + }); + } + + return timeoutKey; + }; + + self.removeFunctionWithId = function(timeoutKey) { + for (var runAtMillis in scheduledFunctions) { + var funcs = scheduledFunctions[runAtMillis]; + var i = indexOfFirstToPass(funcs, function (func) { + return func.timeoutKey === timeoutKey; + }); + + if (i > -1) { + if (funcs.length === 1) { + delete scheduledFunctions[runAtMillis]; + deleteFromLookup(runAtMillis); + } else { + funcs.splice(i, 1); + } + + // intervals get rescheduled when executed, so there's never more + // than a single scheduled function with a given timeoutKey + break; + } + } + }; + + return self; + + function indexOfFirstToPass(array, testFn) { + var index = -1; + + for (var i = 0; i < array.length; ++i) { + if (testFn(array[i])) { + index = i; + break; + } + } + + return index; + } + + function deleteFromLookup(key) { + var value = Number(key); + var i = indexOfFirstToPass(scheduledLookup, function (millis) { + return millis === value; + }); + + if (i > -1) { + scheduledLookup.splice(i, 1); + } + } + + function reschedule(scheduledFn) { + self.scheduleFunction(scheduledFn.funcToCall, + scheduledFn.millis, + scheduledFn.params, + true, + scheduledFn.timeoutKey, + scheduledFn.runAtMillis + scheduledFn.millis); + } + + function forEachFunction(funcsToRun, callback) { + for (var i = 0; i < funcsToRun.length; ++i) { + callback(funcsToRun[i]); + } + } + + function runScheduledFunctions(endTime) { + if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { + return; + } + + do { + currentTime = scheduledLookup.shift(); + + var funcsToRun = scheduledFunctions[currentTime]; + delete scheduledFunctions[currentTime]; + + forEachFunction(funcsToRun, function(funcToRun) { + if (funcToRun.recurring) { + reschedule(funcToRun); + } + }); + + forEachFunction(funcsToRun, function(funcToRun) { + funcToRun.funcToCall.apply(null, funcToRun.params || []); + }); + } while (scheduledLookup.length > 0 && + // checking first if we're out of time prevents setTimeout(0) + // scheduled in a funcToRun from forcing an extra iteration + currentTime !== endTime && + scheduledLookup[0] <= endTime); + } + } + + return DelayedFunctionScheduler; +}; + +getJasmineRequireObj().ExceptionFormatter = function() { + function ExceptionFormatter() { + this.message = function(error) { + var message = ''; + + if (error.name && error.message) { + message += error.name + ': ' + error.message; + } else { + message += error.toString() + ' thrown'; + } + + if (error.fileName || error.sourceURL) { + message += ' in ' + (error.fileName || error.sourceURL); + } + + if (error.line || error.lineNumber) { + message += ' (line ' + (error.line || error.lineNumber) + ')'; + } + + return message; + }; + + this.stack = function(error) { + return error ? error.stack : null; + }; + } + + return ExceptionFormatter; +}; + +getJasmineRequireObj().Expectation = function() { + + function Expectation(options) { + this.util = options.util || { buildFailureMessage: function() {} }; + this.customEqualityTesters = options.customEqualityTesters || []; + this.actual = options.actual; + this.addExpectationResult = options.addExpectationResult || function(){}; + this.isNot = options.isNot; + + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); + } + } + + Expectation.prototype.wrapCompare = function(name, matcherFactory) { + return function() { + var args = Array.prototype.slice.call(arguments, 0), + expected = args.slice(0), + message = ''; + + args.unshift(this.actual); + + var matcher = matcherFactory(this.util, this.customEqualityTesters), + matcherCompare = matcher.compare; + + function defaultNegativeCompare() { + var result = matcher.compare.apply(null, args); + result.pass = !result.pass; + return result; + } + + if (this.isNot) { + matcherCompare = matcher.negativeCompare || defaultNegativeCompare; + } + + var result = matcherCompare.apply(null, args); + + if (!result.pass) { + if (!result.message) { + args.unshift(this.isNot); + args.unshift(name); + message = this.util.buildFailureMessage.apply(null, args); + } else { + if (Object.prototype.toString.apply(result.message) === '[object Function]') { + message = result.message(); + } else { + message = result.message; + } + } + } + + if (expected.length == 1) { + expected = expected[0]; + } + + // TODO: how many of these params are needed? + this.addExpectationResult( + result.pass, + { + matcherName: name, + passed: result.pass, + message: message, + actual: this.actual, + expected: expected // TODO: this may need to be arrayified/sliced + } + ); + }; + }; + + Expectation.addCoreMatchers = function(matchers) { + var prototype = Expectation.prototype; + for (var matcherName in matchers) { + var matcher = matchers[matcherName]; + prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); + } + }; + + Expectation.Factory = function(options) { + options = options || {}; + + var expect = new Expectation(options); + + // TODO: this would be nice as its own Object - NegativeExpectation + // TODO: copy instead of mutate options + options.isNot = true; + expect.not = new Expectation(options); + + return expect; + }; + + return Expectation; +}; + +//TODO: expectation result may make more sense as a presentation of an expectation. +getJasmineRequireObj().buildExpectationResult = function() { + function buildExpectationResult(options) { + var messageFormatter = options.messageFormatter || function() {}, + stackFormatter = options.stackFormatter || function() {}; + + var result = { + matcherName: options.matcherName, + message: message(), + stack: stack(), + passed: options.passed + }; + + if(!result.passed) { + result.expected = options.expected; + result.actual = options.actual; + } + + return result; + + function message() { + if (options.passed) { + return 'Passed.'; + } else if (options.message) { + return options.message; + } else if (options.error) { + return messageFormatter(options.error); + } + return ''; + } + + function stack() { + if (options.passed) { + return ''; + } + + var error = options.error; + if (!error) { + try { + throw new Error(message()); + } catch (e) { + error = e; + } + } + return stackFormatter(error); + } + } + + return buildExpectationResult; +}; + +getJasmineRequireObj().MockDate = function() { + function MockDate(global) { + var self = this; + var currentTime = 0; + + if (!global || !global.Date) { + self.install = function() {}; + self.tick = function() {}; + self.uninstall = function() {}; + return self; + } + + var GlobalDate = global.Date; + + self.install = function(mockDate) { + if (mockDate instanceof GlobalDate) { + currentTime = mockDate.getTime(); + } else { + currentTime = new GlobalDate().getTime(); + } + + global.Date = FakeDate; + }; + + self.tick = function(millis) { + millis = millis || 0; + currentTime = currentTime + millis; + }; + + self.uninstall = function() { + currentTime = 0; + global.Date = GlobalDate; + }; + + createDateProperties(); + + return self; + + function FakeDate() { + switch(arguments.length) { + case 0: + return new GlobalDate(currentTime); + case 1: + return new GlobalDate(arguments[0]); + case 2: + return new GlobalDate(arguments[0], arguments[1]); + case 3: + return new GlobalDate(arguments[0], arguments[1], arguments[2]); + case 4: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); + case 5: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4]); + case 6: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5]); + default: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5], arguments[6]); + } + } + + function createDateProperties() { + FakeDate.prototype = GlobalDate.prototype; + + FakeDate.now = function() { + if (GlobalDate.now) { + return currentTime; + } else { + throw new Error('Browser does not support Date.now()'); + } + }; + + FakeDate.toSource = GlobalDate.toSource; + FakeDate.toString = GlobalDate.toString; + FakeDate.parse = GlobalDate.parse; + FakeDate.UTC = GlobalDate.UTC; + } + } + + return MockDate; +}; + +getJasmineRequireObj().pp = function(j$) { + + function PrettyPrinter() { + this.ppNestLevel_ = 0; + this.seen = []; + } + + PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (j$.util.isUndefined(value)) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === 0 && 1/value === -Infinity) { + this.emitScalar('-0'); + } else if (value === j$.getGlobal()) { + this.emitScalar(''); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (j$.isSpy(value)) { + this.emitScalar('spy on ' + value.and.identity()); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (j$.util.arrayContains(this.seen, value)) { + this.emitScalar(''); + } else if (j$.isArray_(value) || j$.isA_('Object', value)) { + this.seen.push(value); + if (j$.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + this.seen.pop(); + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } + }; + + PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } + fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && + obj.__lookupGetter__(property) !== null) : false); + } + }; + + PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; + + function StringPrettyPrinter() { + PrettyPrinter.call(this); + + this.string = ''; + } + + j$.util.inherit(StringPrettyPrinter, PrettyPrinter); + + StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); + }; + + StringPrettyPrinter.prototype.emitString = function(value) { + this.append('\'' + value + '\''); + }; + + StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + this.append('Array'); + return; + } + var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); + this.append('[ '); + for (var i = 0; i < length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + if(array.length > length){ + this.append(', ...'); + } + + var self = this; + var first = array.length === 0; + this.iterateObject(array, function(property, isGetter) { + if (property.match(/^\d+$/)) { + return; + } + + if (first) { + first = false; + } else { + self.append(', '); + } + + self.formatProperty(array, property, isGetter); + }); + + this.append(' ]'); + }; + + StringPrettyPrinter.prototype.emitObject = function(obj) { + var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null'; + this.append(constructorName); + + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + return; + } + + var self = this; + this.append('({ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.formatProperty(obj, property, isGetter); + }); + + this.append(' })'); + }; + + StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { + this.append(property); + this.append(': '); + if (isGetter) { + this.append(''); + } else { + this.format(obj[property]); + } + }; + + StringPrettyPrinter.prototype.append = function(value) { + this.string += value; + }; + + return function(value) { + var stringPrettyPrinter = new StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; + }; +}; + +getJasmineRequireObj().QueueRunner = function(j$) { + + function once(fn) { + var called = false; + return function() { + if (!called) { + called = true; + fn(); + } + }; + } + + function QueueRunner(attrs) { + this.queueableFns = attrs.queueableFns || []; + this.onComplete = attrs.onComplete || function() {}; + this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.onException = attrs.onException || function() {}; + this.catchException = attrs.catchException || function() { return true; }; + this.userContext = attrs.userContext || {}; + this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; + this.fail = attrs.fail || function() {}; + } + + QueueRunner.prototype.execute = function() { + this.run(this.queueableFns, 0); + }; + + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, + self = this, + iterativeIndex; + + + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + attemptAsync(queueableFn); + return; + } else { + attemptSync(queueableFn); + } + } + + var runnerDone = iterativeIndex >= length; + + if (runnerDone) { + this.clearStack(this.onComplete); + } + + function attemptSync(queueableFn) { + try { + queueableFn.fn.call(self.userContext); + } catch (e) { + handleException(e, queueableFn); + } + } + + function attemptAsync(queueableFn) { + var clearTimeout = function () { + Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); + }, + next = once(function () { + clearTimeout(timeoutId); + self.run(queueableFns, iterativeIndex + 1); + }), + timeoutId; + + next.fail = function() { + self.fail.apply(null, arguments); + next(); + }; + + if (queueableFn.timeout) { + timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() { + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error, queueableFn); + next(); + }, queueableFn.timeout()]]); + } + + try { + queueableFn.fn.call(self.userContext, next); + } catch (e) { + handleException(e, queueableFn); + next(); + } + } + + function onException(e, queueableFn) { + self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e, queueableFn); + if (!self.catchException(e)) { + //TODO: set a var when we catch an exception and + //use a finally block to close the loop in a nice way.. + throw e; + } + } + }; + + return QueueRunner; +}; + +getJasmineRequireObj().ReportDispatcher = function() { + function ReportDispatcher(methods) { + + var dispatchedMethods = methods || []; + + for (var i = 0; i < dispatchedMethods.length; i++) { + var method = dispatchedMethods[i]; + this[method] = (function(m) { + return function() { + dispatch(m, arguments); + }; + }(method)); + } + + var reporters = []; + + this.addReporter = function(reporter) { + reporters.push(reporter); + }; + + return this; + + function dispatch(method, args) { + for (var i = 0; i < reporters.length; i++) { + var reporter = reporters[i]; + if (reporter[method]) { + reporter[method].apply(reporter, args); + } + } + } + } + + return ReportDispatcher; +}; + + +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(methodName)) { + throw new Error('No method name supplied'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; - // create redirection for trailing slashes - if (path) { - var redirectPath = (path[path.length - 1] == '/') - ? path.substr(0, path.length - 1) - : path + '/'; + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } - routes[redirectPath] = angular.extend( - {redirectTo: path}, - pathRegExp(redirectPath, routeCopy) - ); - } + return SpyRegistry; +}; - return this; - }; +getJasmineRequireObj().SpyStrategy = function() { - /** - * @ngdoc property - * @name $routeProvider#caseInsensitiveMatch - * @description - * - * A boolean property indicating if routes defined - * using this provider should be matched using a case insensitive - * algorithm. Defaults to `false`. - */ - this.caseInsensitiveMatch = false; + function SpyStrategy(options) { + options = options || {}; - /** - * @param path {string} path - * @param opts {Object} options - * @return {?Object} - * - * @description - * Normalizes the given path, returning a regular expression - * and the original path. - * - * Inspired by pathRexp in visionmedia/express/lib/utils.js. - */ - function pathRegExp(path, opts) { - var insensitive = opts.caseInsensitiveMatch, - ret = { - originalPath: path, - regexp: path - }, - keys = ret.keys = []; + var identity = options.name || 'unknown', + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; - path = path - .replace(/([().])/g, '\\$1') - .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { - var optional = option === '?' ? option : null; - var star = option === '*' ? option : null; - keys.push({ name: key, optional: !!optional }); - slash = slash || ''; - return '' - + (optional ? '' : slash) - + '(?:' - + (optional ? slash : '') - + (star && '(.+?)' || '([^/]+)') - + (optional || '') - + ')' - + (optional || ''); - }) - .replace(/([\/$\*])/g, '\\$1'); + this.identity = function() { + return identity; + }; - ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); - return ret; - } + this.exec = function() { + return plan.apply(this, arguments); + }; - /** - * @ngdoc method - * @name $routeProvider#otherwise - * - * @description - * Sets route definition that will be used on route change when no other route definition - * is matched. - * - * @param {Object|string} params Mapping information to be assigned to `$route.current`. - * If called with a string, the value maps to `redirectTo`. - * @returns {Object} self - */ - this.otherwise = function(params) { - if (typeof params === 'string') { - params = {redirectTo: params}; - } - this.when(null, params); - return this; - }; + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + this.returnValue = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; - this.$get = ['$rootScope', - '$location', - '$routeParams', - '$q', - '$injector', - '$templateRequest', - '$sce', - function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { + this.returnValues = function() { + var values = Array.prototype.slice.call(arguments); + plan = function () { + return values.shift(); + }; + return getSpy(); + }; - /** - * @ngdoc service - * @name $route - * @requires $location - * @requires $routeParams - * - * @property {Object} current Reference to the current route definition. - * The route definition contains: - * - * - `controller`: The controller constructor as define in route definition. - * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for - * controller instantiation. The `locals` contain - * the resolved values of the `resolve` map. Additionally the `locals` also contain: - * - * - `$scope` - The current route scope. - * - `$template` - The current route template HTML. - * - * @property {Object} routes Object with all route configuration Objects as its properties. - * - * @description - * `$route` is used for deep-linking URLs to controllers and views (HTML partials). - * It watches `$location.url()` and tries to map the path to an existing route definition. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. - * - * The `$route` service is typically used in conjunction with the - * {@link ngRoute.directive:ngView `ngView`} directive and the - * {@link ngRoute.$routeParams `$routeParams`} service. - * - * @example - * This example shows how changing the URL hash causes the `$route` to match a route against the - * URL, and the `ngView` pulls in the partial. - * - * - * - *
- * Choose: - * Moby | - * Moby: Ch1 | - * Gatsby | - * Gatsby: Ch4 | - * Scarlet Letter
- * - *
- * - *
- * - *
$location.path() = {{$location.path()}}
- *
$route.current.templateUrl = {{$route.current.templateUrl}}
- *
$route.current.params = {{$route.current.params}}
- *
$route.current.scope.name = {{$route.current.scope.name}}
- *
$routeParams = {{$routeParams}}
- *
- *
- * - * - * controller: {{name}}
- * Book Id: {{params.bookId}}
- *
- * - * - * controller: {{name}}
- * Book Id: {{params.bookId}}
- * Chapter Id: {{params.chapterId}} - *
- * - * - * angular.module('ngRouteExample', ['ngRoute']) - * - * .controller('MainController', function($scope, $route, $routeParams, $location) { - * $scope.$route = $route; - * $scope.$location = $location; - * $scope.$routeParams = $routeParams; - * }) - * - * .controller('BookController', function($scope, $routeParams) { - * $scope.name = "BookController"; - * $scope.params = $routeParams; - * }) - * - * .controller('ChapterController', function($scope, $routeParams) { - * $scope.name = "ChapterController"; - * $scope.params = $routeParams; - * }) - * - * .config(function($routeProvider, $locationProvider) { - * $routeProvider - * .when('/Book/:bookId', { - * templateUrl: 'book.html', - * controller: 'BookController', - * resolve: { - * // I will cause a 1 second delay - * delay: function($q, $timeout) { - * var delay = $q.defer(); - * $timeout(delay.resolve, 1000); - * return delay.promise; - * } - * } - * }) - * .when('/Book/:bookId/ch/:chapterId', { - * templateUrl: 'chapter.html', - * controller: 'ChapterController' - * }); - * - * // configure html5 to get links working on jsfiddle - * $locationProvider.html5Mode(true); - * }); - * - * - * - * - * it('should load and compile correct template', function() { - * element(by.linkText('Moby: Ch1')).click(); - * var content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller\: ChapterController/); - * expect(content).toMatch(/Book Id\: Moby/); - * expect(content).toMatch(/Chapter Id\: 1/); - * - * element(by.partialLinkText('Scarlet')).click(); - * - * content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller\: BookController/); - * expect(content).toMatch(/Book Id\: Scarlet/); - * }); - * - *
- */ + this.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + plan = function() { + throw error; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + plan = fn; + return getSpy(); + }; + + this.stub = function(fn) { + plan = function() {}; + return getSpy(); + }; + } + + return SpyStrategy; +}; + +getJasmineRequireObj().Suite = function(j$) { + function Suite(attrs) { + this.env = attrs.env; + this.id = attrs.id; + this.parentSuite = attrs.parentSuite; + this.description = attrs.description; + this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + this.beforeFns = []; + this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; + this.disabled = false; + + this.children = []; + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [] + }; + } + + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + if (parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + } + return fullName; + }; + + Suite.prototype.disable = function() { + this.disabled = true; + }; + + Suite.prototype.beforeEach = function(fn) { + this.beforeFns.unshift(fn); + }; + + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + + Suite.prototype.afterEach = function(fn) { + this.afterFns.unshift(fn); + }; + + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + + Suite.prototype.addChild = function(child) { + this.children.push(child); + }; + + Suite.prototype.status = function() { + if (this.disabled) { + return 'disabled'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'finished'; + } + }; + + Suite.prototype.isExecutable = function() { + return !this.disabled; + }; + + Suite.prototype.canBeReentered = function() { + return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; + }; + + Suite.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + Suite.prototype.onException = function() { + if (arguments[0] instanceof j$.errors.ExpectationFailed) { + return; + } + + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } + } + }; + + Suite.prototype.addExpectationResult = function () { + if(isAfterAll(this.children) && isFailure(arguments)){ + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + if(this.throwOnExpectationFailure) { + throw new j$.errors.ExpectationFailed(); + } + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + try { + child.addExpectationResult.apply(child, arguments); + } catch(e) { + // keep going + } + } + } + }; + + function isAfterAll(children) { + return children && children[0].result.status; + } + + function isFailure(args) { + return !args[0]; + } + + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; + } + } + + return clonedObj; + } + + return Suite; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Suite = jasmineRequire.Suite; +} + +getJasmineRequireObj().Timer = function() { + var defaultNow = (function(Date) { + return function() { return new Date().getTime(); }; + })(Date); + + function Timer(options) { + options = options || {}; + + var now = options.now || defaultNow, + startTime; + + this.start = function() { + startTime = now(); + }; + + this.elapsed = function() { + return now() - startTime; + }; + } + + return Timer; +}; + +getJasmineRequireObj().TreeProcessor = function() { + function TreeProcessor(attrs) { + var tree = attrs.tree, + runnableIds = attrs.runnableIds, + queueRunnerFactory = attrs.queueRunnerFactory, + nodeStart = attrs.nodeStart || function() {}, + nodeComplete = attrs.nodeComplete || function() {}, + stats = { valid: true }, + processed = false, + defaultMin = Infinity, + defaultMax = 1 - Infinity; + + this.processTree = function() { + processNode(tree, false); + processed = true; + return stats; + }; + + this.execute = function(done) { + if (!processed) { + this.processTree(); + } + + if (!stats.valid) { + throw 'invalid order'; + } + + var childFns = wrapChildren(tree, 0); + + queueRunnerFactory({ + queueableFns: childFns, + userContext: tree.sharedUserContext(), + onException: function() { + tree.onException.apply(tree, arguments); + }, + onComplete: done + }); + }; + + function runnableIndex(id) { + for (var i = 0; i < runnableIds.length; i++) { + if (runnableIds[i] === id) { + return i; + } + } + } + + function processNode(node, parentEnabled) { + var executableIndex = runnableIndex(node.id); + + if (executableIndex !== undefined) { + parentEnabled = true; + } + + parentEnabled = parentEnabled && node.isExecutable(); + + if (!node.children) { + stats[node.id] = { + executable: parentEnabled && node.isExecutable(), + segments: [{ + index: 0, + owner: node, + nodes: [node], + min: startingMin(executableIndex), + max: startingMax(executableIndex) + }] + }; + } else { + var hasExecutableChild = false; + + for (var i = 0; i < node.children.length; i++) { + var child = node.children[i]; + + processNode(child, parentEnabled); + + if (!stats.valid) { + return; + } + + var childStats = stats[child.id]; + + hasExecutableChild = hasExecutableChild || childStats.executable; + } + + stats[node.id] = { + executable: hasExecutableChild + }; + + segmentChildren(node, stats[node.id], executableIndex); + + if (!node.canBeReentered() && stats[node.id].segments.length > 1) { + stats = { valid: false }; + } + } + } - /** - * @ngdoc event - * @name $route#$routeChangeStart - * @eventType broadcast on root scope - * @description - * Broadcasted before a route change. At this point the route services starts - * resolving all of the dependencies needed for the route change to occur. - * Typically this involves fetching the view template as well as any dependencies - * defined in `resolve` route property. Once all of the dependencies are resolved - * `$routeChangeSuccess` is fired. - * - * The route change (and the `$location` change that triggered it) can be prevented - * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} - * for more details about event object. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} next Future route information. - * @param {Route} current Current route information. - */ + function startingMin(executableIndex) { + return executableIndex === undefined ? defaultMin : executableIndex; + } - /** - * @ngdoc event - * @name $route#$routeChangeSuccess - * @eventType broadcast on root scope - * @description - * Broadcasted after a route change has happened successfully. - * The `resolve` dependencies are now available in the `current.locals` property. - * - * {@link ngRoute.directive:ngView ngView} listens for the directive - * to instantiate the controller and render the view. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} current Current route information. - * @param {Route|Undefined} previous Previous route information, or undefined if current is - * first route entered. - */ + function startingMax(executableIndex) { + return executableIndex === undefined ? defaultMax : executableIndex; + } - /** - * @ngdoc event - * @name $route#$routeChangeError - * @eventType broadcast on root scope - * @description - * Broadcasted if any of the resolve promises are rejected. - * - * @param {Object} angularEvent Synthetic event object - * @param {Route} current Current route information. - * @param {Route} previous Previous route information. - * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. - */ + function segmentChildren(node, nodeStats, executableIndex) { + var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, + result = [currentSegment], + lastMax = defaultMax, + orderedChildSegments = orderChildSegments(node.children); - /** - * @ngdoc event - * @name $route#$routeUpdate - * @eventType broadcast on root scope - * @description - * The `reloadOnSearch` property has been set to false, and we are reusing the same - * instance of the Controller. - * - * @param {Object} angularEvent Synthetic event object - * @param {Route} current Current/previous route information. - */ + function isSegmentBoundary(minIndex) { + return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1; + } - var forceReload = false, - preparedRoute, - preparedRouteIsUpdateOnly, - $route = { - routes: routes, + for (var i = 0; i < orderedChildSegments.length; i++) { + var childSegment = orderedChildSegments[i], + maxIndex = childSegment.max, + minIndex = childSegment.min; - /** - * @ngdoc method - * @name $route#reload - * - * @description - * Causes `$route` service to reload the current route even if - * {@link ng.$location $location} hasn't changed. - * - * As a result of that, {@link ngRoute.directive:ngView ngView} - * creates new scope and reinstantiates the controller. - */ - reload: function() { - forceReload = true; - $rootScope.$evalAsync(function() { - // Don't support cancellation of a reload for now... - prepareRoute(); - commitRoute(); - }); - }, + if (isSegmentBoundary(minIndex)) { + currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax}; + result.push(currentSegment); + } - /** - * @ngdoc method - * @name $route#updateParams - * - * @description - * Causes `$route` service to update the current URL, replacing - * current route parameters with those specified in `newParams`. - * Provided property names that match the route's path segment - * definitions will be interpolated into the location's path, while - * remaining properties will be treated as query params. - * - * @param {!Object} newParams mapping of URL parameter names to values - */ - updateParams: function(newParams) { - if (this.current && this.current.$$route) { - newParams = angular.extend({}, this.current.params, newParams); - $location.path(interpolate(this.current.$$route.originalPath, newParams)); - // interpolate modifies newParams, only query params are left - $location.search(newParams); - } else { - throw $routeMinErr('norout', 'Tried updating route when with no current route'); - } - } - }; + currentSegment.nodes.push(childSegment); + currentSegment.min = Math.min(currentSegment.min, minIndex); + currentSegment.max = Math.max(currentSegment.max, maxIndex); + lastMax = maxIndex; + } - $rootScope.$on('$locationChangeStart', prepareRoute); - $rootScope.$on('$locationChangeSuccess', commitRoute); + nodeStats.segments = result; + } - return $route; + function orderChildSegments(children) { + var specifiedOrder = [], + unspecifiedOrder = []; - ///////////////////////////////////////////////////// + for (var i = 0; i < children.length; i++) { + var child = children[i], + segments = stats[child.id].segments; - /** - * @param on {string} current url - * @param route {Object} route regexp to match the url against - * @return {?Object} - * - * @description - * Check if the route matches the current url. - * - * Inspired by match in - * visionmedia/express/lib/router/router.js. - */ - function switchRouteMatcher(on, route) { - var keys = route.keys, - params = {}; + for (var j = 0; j < segments.length; j++) { + var seg = segments[j]; - if (!route.regexp) return null; + if (seg.min === defaultMin) { + unspecifiedOrder.push(seg); + } else { + specifiedOrder.push(seg); + } + } + } - var m = route.regexp.exec(on); - if (!m) return null; + specifiedOrder.sort(function(a, b) { + return a.min - b.min; + }); - for (var i = 1, len = m.length; i < len; ++i) { - var key = keys[i - 1]; + return specifiedOrder.concat(unspecifiedOrder); + } - var val = m[i]; + function executeNode(node, segmentNumber) { + if (node.children) { + return { + fn: function(done) { + nodeStart(node); - if (key && val) { - params[key.name] = val; - } - } - return params; - } + queueRunnerFactory({ + onComplete: function() { + nodeComplete(node, node.getResult()); + done(); + }, + queueableFns: wrapChildren(node, segmentNumber), + userContext: node.sharedUserContext(), + onException: function() { + node.onException.apply(node, arguments); + } + }); + } + }; + } else { + return { + fn: function(done) { node.execute(done, stats[node.id].executable); } + }; + } + } - function prepareRoute($locationEvent) { - var lastRoute = $route.current; + function wrapChildren(node, segmentNumber) { + var result = [], + segmentChildren = stats[node.id].segments[segmentNumber].nodes; - preparedRoute = parseRoute(); - preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route - && angular.equals(preparedRoute.pathParams, lastRoute.pathParams) - && !preparedRoute.reloadOnSearch && !forceReload; + for (var i = 0; i < segmentChildren.length; i++) { + result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); + } - if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { - if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { - if ($locationEvent) { - $locationEvent.preventDefault(); - } - } - } - } + if (!stats[node.id].executable) { + return result; + } - function commitRoute() { - var lastRoute = $route.current; - var nextRoute = preparedRoute; + return node.beforeAllFns.concat(result).concat(node.afterAllFns); + } + } - if (preparedRouteIsUpdateOnly) { - lastRoute.params = nextRoute.params; - angular.copy(lastRoute.params, $routeParams); - $rootScope.$broadcast('$routeUpdate', lastRoute); - } else if (nextRoute || lastRoute) { - forceReload = false; - $route.current = nextRoute; - if (nextRoute) { - if (nextRoute.redirectTo) { - if (angular.isString(nextRoute.redirectTo)) { - $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) - .replace(); - } else { - $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) - .replace(); - } - } - } + return TreeProcessor; +}; - $q.when(nextRoute). - then(function() { - if (nextRoute) { - var locals = angular.extend({}, nextRoute.resolve), - template, templateUrl; +getJasmineRequireObj().Any = function(j$) { - angular.forEach(locals, function(value, key) { - locals[key] = angular.isString(value) ? - $injector.get(value) : $injector.invoke(value, null, null, key); - }); + function Any(expectedObject) { + this.expectedObject = expectedObject; + } - if (angular.isDefined(template = nextRoute.template)) { - if (angular.isFunction(template)) { - template = template(nextRoute.params); - } - } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { - if (angular.isFunction(templateUrl)) { - templateUrl = templateUrl(nextRoute.params); - } - if (angular.isDefined(templateUrl)) { - nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl); - template = $templateRequest(templateUrl); - } - } - if (angular.isDefined(template)) { - locals['$template'] = template; - } - return $q.all(locals); - } - }). - then(function(locals) { - // after route change - if (nextRoute == $route.current) { - if (nextRoute) { - nextRoute.locals = locals; - angular.copy(nextRoute.params, $routeParams); - } - $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); - } - }, function(error) { - if (nextRoute == $route.current) { - $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); - } - }); - } - } + Any.prototype.asymmetricMatch = function(other) { + if (this.expectedObject == String) { + return typeof other == 'string' || other instanceof String; + } + if (this.expectedObject == Number) { + return typeof other == 'number' || other instanceof Number; + } - /** - * @returns {Object} the current active route, by matching it against the URL - */ - function parseRoute() { - // Match a route - var params, match; - angular.forEach(routes, function(route, path) { - if (!match && (params = switchRouteMatcher($location.path(), route))) { - match = inherit(route, { - params: angular.extend({}, $location.search(), params), - pathParams: params}); - match.$$route = route; - } - }); - // No route matched; fallback to "otherwise" route - return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); - } + if (this.expectedObject == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedObject == Object) { + return typeof other == 'object'; + } + + if (this.expectedObject == Boolean) { + return typeof other == 'boolean'; + } + + return other instanceof this.expectedObject; + }; + + Any.prototype.jasmineToString = function() { + return ''; + }; + + return Any; +}; + +getJasmineRequireObj().Anything = function(j$) { + + function Anything() {} + + Anything.prototype.asymmetricMatch = function(other) { + return !j$.util.isUndefined(other) && other !== null; + }; - /** - * @returns {string} interpolation of the redirect path with the parameters - */ - function interpolate(string, params) { - var result = []; - angular.forEach((string || '').split(':'), function(segment, i) { - if (i === 0) { - result.push(segment); - } else { - var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/); - var key = segmentMatch[1]; - result.push(params[key]); - result.push(segmentMatch[2] || ''); - delete params[key]; - } - }); - return result.join(''); - } - }]; - } + Anything.prototype.jasmineToString = function() { + return ''; + }; - ngRouteModule.provider('$routeParams', $RouteParamsProvider); + return Anything; +}; +getJasmineRequireObj().ArrayContaining = function(j$) { + function ArrayContaining(sample) { + this.sample = sample; + } - /** - * @ngdoc service - * @name $routeParams - * @requires $route - * - * @description - * The `$routeParams` service allows you to retrieve the current set of route parameters. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * The route parameters are a combination of {@link ng.$location `$location`}'s - * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. - * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. - * - * In case of parameter name collision, `path` params take precedence over `search` params. - * - * The service guarantees that the identity of the `$routeParams` object will remain unchanged - * (but its properties will likely change) even when a route change occurs. - * - * Note that the `$routeParams` are only updated *after* a route change completes successfully. - * This means that you cannot rely on `$routeParams` being correct in route resolve functions. - * Instead you can use `$route.current.params` to access the new route's parameters. - * - * @example - * ```js - * // Given: - * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby - * // Route: /Chapter/:chapterId/Section/:sectionId - * // - * // Then - * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} - * ``` - */ - function $RouteParamsProvider() { - this.$get = function() { return {}; }; - } + ArrayContaining.prototype.asymmetricMatch = function(other) { + var className = Object.prototype.toString.call(this.sample); + if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); } - ngRouteModule.directive('ngView', ngViewFactory); - ngRouteModule.directive('ngView', ngViewFillContentFactory); + for (var i = 0; i < this.sample.length; i++) { + var item = this.sample[i]; + if (!j$.matchersUtil.contains(other, item)) { + return false; + } + } + return true; + }; - /** - * @ngdoc directive - * @name ngView - * @restrict ECA - * - * @description - * # Overview - * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by - * including the rendered template of the current route into the main layout (`index.html`) file. - * Every time the current route changes, the included view changes with it according to the - * configuration of the `$route` service. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * @animations - * enter - animation is used to bring new content into the browser. - * leave - animation is used to animate existing content away. - * - * The enter and leave animation occur concurrently. - * - * @scope - * @priority 400 - * @param {string=} onload Expression to evaluate whenever the view updates. - * - * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll - * $anchorScroll} to scroll the viewport after the view is updated. - * - * - If the attribute is not set, disable scrolling. - * - If the attribute is set without value, enable scrolling. - * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated - * as an expression yields a truthy value. - * @example - - -
- Choose: - Moby | - Moby: Ch1 | - Gatsby | - Gatsby: Ch4 | - Scarlet Letter
+ ArrayContaining.prototype.jasmineToString = function () { + return ''; + }; -
-
-
-
+ return ArrayContaining; +}; -
$location.path() = {{main.$location.path()}}
-
$route.current.templateUrl = {{main.$route.current.templateUrl}}
-
$route.current.params = {{main.$route.current.params}}
-
$routeParams = {{main.$routeParams}}
-
-
+getJasmineRequireObj().ObjectContaining = function(j$) { - -
- controller: {{book.name}}
- Book Id: {{book.params.bookId}}
-
-
+ function ObjectContaining(sample) { + this.sample = sample; + } - -
- controller: {{chapter.name}}
- Book Id: {{chapter.params.bookId}}
- Chapter Id: {{chapter.params.chapterId}} -
-
+ function getPrototype(obj) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(obj); + } - - .view-animate-container { - position:relative; - height:100px!important; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } + if (obj.constructor.prototype == obj) { + return null; + } - .view-animate { - padding:10px; - } + return obj.constructor.prototype; + } - .view-animate.ng-enter, .view-animate.ng-leave { - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + function hasProperty(obj, property) { + if (!obj) { + return false; + } - display:block; - width:100%; - border-left:1px solid black; + if (Object.prototype.hasOwnProperty.call(obj, property)) { + return true; + } - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - padding:10px; - } + return hasProperty(getPrototype(obj), property); + } - .view-animate.ng-enter { - left:100%; - } - .view-animate.ng-enter.ng-enter-active { - left:0; - } - .view-animate.ng-leave.ng-leave-active { - left:-100%; - } - + ObjectContaining.prototype.asymmetricMatch = function(other) { + if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } - - angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) - .config(['$routeProvider', '$locationProvider', - function($routeProvider, $locationProvider) { - $routeProvider - .when('/Book/:bookId', { - templateUrl: 'book.html', - controller: 'BookCtrl', - controllerAs: 'book' - }) - .when('/Book/:bookId/ch/:chapterId', { - templateUrl: 'chapter.html', - controller: 'ChapterCtrl', - controllerAs: 'chapter' - }); + for (var property in this.sample) { + if (!hasProperty(other, property) || + !j$.matchersUtil.equals(this.sample[property], other[property])) { + return false; + } + } - $locationProvider.html5Mode(true); - }]) - .controller('MainCtrl', ['$route', '$routeParams', '$location', - function($route, $routeParams, $location) { - this.$route = $route; - this.$location = $location; - this.$routeParams = $routeParams; - }]) - .controller('BookCtrl', ['$routeParams', function($routeParams) { - this.name = "BookCtrl"; - this.params = $routeParams; - }]) - .controller('ChapterCtrl', ['$routeParams', function($routeParams) { - this.name = "ChapterCtrl"; - this.params = $routeParams; - }]); + return true; + }; - + ObjectContaining.prototype.jasmineToString = function() { + return ''; + }; - - it('should load and compile correct template', function() { - element(by.linkText('Moby: Ch1')).click(); - var content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller\: ChapterCtrl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); + return ObjectContaining; +}; - element(by.partialLinkText('Scarlet')).click(); +getJasmineRequireObj().StringMatching = function(j$) { - content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller\: BookCtrl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - -
- */ + function StringMatching(expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } + this.regexp = new RegExp(expected); + } - /** - * @ngdoc event - * @name ngView#$viewContentLoaded - * @eventType emit on the current ngView scope - * @description - * Emitted every time the ngView content is reloaded. - */ - ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; - function ngViewFactory($route, $anchorScroll, $animate) { - return { - restrict: 'ECA', - terminal: true, - priority: 400, - transclude: 'element', - link: function(scope, $element, attr, ctrl, $transclude) { - var currentScope, - currentElement, - previousLeaveAnimation, - autoScrollExp = attr.autoscroll, - onloadExp = attr.onload || ''; + StringMatching.prototype.asymmetricMatch = function(other) { + return this.regexp.test(other); + }; + + StringMatching.prototype.jasmineToString = function() { + return ''; + }; + + return StringMatching; +}; + +getJasmineRequireObj().errors = function() { + function ExpectationFailed() {} + + ExpectationFailed.prototype = new Error(); + ExpectationFailed.prototype.constructor = ExpectationFailed; + + return { + ExpectationFailed: ExpectationFailed + }; +}; +getJasmineRequireObj().matchersUtil = function(j$) { + // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? + + return { + equals: function(a, b, customTesters) { + customTesters = customTesters || []; + + return eq(a, b, [], [], customTesters); + }, + + contains: function(haystack, needle, customTesters) { + customTesters = customTesters || []; + + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { + for (var i = 0; i < haystack.length; i++) { + if (eq(haystack[i], needle, [], [], customTesters)) { + return true; + } + } + return false; + } + + return !!haystack && haystack.indexOf(needle) >= 0; + }, + + buildFailureMessage: function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; + } + }; + + function isAsymmetric(obj) { + return obj && j$.isA_('Function', obj.asymmetricMatch); + } + + function asymmetricMatch(a, b) { + var asymmetricA = isAsymmetric(a), + asymmetricB = isAsymmetric(b); + + if (asymmetricA && asymmetricB) { + return undefined; + } + + if (asymmetricA) { + return a.asymmetricMatch(b); + } + + if (asymmetricB) { + return b.asymmetricMatch(a); + } + } + + // Equality function lovingly adapted from isEqual in + // [Underscore](http://underscorejs.org) + function eq(a, b, aStack, bStack, customTesters) { + var result = true; + + var asymmetricResult = asymmetricMatch(a, b); + if (!j$.util.isUndefined(asymmetricResult)) { + return asymmetricResult; + } + + for (var i = 0; i < customTesters.length; i++) { + var customTesterResult = customTesters[i](a, b); + if (!j$.util.isUndefined(customTesterResult)) { + return customTesterResult; + } + } + + if (a instanceof Error && b instanceof Error) { + return a.message == b.message; + } + + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) { return a !== 0 || 1 / a == 1 / b; } + // A strict comparison is necessary because `null == undefined`. + if (a === null || b === null) { return a === b; } + var className = Object.prototype.toString.call(a); + if (className != Object.prototype.toString.call(b)) { return false; } + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') { return false; } + + var aIsDomNode = j$.isDomNode(a); + var bIsDomNode = j$.isDomNode(b); + if (aIsDomNode && bIsDomNode) { + // At first try to use DOM3 method isEqualNode + if (a.isEqualNode) { + return a.isEqualNode(b); + } + // IE8 doesn't support isEqualNode, try to use outerHTML && innerText + var aIsElement = a instanceof Element; + var bIsElement = b instanceof Element; + if (aIsElement && bIsElement) { + return a.outerHTML == b.outerHTML; + } + if (aIsElement || bIsElement) { + return false; + } + return a.innerText == b.innerText && a.textContent == b.textContent; + } + if (aIsDomNode || bIsDomNode) { + return false; + } + + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) { return bStack[length] == b; } + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0; + // Recursively compare objects and arrays. + // Compare array lengths to determine if a deep comparison is necessary. + if (className == '[object Array]' && a.length !== b.length) { + result = false; + } + + if (result) { + // Objects with different constructors are not equivalent, but `Object`s + // or `Array`s from different frames are. + if (className !== '[object Array]') { + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && + isFunction(bCtor) && bCtor instanceof bCtor)) { + return false; + } + } + // Deep compare objects. + for (var key in a) { + if (has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (has(b, key) && !(size--)) { break; } + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + + return result; + + function has(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); + } + + function isFunction(obj) { + return typeof obj === 'function'; + } + } +}; + +getJasmineRequireObj().toBe = function() { + function toBe() { + return { + compare: function(actual, expected) { + return { + pass: actual === expected + }; + } + }; + } - scope.$on('$routeChangeSuccess', update); - update(); + return toBe; +}; - function cleanupLastView() { - if (previousLeaveAnimation) { - $animate.cancel(previousLeaveAnimation); - previousLeaveAnimation = null; - } +getJasmineRequireObj().toBeCloseTo = function() { - if (currentScope) { - currentScope.$destroy(); - currentScope = null; - } - if (currentElement) { - previousLeaveAnimation = $animate.leave(currentElement); - previousLeaveAnimation.then(function() { - previousLeaveAnimation = null; - }); - currentElement = null; - } - } + function toBeCloseTo() { + return { + compare: function(actual, expected, precision) { + if (precision !== 0) { + precision = precision || 2; + } - function update() { - var locals = $route.current && $route.current.locals, - template = locals && locals.$template; + return { + pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) + }; + } + }; + } + + return toBeCloseTo; +}; + +getJasmineRequireObj().toBeDefined = function() { + function toBeDefined() { + return { + compare: function(actual) { + return { + pass: (void 0 !== actual) + }; + } + }; + } + + return toBeDefined; +}; + +getJasmineRequireObj().toBeFalsy = function() { + function toBeFalsy() { + return { + compare: function(actual) { + return { + pass: !!!actual + }; + } + }; + } - if (angular.isDefined(template)) { - var newScope = scope.$new(); - var current = $route.current; + return toBeFalsy; +}; - // Note: This will also link all children of ng-view that were contained in the original - // html. If that content contains controllers, ... they could pollute/change the scope. - // However, using ng-view on an element with additional content does not make sense... - // Note: We can't remove them in the cloneAttchFn of $transclude as that - // function is called before linking the content, which would apply child - // directives to non existing elements. - var clone = $transclude(newScope, function(clone) { - $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { - if (angular.isDefined(autoScrollExp) - && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } - }); - cleanupLastView(); - }); +getJasmineRequireObj().toBeGreaterThan = function() { - currentElement = clone; - currentScope = current.scope = newScope; - currentScope.$emit('$viewContentLoaded'); - currentScope.$eval(onloadExp); - } else { - cleanupLastView(); - } - } - } - }; - } + function toBeGreaterThan() { + return { + compare: function(actual, expected) { + return { + pass: actual > expected + }; + } + }; + } -// This directive is called during the $transclude call of the first `ngView` directive. -// It will replace and compile the content of the element with the loaded template. -// We need this directive so that the element content is already filled when -// the link function of another directive on the same element as ngView -// is called. - ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; - function ngViewFillContentFactory($compile, $controller, $route) { - return { - restrict: 'ECA', - priority: -400, - link: function(scope, $element) { - var current = $route.current, - locals = current.locals; + return toBeGreaterThan; +}; - $element.html(locals.$template); - var link = $compile($element.contents()); +getJasmineRequireObj().toBeLessThan = function() { + function toBeLessThan() { + return { - if (current.controller) { - locals.$scope = scope; - var controller = $controller(current.controller, locals); - if (current.controllerAs) { - scope[current.controllerAs] = controller; - } - $element.data('$ngControllerController', controller); - $element.children().data('$ngControllerController', controller); - } + compare: function(actual, expected) { + return { + pass: actual < expected + }; + } + }; + } + + return toBeLessThan; +}; +getJasmineRequireObj().toBeNaN = function(j$) { + + function toBeNaN() { + return { + compare: function(actual) { + var result = { + pass: (actual !== actual) + }; - link(scope); - } - }; - } + if (result.pass) { + result.message = 'Expected actual not to be NaN.'; + } else { + result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; + } + return result; + } + }; + } -})(window, window.angular); + return toBeNaN; +}; -/** - * @license AngularJS v1.4.7 - * (c) 2010-2015 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; +getJasmineRequireObj().toBeNull = function() { - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Any commits to this file should be reviewed with security in mind. * - * Changes to this file can potentially create security vulnerabilities. * - * An approval from 2 Core members with history of modifying * - * this file is required. * - * * - * Does the change somehow allow for arbitrary javascript to be executed? * - * Or allows for someone to change the prototype of built-in objects? * - * Or gives undesired access to variables likes document or window? * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + function toBeNull() { + return { + compare: function(actual) { + return { + pass: actual === null + }; + } + }; + } - var $sanitizeMinErr = angular.$$minErr('$sanitize'); + return toBeNull; +}; - /** - * @ngdoc module - * @name ngSanitize - * @description - * - * # ngSanitize - * - * The `ngSanitize` module provides functionality to sanitize HTML. - * - * - *
- * - * See {@link ngSanitize.$sanitize `$sanitize`} for usage. - */ +getJasmineRequireObj().toBeTruthy = function() { - /* - * HTML Parser By Misko Hevery (misko@hevery.com) - * based on: HTML Parser By John Resig (ejohn.org) - * Original code by Erik Arvidsson, Mozilla Public License - * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js - * - * // Use like so: - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - */ + function toBeTruthy() { + return { + compare: function(actual) { + return { + pass: !!actual + }; + } + }; + } + return toBeTruthy; +}; - /** - * @ngdoc service - * @name $sanitize - * @kind function - * - * @description - * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are - * then serialized back to properly escaped html string. This means that no unsafe input can make - * it into the returned string, however, since our parser is more strict than a typical browser - * parser, it's possible that some obscure input, which would be recognized as valid HTML by a - * browser, won't make it through the sanitizer. The input may also contain SVG markup. - * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and - * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. - * - * @param {string} html HTML input. - * @returns {string} Sanitized HTML. - * - * @example - - - -
- Snippet: - - - - - - - - - - - - - - - - - - - - - - - - - -
DirectiveHowSourceRendered
ng-bind-htmlAutomatically uses $sanitize
<div ng-bind-html="snippet">
</div>
ng-bind-htmlBypass $sanitize by explicitly trusting the dangerous value -
<div ng-bind-html="deliberatelyTrustDangerousSnippet()">
-	 </div>
-
ng-bindAutomatically escapes
<div ng-bind="snippet">
</div>
-
-
- - it('should sanitize the html snippet by default', function() { - expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). - toBe('

an html\nclick here\nsnippet

'); - }); +getJasmineRequireObj().toBeUndefined = function() { - it('should inline raw snippet if bound to a trusted value', function() { - expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). - toBe("

an html\n" + - "click here\n" + - "snippet

"); - }); + function toBeUndefined() { + return { + compare: function(actual) { + return { + pass: void 0 === actual + }; + } + }; + } - it('should escape snippet without any filter', function() { - expect(element(by.css('#bind-default div')).getInnerHtml()). - toBe("<p style=\"color:blue\">an html\n" + - "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + - "snippet</p>"); - }); + return toBeUndefined; +}; - it('should update', function() { - element(by.model('snippet')).clear(); - element(by.model('snippet')).sendKeys('new text'); - expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). - toBe('new text'); - expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( - 'new text'); - expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( - "new <b onclick=\"alert(1)\">text</b>"); - }); -
-
- */ - function $SanitizeProvider() { - this.$get = ['$$sanitizeUri', function($$sanitizeUri) { - return function(html) { - var buf = []; - htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { - return !/^unsafe/.test($$sanitizeUri(uri, isImage)); - })); - return buf.join(''); - }; - }]; - } +getJasmineRequireObj().toContain = function() { + function toContain(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + + return { + pass: util.contains(actual, expected, customEqualityTesters) + }; + } + }; + } - function sanitizeText(chars) { - var buf = []; - var writer = htmlSanitizeWriter(buf, angular.noop); - writer.chars(chars); - return buf.join(''); - } + return toContain; +}; +getJasmineRequireObj().toEqual = function() { -// Regular Expressions for parsing tags and attributes - var START_TAG_REGEXP = - /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/, - END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/, - ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, - BEGIN_TAG_REGEXP = /^/g, - DOCTYPE_REGEXP = /]*?)>/i, - CDATA_REGEXP = //g, - SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, - // Match everything outside of normal chars and " (quote character) - NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; + function toEqual(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + return { + compare: function(actual, expected) { + var result = { + pass: false + }; -// Good source of info about elements and attributes -// http://dev.w3.org/html5/spec/Overview.html#semantics -// http://simon.html5.org/html-elements + result.pass = util.equals(actual, expected, customEqualityTesters); -// Safe Void Elements - HTML5 -// http://dev.w3.org/html5/spec/Overview.html#void-elements - var voidElements = makeMap("area,br,col,hr,img,wbr"); + return result; + } + }; + } -// Elements that you can, intentionally, leave open (and which close themselves) -// http://dev.w3.org/html5/spec/Overview.html#optional-tags - var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), - optionalEndTagInlineElements = makeMap("rp,rt"), - optionalEndTagElements = angular.extend({}, - optionalEndTagInlineElements, - optionalEndTagBlockElements); + return toEqual; +}; -// Safe Block Elements - HTML5 - var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + - "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + - "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); +getJasmineRequireObj().toHaveBeenCalled = function(j$) { -// Inline Elements - HTML5 - var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + - "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + - "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); + function toHaveBeenCalled() { + return { + compare: function(actual) { + var result = {}; -// SVG Elements -// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements -// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted. -// They can potentially allow for arbitrary javascript to be executed. See #11290 - var svgElements = makeMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," + - "hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," + - "radialGradient,rect,stop,svg,switch,text,title,tspan,use"); + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } -// Special Elements (can contain anything) - var specialElements = makeMap("script,style"); + if (arguments.length > 1) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } - var validElements = angular.extend({}, - voidElements, - blockElements, - inlineElements, - optionalEndTagElements, - svgElements); + result.pass = actual.calls.any(); -//Attributes that have href and hence need to be sanitized - var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href"); + result.message = result.pass ? + 'Expected spy ' + actual.and.identity() + ' not to have been called.' : + 'Expected spy ' + actual.and.identity() + ' to have been called.'; - var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + - 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + - 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + - 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' + - 'valign,value,vspace,width'); + return result; + } + }; + } -// SVG attributes (without "id" and "name" attributes) -// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes - var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + - 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' + - 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' + - 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' + - 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' + - 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' + - 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' + - 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' + - 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' + - 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' + - 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' + - 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' + - 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' + - 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' + - 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true); + return toHaveBeenCalled; +}; - var validAttrs = angular.extend({}, - uriAttrs, - svgAttrs, - htmlAttrs); +getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { - function makeMap(str, lowercaseKeys) { - var obj = {}, items = str.split(','), i; - for (i = 0; i < items.length; i++) { - obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true; - } - return obj; - } + function toHaveBeenCalledWith(util, customEqualityTesters) { + return { + compare: function() { + var args = Array.prototype.slice.call(arguments, 0), + actual = args[0], + expectedArgs = args.slice(1), + result = { pass: false }; + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } - /** - * @example - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - * @param {string} html string - * @param {object} handler - */ - function htmlParser(html, handler) { - if (typeof html !== 'string') { - if (html === null || typeof html === 'undefined') { - html = ''; - } else { - html = '' + html; - } - } - var index, chars, match, stack = [], last = html, text; - stack.last = function() { return stack[stack.length - 1]; }; + if (!actual.calls.any()) { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; + return result; + } - while (html) { - text = ''; - chars = true; + if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + result.pass = true; + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; + } else { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; + } - // Make sure we're not in a script or style element - if (!stack.last() || !specialElements[stack.last()]) { + return result; + } + }; + } - // Comment - if (html.indexOf("", index) === index) { - if (handler.comment) handler.comment(html.substring(4, index)); - html = html.substring(index + 3); - chars = false; - } - // DOCTYPE - } else if (DOCTYPE_REGEXP.test(html)) { - match = html.match(DOCTYPE_REGEXP); +getJasmineRequireObj().toMatch = function(j$) { - if (match) { - html = html.replace(match[0], ''); - chars = false; - } - // end tag - } else if (BEGING_END_TAGE_REGEXP.test(html)) { - match = html.match(END_TAG_REGEXP); + function toMatch() { + return { + compare: function(actual, expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } - if (match) { - html = html.substring(match[0].length); - match[0].replace(END_TAG_REGEXP, parseEndTag); - chars = false; - } + var regexp = new RegExp(expected); - // start tag - } else if (BEGIN_TAG_REGEXP.test(html)) { - match = html.match(START_TAG_REGEXP); + return { + pass: regexp.test(actual) + }; + } + }; + } - if (match) { - // We only have a valid start-tag if there is a '>'. - if (match[4]) { - html = html.substring(match[0].length); - match[0].replace(START_TAG_REGEXP, parseStartTag); - } - chars = false; - } else { - // no ending tag found --- this piece should be encoded as an entity. - text += '<'; - html = html.substring(1); - } - } + return toMatch; +}; - if (chars) { - index = html.indexOf("<"); +getJasmineRequireObj().toThrow = function(j$) { - text += index < 0 ? html : html.substring(0, index); - html = index < 0 ? "" : html.substring(index); + function toThrow(util) { + return { + compare: function(actual, expected) { + var result = { pass: false }, + threw = false, + thrown; - if (handler.chars) handler.chars(decodeEntities(text)); - } + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } - } else { - // IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w]. - html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), - function(all, text) { - text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } - if (handler.chars) handler.chars(decodeEntities(text)); + if (!threw) { + result.message = 'Expected function to throw an exception.'; + return result; + } - return ""; - }); + if (arguments.length == 1) { + result.pass = true; + result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; - parseEndTag("", stack.last()); - } + return result; + } - if (html == last) { - throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + - "of html: {0}", html); - } - last = html; - } + if (util.equals(thrown, expected)) { + result.pass = true; + result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; + } else { + result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; + } - // Clean up any remaining tags - parseEndTag(); + return result; + } + }; + } + + return toThrow; +}; + +getJasmineRequireObj().toThrowError = function(j$) { + function toThrowError (util) { + return { + compare: function(actual) { + var threw = false, + pass = {pass: true}, + fail = {pass: false}, + thrown; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } - function parseStartTag(tag, tagName, rest, unary) { - tagName = angular.lowercase(tagName); - if (blockElements[tagName]) { - while (stack.last() && inlineElements[stack.last()]) { - parseEndTag("", stack.last()); - } - } + var errorMatcher = getMatcher.apply(null, arguments); - if (optionalEndTagElements[tagName] && stack.last() == tagName) { - parseEndTag("", tagName); - } + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } - unary = voidElements[tagName] || !!unary; + if (!threw) { + fail.message = 'Expected function to throw an Error.'; + return fail; + } - if (!unary) { - stack.push(tagName); - } + if (!(thrown instanceof Error)) { + fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; + return fail; + } - var attrs = {}; + if (errorMatcher.hasNoSpecifics()) { + pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; + return pass; + } - rest.replace(ATTR_REGEXP, - function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { - var value = doubleQuotedValue - || singleQuotedValue - || unquotedValue - || ''; + if (errorMatcher.matches(thrown)) { + pass.message = function() { + return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; + }; + return pass; + } else { + fail.message = function() { + return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + + ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; + }; + return fail; + } + } + }; - attrs[name] = decodeEntities(value); - }); - if (handler.start) handler.start(tagName, attrs, unary); - } + function getMatcher() { + var expected = null, + errorType = null; + + if (arguments.length == 2) { + expected = arguments[1]; + if (isAnErrorType(expected)) { + errorType = expected; + expected = null; + } + } else if (arguments.length > 2) { + errorType = arguments[1]; + expected = arguments[2]; + if (!isAnErrorType(errorType)) { + throw new Error('Expected error type is not an Error.'); + } + } + + if (expected && !isStringOrRegExp(expected)) { + if (errorType) { + throw new Error('Expected error message is not a string or RegExp.'); + } else { + throw new Error('Expected is not an Error, string, or RegExp.'); + } + } + + function messageMatch(message) { + if (typeof expected == 'string') { + return expected == message; + } else { + return expected.test(message); + } + } + + return { + errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', + thrownDescription: function(thrown) { + var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', + thrownMessage = ''; + + if (expected) { + thrownMessage = ' with message ' + j$.pp(thrown.message); + } + + return thrownName + thrownMessage; + }, + messageDescription: function() { + if (expected === null) { + return ''; + } else if (expected instanceof RegExp) { + return ' with a message matching ' + j$.pp(expected); + } else { + return ' with message ' + j$.pp(expected); + } + }, + hasNoSpecifics: function() { + return expected === null && errorType === null; + }, + matches: function(error) { + return (errorType === null || error instanceof errorType) && + (expected === null || messageMatch(error.message)); + } + }; + } + + function isStringOrRegExp(potential) { + return potential instanceof RegExp || (typeof potential == 'string'); + } + + function isAnErrorType(type) { + if (typeof type !== 'function') { + return false; + } + + var Surrogate = function() {}; + Surrogate.prototype = type.prototype; + return (new Surrogate()) instanceof Error; + } + } + + return toThrowError; +}; - function parseEndTag(tag, tagName) { - var pos = 0, i; - tagName = angular.lowercase(tagName); - if (tagName) { - // Find the closest opened tag of the same type - for (pos = stack.length - 1; pos >= 0; pos--) { - if (stack[pos] == tagName) break; - } - } +getJasmineRequireObj().interface = function(jasmine, env) { + var jasmineInterface = { + describe: function(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, - if (pos >= 0) { - // Close all the open elements, up the stack - for (i = stack.length - 1; i >= pos; i--) - if (handler.end) handler.end(stack[i]); + xdescribe: function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, - // Remove the open elements from the stack - stack.length = pos; - } - } - } + fdescribe: function(description, specDefinitions) { + return env.fdescribe(description, specDefinitions); + }, - var hiddenPre=document.createElement("pre"); - /** - * decodes all entities into regular string - * @param value - * @returns {string} A string with decoded entities. - */ - function decodeEntities(value) { - if (!value) { return ''; } + it: function() { + return env.it.apply(env, arguments); + }, - hiddenPre.innerHTML = value.replace(//g, '>'); - } + fit: function() { + return env.fit.apply(env, arguments); + }, - /** - * create an HTML/XML writer which writes to buffer - * @param {Array} buf use buf.jain('') to get out sanitized html string - * @returns {object} in the form of { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * } - */ - function htmlSanitizeWriter(buf, uriValidator) { - var ignore = false; - var out = angular.bind(buf, buf.push); - return { - start: function(tag, attrs, unary) { - tag = angular.lowercase(tag); - if (!ignore && specialElements[tag]) { - ignore = tag; - } - if (!ignore && validElements[tag] === true) { - out('<'); - out(tag); - angular.forEach(attrs, function(value, key) { - var lkey=angular.lowercase(key); - var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); - if (validAttrs[lkey] === true && - (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { - out(' '); - out(key); - out('="'); - out(encodeEntities(value)); - out('"'); - } - }); - out(unary ? '/>' : '>'); - } - }, - end: function(tag) { - tag = angular.lowercase(tag); - if (!ignore && validElements[tag] === true) { - out(''); - } - if (tag == ignore) { - ignore = false; - } - }, - chars: function(chars) { - if (!ignore) { - out(encodeEntities(chars)); - } - } - }; - } + beforeEach: function() { + return env.beforeEach.apply(env, arguments); + }, + afterEach: function() { + return env.afterEach.apply(env, arguments); + }, -// define ngSanitize module and register $sanitize service - angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); + beforeAll: function() { + return env.beforeAll.apply(env, arguments); + }, - /* global sanitizeText: false */ + afterAll: function() { + return env.afterAll.apply(env, arguments); + }, - /** - * @ngdoc filter - * @name linky - * @kind function - * - * @description - * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and - * plain email address links. - * - * Requires the {@link ngSanitize `ngSanitize`} module to be installed. - * - * @param {string} text Input text. - * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. - * @returns {string} Html-linkified text. - * - * @usage - - * - * @example - - - -
- Snippet: - - - - - - - - - - - - - - - - - - - - - -
FilterSourceRendered
linky filter -
<div ng-bind-html="snippet | linky">
</div>
-
-
-
linky target -
<div ng-bind-html="snippetWithTarget | linky:'_blank'">
</div>
-
-
-
no filter
<div ng-bind="snippet">
</div>
- - - it('should linkify the snippet with urls', function() { - expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). - toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + - 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); - expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); - }); + expect: function(actual) { + return env.expect(actual); + }, - it('should not linkify snippet without the linky filter', function() { - expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). - toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + - 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); - expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); - }); + pending: function() { + return env.pending.apply(env, arguments); + }, - it('should update', function() { - element(by.model('snippet')).clear(); - element(by.model('snippet')).sendKeys('new http://link.'); - expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). - toBe('new http://link.'); - expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); - expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) - .toBe('new http://link.'); - }); + fail: function() { + return env.fail.apply(env, arguments); + }, - it('should work with the target property', function() { - expect(element(by.id('linky-target')). - element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). - toBe('http://angularjs.org/'); - expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); - }); - - - */ - angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { - var LINKY_URL_REGEXP = - /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, - MAILTO_REGEXP = /^mailto:/i; + spyOn: function(obj, methodName) { + return env.spyOn(obj, methodName); + }, - return function(text, target) { - if (!text) return text; - var match; - var raw = text; - var html = []; - var url; - var i; - while ((match = raw.match(LINKY_URL_REGEXP))) { - // We can not end in these as they are sometimes found at the end of the sentence - url = match[0]; - // if we did not match ftp/http/www/mailto then assume mailto - if (!match[2] && !match[4]) { - url = (match[3] ? 'http://' : 'mailto:') + url; - } - i = match.index; - addText(raw.substr(0, i)); - addLink(url, match[0].replace(MAILTO_REGEXP, '')); - raw = raw.substring(i + match[0].length); - } - addText(raw); - return $sanitize(html.join('')); + jsApiReporter: new jasmine.JsApiReporter({ + timer: new jasmine.Timer() + }), - function addText(text) { - if (!text) { - return; - } - html.push(sanitizeText(text)); - } + jasmine: jasmine + }; - function addLink(url, text) { - html.push(''); - addText(text); - html.push(''); - } - }; - }]); + jasmine.addCustomEqualityTester = function(tester) { + env.addCustomEqualityTester(tester); + }; + jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); + }; -})(window, window.angular); + jasmine.clock = function() { + return env.clock; + }; + + return jasmineInterface; +}; + +getJasmineRequireObj().version = function() { + return '2.3.4'; +}; (function() { 'use strict'; diff --git a/src/reStart-app/core/Page.ctrl.js b/src/reStart-app/core/Page.ctrl.js index c51eeae..e1b0a05 100644 --- a/src/reStart-app/core/Page.ctrl.js +++ b/src/reStart-app/core/Page.ctrl.js @@ -1,83 +1,83 @@ -(function() { - 'use strict'; +(function () { + 'use strict'; - angular + angular .module('reStart') .controller('PageCtrl', PageCtrl); - PageCtrl.$inject = ['Page', '$scope', 'MQ', 'mediaCheck']; + PageCtrl.$inject = ['Page', '$scope', 'MQ', 'mediaCheck']; - function PageCtrl(Page, $scope, MQ, mediaCheck) { - var page = this; + function PageCtrl(Page, $scope, MQ, mediaCheck) { + var page = this; - // private variables - var _handlingRouteChangeError = false; - // Set up functionality to run on enter/exit of media query - var mc = mediaCheck.init({ - scope: $scope, - media: { - mq: MQ.SMALL, - enter: _enterMobile, - exit: _exitMobile - }, - debounce: 200 - }); + // private variables + var _handlingRouteChangeError = false; + // Set up functionality to run on enter/exit of media query + var mc = mediaCheck.init({ + scope: $scope, + media: { + mq: MQ.SMALL, + enter: _enterMobile, + exit: _exitMobile + }, + debounce: 200 + }); - _init(); + _init(); - /** + /** * INIT function executes procedural code * * @private */ - function _init() { - // associate page - page.pageTitle = Page; + function _init() { + // associate page <title> + page.pageTitle = Page; - $scope.$on('$routeChangeStart', _routeChangeStart); - $scope.$on('$routeChangeSuccess', _routeChangeSuccess); - $scope.$on('$routeChangeError', _routeChangeError); - } + $scope.$on('$routeChangeStart', _routeChangeStart); + $scope.$on('$routeChangeSuccess', _routeChangeSuccess); + $scope.$on('$routeChangeError', _routeChangeError); + } - /** + /** * Enter mobile media query * $broadcast 'enter-mobile' event * * @private */ - function _enterMobile() { - $scope.$broadcast('enter-mobile'); - } + function _enterMobile() { + $scope.$broadcast('enter-mobile'); + } - /** + /** * Exit mobile media query * $broadcast 'exit-mobile' event * * @private */ - function _exitMobile() { - $scope.$broadcast('exit-mobile'); - } + function _exitMobile() { + $scope.$broadcast('exit-mobile'); + } - /** + /** * Turn on loading state * * @private */ - function _loadingOn() { - $scope.$broadcast('loading-on'); - } + function _loadingOn() { + $scope.$broadcast('loading-on'); + } - /** + /** * Turn off loading state * * @private */ - function _loadingOff() { - $scope.$broadcast('loading-off'); - } + function _loadingOff() { + $scope.$broadcast('loading-off'); + } - /** + /** * Route change start handler * If next route has resolve, turn on loading * @@ -86,13 +86,13 @@ * @param current {object} * @private */ - function _routeChangeStart($event, next, current) { - if (next.$$route && next.$$route.resolve) { - _loadingOn(); - } - } + function _routeChangeStart($event, next, current) { + if (next.$$route && next.$$route.resolve) { + _loadingOn(); + } + } - /** + /** * Route change success handler * Match current media query and run appropriate function * If current route has been resolved, turn off loading @@ -102,15 +102,15 @@ * @param previous {object} * @private */ - function _routeChangeSuccess($event, current, previous) { - mc.matchCurrent(MQ.SMALL); + function _routeChangeSuccess($event, current, previous) { + mc.matchCurrent(MQ.SMALL); - if (current.$$route && current.$$route.resolve) { - _loadingOff(); - } - } + if (current.$$route && current.$$route.resolve) { + _loadingOff(); + } + } - /** + /** * Route change error handler * Handle route resolve failures * @@ -120,23 +120,29 @@ * @param rejection {object} * @private */ - function _routeChangeError($event, current, previous, rejection) { - if (_handlingRouteChangeError) { - return; - } + function _routeChangeError($event, current, previous, rejection) { + if (_handlingRouteChangeError) { + return; + } - _handlingRouteChangeError = true; - _loadingOff(); + _handlingRouteChangeError = true; + _loadingOff(); - var destination = (current && (current.title || current.name || current.loadedTemplateUrl)) || 'unknown target'; - var msg = 'Error routing to ' + destination + '. ' + (rejection.msg || ''); + var destination = (current && (current.title || current.name || current.loadedTemplateUrl)) || 'unknown target'; + var msg = 'Error routing to ' + destination + '. ' + (rejection.msg || ''); - console.log(msg); + console.log(msg); - /** + /** * On routing error, show an error. */ - alert('An error occurred. Please try again.'); - } - } + alert('An error occurred. Please try again.'); + } + + PageCtrl.enterMobile = _enterMobile;//test code + PageCtrl.exitMobile = _exitMobile;//test code + PageCtrl.loadingOn = _loadingOn;//test code + PageCtrl.loadingOff = _loadingOff;//test code + return PageCtrl;//test code + } })(); \ No newline at end of file diff --git a/src/reStart-app/core/Page.ctrl.spec.js b/src/reStart-app/core/Page.ctrl.spec.js new file mode 100644 index 0000000..44744f7 --- /dev/null +++ b/src/reStart-app/core/Page.ctrl.spec.js @@ -0,0 +1,47 @@ +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: PageCtrl', function () { + + var scope, pageVm, broadcast; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + pageVm = $controller('PageCtrl as pageVm', { $scope: scope }); + broadcast = false; + })); + + it("Broadcasts loading-on event", function () { + scope.$on('loading-on', broadcastRecieved); + pageVm.loadingOn(); + expect(broadcast).toBeTruthy(); + }) + it("Broadcasts loading-off event", function () { + scope.$on('loading-off', broadcastRecieved); + pageVm.loadingOff(); + expect(broadcast).toBeTruthy(); + }) + it("Broadcasts enter-mobile event", function () { + scope.$on('enter-mobile', broadcastRecieved); + pageVm.enterMobile(); + expect(broadcast).toBeTruthy(); + }) + it("Broadcasts exit-mobile event", function () { + scope.$on('exit-mobile', broadcastRecieved); + pageVm.exitMobile(); + expect(broadcast).toBeTruthy(); + }) + + function broadcastRecieved() { + broadcast = true; + } + + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/core/Page.factory.spec.js b/src/reStart-app/core/Page.factory.spec.js new file mode 100644 index 0000000..a14cdbc --- /dev/null +++ b/src/reStart-app/core/Page.factory.spec.js @@ -0,0 +1,25 @@ +; (function () { + describe('Module: reStart', function () { + var $factory; + beforeEach(function () { + module('reStart'); + }); + describe('Factory: Page', function () { + + var page; + beforeEach(inject(function ($injector) { + page = $injector.get('Page'); + })); + + it('Gets the page title', function () { + var title=page.getTitle(); + expect(title).toEqual('reStart Angular | Home'); + }) + it('Sets the page title', function () { + page.setTitle('New'); + expect(page.getTitle()).toEqual('reStart Angular | New'); + }) + + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/core/Utils.factory.spec.js b/src/reStart-app/core/Utils.factory.spec.js new file mode 100644 index 0000000..ebffdbe --- /dev/null +++ b/src/reStart-app/core/Utils.factory.spec.js @@ -0,0 +1,26 @@ +; (function () { + describe('Module: reStart', function () { + var $factory; + beforeEach(function () { + module('reStart'); + }); + describe('Factory: Utils', function () { + + var utils; + beforeEach(inject(function ($injector) { + utils = $injector.get('Utils'); + })); + + it('Gets the greeting', function () { + var greeting = utils.greeting; + expect(greeting).toEqual('Hello'); + }) + it('Greets given name', function () { + spyOn(utils,'alertGreeting') + utils.alertGreeting('Named'); + expect(utils.alertGreeting).toHaveBeenCalledWith("Named") + }) + + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/core/get-data/JSONData.factory.spec.js b/src/reStart-app/core/get-data/JSONData.factory.spec.js new file mode 100644 index 0000000..9dc55b7 --- /dev/null +++ b/src/reStart-app/core/get-data/JSONData.factory.spec.js @@ -0,0 +1,34 @@ +; (function () { + describe('Module: reStart', function () { + var $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + }); + }); + describe('Factory: JSONData', function () { + + var JSONData; + beforeEach(inject(function (_JSONData_,_$q_) { + var deferred = _$q_.defer(); + JSONData = _JSONData_; + rootScope = $rootScope; + + deferred.resolve({'local':'data'}); + spyOn(JSONData, 'getLocalData').and.returnValue(deferred.promise); + + })); + + it('Gets local data', function () { + var data; + JSONData.getLocalData().then(function (json) { + data = json; + }); + rootScope.$apply() + expect(data.local).toEqual('data'); + }) + + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/core/get-data/Res.factory.spec.js b/src/reStart-app/core/get-data/Res.factory.spec.js new file mode 100644 index 0000000..519bd64 --- /dev/null +++ b/src/reStart-app/core/get-data/Res.factory.spec.js @@ -0,0 +1,28 @@ +; (function () { + describe('Module: reStart', function () { + var $factory; + beforeEach(function () { + module('reStart'); + }); + describe('Factory: Res', function () { + + var res; + beforeEach(inject(function ($injector) { + res = $injector.get('Res'); + })); + + it('Checks if response is of type object', function () { + var obj = { data: {Iam:'obj'} } + var response = res.success(obj); + expect(response.Iam).toEqual('obj'); + }) + it('Throws an error if data is not of type obj', function () { + expect(res.success).toThrow(); + }) + it('Responds to Error', function () { + expect(res.error).toThrow(); + }) + + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/core/ui/loading.dir.spec.js b/src/reStart-app/core/ui/loading.dir.spec.js new file mode 100644 index 0000000..d39eed1 --- /dev/null +++ b/src/reStart-app/core/ui/loading.dir.spec.js @@ -0,0 +1,31 @@ +; (function () { + var $compile, $rootScope; + beforeEach(function () { + module('reStart'); + }); + + describe('Directive: Loading', function () { + var element; + beforeEach(function () { + module('templates'); + inject( + ['$compile', '$rootScope', function ($c, $r) { + $compile = $c; + $rootScope = $r; + }] + ) + element = $compile('<loading></loading>')($rootScope); + angular.element(document.body).append(element); + //trigger directive to be injected + $rootScope.$digest(); + }); + + it("should load the loading template", function () { + expect(element.text()).toBeFalsy(); + }) + + afterEach(function () { + element.remove(); + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/core/ui/trustAsHTML.filter.spec.js b/src/reStart-app/core/ui/trustAsHTML.filter.spec.js new file mode 100644 index 0000000..3209772 --- /dev/null +++ b/src/reStart-app/core/ui/trustAsHTML.filter.spec.js @@ -0,0 +1,17 @@ +; (function () { + beforeEach(function () { + module('reStart'); + }); + describe('Filter: Trust As HTML', function () { + + it('has a tust as html filter', inject(function ($filter) { + expect($filter('trustAsHTML')).not.toBeNull(); + })); + + + it('uses sce to trust text as html', inject(function ($filter) { + var trustAsHTML = $filter('trustAsHTML'); + expect(trustAsHTML("<div>")).toBeTruthy(); + })); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/modules/header/Header.ctrl.spec.js b/src/reStart-app/modules/header/Header.ctrl.spec.js new file mode 100644 index 0000000..f9365e6 --- /dev/null +++ b/src/reStart-app/modules/header/Header.ctrl.spec.js @@ -0,0 +1,28 @@ +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: HeaderCtrl', function () { + + var scope, headerVm; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + headerVm = $controller('HeaderCtrl as headerVm', { $scope: scope }); + })); + + it("Apply class to index nav if active", function () { + expect(headerVm.indexIsActive('/')).toBeTruthy(); + }) + it("Apply class to currently active nav item", function () { + expect(headerVm.navIsActive('/')).toBeTruthy(); + }) + + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/modules/header/header.tpl.html b/src/reStart-app/modules/header/header.tpl.html index 0cf2397..3e24c3f 100644 --- a/src/reStart-app/modules/header/header.tpl.html +++ b/src/reStart-app/modules/header/header.tpl.html @@ -7,11 +7,11 @@ <h1 class="header-mobile-page-siteTitle" ng-bind="::header.json.title"></h1> <nav id="nav" class="nav" role="navigation"> <ul class="nav-list"> <li> - <a ng-class="{active: header.indexIsActive('/')}" + <a class="home-nav" ng-class="{active: header.indexIsActive('/')}" ng-href="/"><span class="nav-list-item-text">Home</span></a> </li> <li> - <a ng-class="{active: header.navIsActive('/subpage')}" ng-href="/subpage"><span + <a class="sub-nav" ng-class="{active: header.navIsActive('/subpage')}" ng-href="/subpage"><span class="nav-list-item-text">Subpage</span></a> </li> </ul> diff --git a/src/reStart-app/modules/header/navControl.dir.js b/src/reStart-app/modules/header/navControl.dir.js index b34da95..1275e10 100644 --- a/src/reStart-app/modules/header/navControl.dir.js +++ b/src/reStart-app/modules/header/navControl.dir.js @@ -89,6 +89,7 @@ * Toggle nav open/closed */ function toggleNav() { + console.log("yo"); if (!_navOpen) { _openNav(); } else { diff --git a/src/reStart-app/modules/header/navControl.dir.spec.js b/src/reStart-app/modules/header/navControl.dir.spec.js new file mode 100644 index 0000000..63fb86e --- /dev/null +++ b/src/reStart-app/modules/header/navControl.dir.spec.js @@ -0,0 +1,43 @@ +; (function () { + var $compile, $rootScope,scope,element; + beforeEach(function () { + module('reStart'); + }); + + describe('Directive: navControl', function () { + beforeEach(function () { + module('templates'); + + //disable loading directive? + angular.module("reStart").directive("loading", function (loading) { + return { + priority: 100000, + terminal: true, + link: function () { + // do nothing + } + } + }); + + inject( + ['$compile', '$rootScope', function ($c, $r) { + $compile = $c; + $rootScope = $r; + scope = $rootScope.$new(); + }] + ) + element = $compile('<div nav-control><a class="toggle-offcanvas" ng-click="nav.toggleNav()"><span></span></a></div>')($rootScope); + angular.element(document.body).append(element); + $('loading').remove(); + //trigger directive to be injected + $rootScope.$digest(); + }); + + xit("toggles nav on and off", function () { + angular.element('.toggle-offcanvas').click(); + $rootScope.$digest(); + console.log(angular.element('body')[0]) + expect(angular.element('body').hasClass('nav-closed')).toBeFalsy(); + }) + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/pages/error404/Error404.ctrl.js b/src/reStart-app/pages/error404/Error404.ctrl.js index dd11164..ccd27ce 100644 --- a/src/reStart-app/pages/error404/Error404.ctrl.js +++ b/src/reStart-app/pages/error404/Error404.ctrl.js @@ -27,5 +27,9 @@ // no data to load, but loading state might be on $scope.$emit('loading-off'); } + + return { //test code + init: _init //test code + } //test code } })(); \ No newline at end of file diff --git a/src/reStart-app/pages/error404/Error404.ctrl.spec.js b/src/reStart-app/pages/error404/Error404.ctrl.spec.js new file mode 100644 index 0000000..78029a0 --- /dev/null +++ b/src/reStart-app/pages/error404/Error404.ctrl.spec.js @@ -0,0 +1,32 @@ +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: Error404Ctrl', function () { + + var scope, Error404Vm, broadcast; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + Error404Vm = $controller('Error404Ctrl as Error404Vm', { $scope: scope }); + broadcast = false; + })); + + it("Sends a loading-off message", function () { + scope.$on('loading-off', broadcastRecieved); + Error404Vm.init(); + expect(broadcast).toBeTruthy(); + }) + + function broadcastRecieved() { + broadcast = true; + } + + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/pages/home/Home.ctrl.js b/src/reStart-app/pages/home/Home.ctrl.js index b7c79e9..8bd39d1 100644 --- a/src/reStart-app/pages/home/Home.ctrl.js +++ b/src/reStart-app/pages/home/Home.ctrl.js @@ -87,5 +87,17 @@ function _exitMobile() { home.viewformat = 'large'; } + + function getView() { //test code + return home.viewformat; //test code + } //test code + + return { //test code + enterMobile: _enterMobile, //test code + exitMobile: _exitMobile, //test code + getJsonSucess: _getJsonSuccess, //test code + activate: _activate, //test code + getView: getView //test code + } //test code } })(); \ No newline at end of file diff --git a/src/reStart-app/pages/home/Home.ctrl.spec.js b/src/reStart-app/pages/home/Home.ctrl.spec.js new file mode 100644 index 0000000..ee84f14 --- /dev/null +++ b/src/reStart-app/pages/home/Home.ctrl.spec.js @@ -0,0 +1,43 @@ +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: HomeCtrl', function () { + + var scope, homeVm, broadcast; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + homeVm = $controller('HomeCtrl as homeVm', { $scope: scope }); + broadcast = false; + })); + + it("Broadcasts loading-on event", function () { + scope.$on('loading-on', broadcastRecieved); + homeVm.activate(); + expect(broadcast).toBeTruthy(); + }) + it("loads data into controller", function () { + expect(homeVm.getJsonSucess({ data: "yo" })).toBeTruthy(); + }) + it("Sets view format to small", function () { + homeVm.enterMobile(); + expect(homeVm.getView()).toEqual("small") + }) + it("Sets view format to large", function () { + homeVm.exitMobile(); + expect(homeVm.getView()).toEqual("large") + }) + + function broadcastRecieved() { + broadcast = true; + } + + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/pages/home/Home.view.html b/src/reStart-app/pages/home/Home.view.html index 3416d99..1a10366 100644 --- a/src/reStart-app/pages/home/Home.view.html +++ b/src/reStart-app/pages/home/Home.view.html @@ -8,7 +8,7 @@ <h1 class="content-heading" ng-bind="::home.title"></h1> <button ng-click="home.alertGreeting(home.name)">Greet</button> </p> - <h2>{{::home.global.greeting}}, {{home.name}}!</h2> + <h2 class="home-string">{{::home.global.greeting}}, {{home.name}}!</h2> <p ng-bind="::home.json.summary"></p> @@ -19,7 +19,7 @@ <h3>View Switching</h3> <p>The following view is manipulated by the current media query. Resize your browser window to see the display change.</p> - <p><strong>Display:</strong> {{home.viewformat === 'large' ? 'Table (large view)' : 'List (small view)'}}</p> + <p class="display"><strong>Display:</strong> {{home.viewformat === 'large' ? 'Table (large view)' : 'List (small view)'}}</p> <div id="markupView-wrapper" class="markupView-wrapper" diff --git a/src/reStart-app/pages/sub/Sub.ctrl.spec.js b/src/reStart-app/pages/sub/Sub.ctrl.spec.js new file mode 100644 index 0000000..05e1593 --- /dev/null +++ b/src/reStart-app/pages/sub/Sub.ctrl.spec.js @@ -0,0 +1,25 @@ +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: SubCtrl', function () { + + var scope, subVm; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + subVm = $controller('SubCtrl as subVm', { $scope: scope, 'Utils':'', 'resolveLocalData':{stuff:"And Things"}}); + })); + + it("Sets the page title", function () { + expect(subVm.title).toEqual("Subpage"); + }) + + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/pages/sub/sample.dir.spec.js b/src/reStart-app/pages/sub/sample.dir.spec.js new file mode 100644 index 0000000..a1d2a8b --- /dev/null +++ b/src/reStart-app/pages/sub/sample.dir.spec.js @@ -0,0 +1,31 @@ +; (function () { + var $compile, $rootScope, scope, element; + beforeEach(function () { + module('reStart'); + }); + + describe('Directive: navControl', function () { + beforeEach(function () { + module('templates'); + + inject( + ['$compile', '$rootScope', function ($c, $r) { + $compile = $c; + $rootScope = $r; + scope = $rootScope.$new(); + }] + ) + element = $compile('<sample-directive json-data="sub.json">'+"I've been transcluded!"+'</sample-directive>')($rootScope); + angular.element(document.body).append(element); + //trigger directive to be injected + $rootScope.$digest(); + }); + + it("injects a template via directive", function () { + expect(angular.element('body').html()).toContain("included by a directive"); + }); + it("transcludes data to the injected template", function () { + expect(angular.element('.transclude').html()).toContain("I've been transcluded!"); + }); + }); +})(); \ No newline at end of file diff --git a/src/reStart-app/pages/sub/sample.tpl.html b/src/reStart-app/pages/sub/sample.tpl.html index aa8b4f4..d5e9142 100644 --- a/src/reStart-app/pages/sub/sample.tpl.html +++ b/src/reStart-app/pages/sub/sample.tpl.html @@ -1,4 +1,4 @@ <div> - <p>I am a template included by a directive. {{sd.jsonData.summary}}</p> - <p ng-transclude></p> + <p class="template">I am a template included by a directive. {{sd.jsonData.summary}}</p> + <p ng-transclude class="transclude"></p> </div> \ No newline at end of file diff --git a/src/reStart-app/reStart-app.js b/src/reStart-app/reStart-app.js index 0d16fab..4273ecb 100644 --- a/src/reStart-app/reStart-app.js +++ b/src/reStart-app/reStart-app.js @@ -1,522 +1,528 @@ -// application module setter -(function() { - 'use strict'; - - angular - .module('reStart', ['ngRoute', 'ngResource', 'ngSanitize', 'mediaCheck', 'resize']); +// application module setter +(function() { + 'use strict'; + + angular + .module('reStart', ['ngRoute', 'ngResource', 'ngSanitize', 'mediaCheck', 'resize']); })(); -(function() { - 'use strict'; - - angular - .module('reStart') - .controller('PageCtrl', PageCtrl); - - PageCtrl.$inject = ['Page', '$scope', 'MQ', 'mediaCheck']; - - function PageCtrl(Page, $scope, MQ, mediaCheck) { - var page = this; - - // private variables - var _handlingRouteChangeError = false; - // Set up functionality to run on enter/exit of media query - var mc = mediaCheck.init({ - scope: $scope, - media: { - mq: MQ.SMALL, - enter: _enterMobile, - exit: _exitMobile - }, - debounce: 200 - }); - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // associate page <title> - page.pageTitle = Page; - - $scope.$on('$routeChangeStart', _routeChangeStart); - $scope.$on('$routeChangeSuccess', _routeChangeSuccess); - $scope.$on('$routeChangeError', _routeChangeError); - } - - /** - * Enter mobile media query - * $broadcast 'enter-mobile' event - * - * @private - */ - function _enterMobile() { - $scope.$broadcast('enter-mobile'); - } - - /** - * Exit mobile media query - * $broadcast 'exit-mobile' event - * - * @private - */ - function _exitMobile() { - $scope.$broadcast('exit-mobile'); - } - - /** - * Turn on loading state - * - * @private - */ - function _loadingOn() { - $scope.$broadcast('loading-on'); - } - - /** - * Turn off loading state - * - * @private - */ - function _loadingOff() { - $scope.$broadcast('loading-off'); - } - - /** - * Route change start handler - * If next route has resolve, turn on loading - * - * @param $event {object} - * @param next {object} - * @param current {object} - * @private - */ - function _routeChangeStart($event, next, current) { - if (next.$$route && next.$$route.resolve) { - _loadingOn(); - } - } - - /** - * Route change success handler - * Match current media query and run appropriate function - * If current route has been resolved, turn off loading - * - * @param $event {object} - * @param current {object} - * @param previous {object} - * @private - */ - function _routeChangeSuccess($event, current, previous) { - mc.matchCurrent(MQ.SMALL); - - if (current.$$route && current.$$route.resolve) { - _loadingOff(); - } - } - - /** - * Route change error handler - * Handle route resolve failures - * - * @param $event {object} - * @param current {object} - * @param previous {object} - * @param rejection {object} - * @private - */ - function _routeChangeError($event, current, previous, rejection) { - if (_handlingRouteChangeError) { - return; - } - - _handlingRouteChangeError = true; - _loadingOff(); - - var destination = (current && (current.title || current.name || current.loadedTemplateUrl)) || 'unknown target'; - var msg = 'Error routing to ' + destination + '. ' + (rejection.msg || ''); - - console.log(msg); - - /** - * On routing error, show an error. - */ - alert('An error occurred. Please try again.'); - } - } +(function () { + 'use strict'; + + angular + .module('reStart') + .controller('PageCtrl', PageCtrl); + + PageCtrl.$inject = ['Page', '$scope', 'MQ', 'mediaCheck']; + + function PageCtrl(Page, $scope, MQ, mediaCheck) { + var page = this; + + // private variables + var _handlingRouteChangeError = false; + // Set up functionality to run on enter/exit of media query + var mc = mediaCheck.init({ + scope: $scope, + media: { + mq: MQ.SMALL, + enter: _enterMobile, + exit: _exitMobile + }, + debounce: 200 + }); + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // associate page <title> + page.pageTitle = Page; + + $scope.$on('$routeChangeStart', _routeChangeStart); + $scope.$on('$routeChangeSuccess', _routeChangeSuccess); + $scope.$on('$routeChangeError', _routeChangeError); + } + + /** + * Enter mobile media query + * $broadcast 'enter-mobile' event + * + * @private + */ + function _enterMobile() { + $scope.$broadcast('enter-mobile'); + } + + /** + * Exit mobile media query + * $broadcast 'exit-mobile' event + * + * @private + */ + function _exitMobile() { + $scope.$broadcast('exit-mobile'); + } + + /** + * Turn on loading state + * + * @private + */ + function _loadingOn() { + $scope.$broadcast('loading-on'); + } + + /** + * Turn off loading state + * + * @private + */ + function _loadingOff() { + $scope.$broadcast('loading-off'); + } + + /** + * Route change start handler + * If next route has resolve, turn on loading + * + * @param $event {object} + * @param next {object} + * @param current {object} + * @private + */ + function _routeChangeStart($event, next, current) { + if (next.$$route && next.$$route.resolve) { + _loadingOn(); + } + } + + /** + * Route change success handler + * Match current media query and run appropriate function + * If current route has been resolved, turn off loading + * + * @param $event {object} + * @param current {object} + * @param previous {object} + * @private + */ + function _routeChangeSuccess($event, current, previous) { + mc.matchCurrent(MQ.SMALL); + + if (current.$$route && current.$$route.resolve) { + _loadingOff(); + } + } + + /** + * Route change error handler + * Handle route resolve failures + * + * @param $event {object} + * @param current {object} + * @param previous {object} + * @param rejection {object} + * @private + */ + function _routeChangeError($event, current, previous, rejection) { + if (_handlingRouteChangeError) { + return; + } + + _handlingRouteChangeError = true; + _loadingOff(); + + var destination = (current && (current.title || current.name || current.loadedTemplateUrl)) || 'unknown target'; + var msg = 'Error routing to ' + destination + '. ' + (rejection.msg || ''); + + console.log(msg); + + /** + * On routing error, show an error. + */ + alert('An error occurred. Please try again.'); + } + + PageCtrl.enterMobile = _enterMobile;//test code + PageCtrl.exitMobile = _exitMobile;//test code + PageCtrl.loadingOn = _loadingOn;//test code + PageCtrl.loadingOff = _loadingOff;//test code + return PageCtrl;//test code + } })(); -(function() { - 'use strict'; - - angular - .module('reStart') - .factory('Page', Page); - - function Page() { - // private vars - var siteTitle = 'reStart Angular'; - var pageTitle = 'Home'; - - // callable members - return { - getTitle: getTitle, - setTitle: setTitle - }; - - /** - * Title function - * Sets site title and page title - * - * @returns {string} site title + page title - */ - function getTitle() { - return siteTitle + ' | ' + pageTitle; - } - - /** - * Set page title - * - * @param newTitle {string} - */ - function setTitle(newTitle) { - pageTitle = newTitle; - } - } +(function() { + 'use strict'; + + angular + .module('reStart') + .factory('Page', Page); + + function Page() { + // private vars + var siteTitle = 'reStart Angular'; + var pageTitle = 'Home'; + + // callable members + return { + getTitle: getTitle, + setTitle: setTitle + }; + + /** + * Title function + * Sets site title and page title + * + * @returns {string} site title + page title + */ + function getTitle() { + return siteTitle + ' | ' + pageTitle; + } + + /** + * Set page title + * + * @param newTitle {string} + */ + function setTitle(newTitle) { + pageTitle = newTitle; + } + } })(); -// "global" object to share between controllers -(function() { - 'use strict'; - - angular - .module('reStart') - .factory('Utils', Utils); - - function Utils() { - var greeting = 'Hello'; - - // callable members - return { - greeting: greeting, - alertGreeting: alertGreeting - }; - - /** - * Alert greeting - * - * @param name {string} - */ - function alertGreeting(name) { - alert(greeting + ', ' + name + '!'); - } - } +// "global" object to share between controllers +(function() { + 'use strict'; + + angular + .module('reStart') + .factory('Utils', Utils); + + function Utils() { + var greeting = 'Hello'; + + // callable members + return { + greeting: greeting, + alertGreeting: alertGreeting + }; + + /** + * Alert greeting + * + * @param name {string} + */ + function alertGreeting(name) { + alert(greeting + ', ' + name + '!'); + } + } })(); -// application config -(function() { - 'use strict'; - - angular - .module('reStart') - .config(appConfig); - - appConfig.$inject = ['$routeProvider', '$locationProvider']; - - function appConfig($routeProvider, $locationProvider) { - $routeProvider - .when('/', { - templateUrl: 'reStart-app/pages/home/Home.view.html', - controller: 'HomeCtrl', - controllerAs: 'home' - }) - .when('/subpage', { - templateUrl: 'reStart-app/pages/sub/Sub.view.html', - controller: 'SubCtrl', - controllerAs: 'sub', - resolve: { - resolveLocalData: resolveLocalData - } - }) - .otherwise({ - templateUrl: 'reStart-app/pages/error404/Error404.view.html', - controller: 'Error404Ctrl', - controllerAs: 'e404' - }); - - $locationProvider - .html5Mode({ - enabled: true - }) - .hashPrefix('!'); - } - - resolveLocalData.$inject = ['JSONData']; - /** - * Get local data for route resolve - * - * @param JSONData {factory} - * @returns {promise} data - */ - function resolveLocalData(JSONData) { - return JSONData.getLocalData(); - } +// application config +(function() { + 'use strict'; + + angular + .module('reStart') + .config(appConfig); + + appConfig.$inject = ['$routeProvider', '$locationProvider']; + + function appConfig($routeProvider, $locationProvider) { + $routeProvider + .when('/', { + templateUrl: 'reStart-app/pages/home/Home.view.html', + controller: 'HomeCtrl', + controllerAs: 'home' + }) + .when('/subpage', { + templateUrl: 'reStart-app/pages/sub/Sub.view.html', + controller: 'SubCtrl', + controllerAs: 'sub', + resolve: { + resolveLocalData: resolveLocalData + } + }) + .otherwise({ + templateUrl: 'reStart-app/pages/error404/Error404.view.html', + controller: 'Error404Ctrl', + controllerAs: 'e404' + }); + + $locationProvider + .html5Mode({ + enabled: true + }) + .hashPrefix('!'); + } + + resolveLocalData.$inject = ['JSONData']; + /** + * Get local data for route resolve + * + * @param JSONData {factory} + * @returns {promise} data + */ + function resolveLocalData(JSONData) { + return JSONData.getLocalData(); + } })(); -// fetch JSON data to share between controllers -(function() { - 'use strict'; - - angular - .module('reStart') - .factory('JSONData', JSONData); - - JSONData.$inject = ['$http', 'Res']; - - function JSONData($http, Res) { - // callable members - return { - getLocalData: getLocalData - }; - - /** - * GET local JSON data file and return results - * - * @returns {promise} - */ - function getLocalData() { - return $http - .get('/data/data.json') - .then(Res.success, Res.error); - } - } +// fetch JSON data to share between controllers +(function() { + 'use strict'; + + angular + .module('reStart') + .factory('JSONData', JSONData); + + JSONData.$inject = ['$http', 'Res']; + + function JSONData($http, Res) { + // callable members + return { + getLocalData: getLocalData + }; + + /** + * GET local JSON data file and return results + * + * @returns {promise} + */ + function getLocalData() { + return $http + .get('/data/data.json') + .then(Res.success, Res.error); + } + } })(); -(function() { - 'use strict'; - - angular - .module('reStart') - .factory('Res', Res); - - function Res() { - // callable members - return { - success: success, - error: error - }; - - /** - * Promise response function - * Checks typeof data returned and succeeds if JS object, throws error if not - * Useful for APIs (ie, with nginx) where server error HTML page may be returned in error - * - * @param response {*} data from $http - * @returns {*} object, array - */ - function success(response) { - if (typeof response.data === 'object') { - return response.data; - } else { - throw new Error('retrieved data is not typeof object.'); - } - } - - /** - * Promise response function - error - * Throws an error with error data - * - * @param error {object} - */ - function error(error) { - throw new Error('Error retrieving data', error); - } - } +(function() { + 'use strict'; + + angular + .module('reStart') + .factory('Res', Res); + + function Res() { + // callable members + return { + success: success, + error: error + }; + + /** + * Promise response function + * Checks typeof data returned and succeeds if JS object, throws error if not + * Useful for APIs (ie, with nginx) where server error HTML page may be returned in error + * + * @param response {*} data from $http + * @returns {*} object, array + */ + function success(response) { + if (typeof response.data === 'object') { + return response.data; + } else { + throw new Error('retrieved data is not typeof object.'); + } + } + + /** + * Promise response function - error + * Throws an error with error data + * + * @param error {object} + */ + function error(error) { + throw new Error('Error retrieving data', error); + } + } })(); -(function() { - 'use strict'; - - // media query constants - var MQ = { - SMALL: '(max-width: 767px)', - LARGE: '(min-width: 768px)' - }; - - angular - .module('reStart') - .constant('MQ', MQ); +(function() { + 'use strict'; + + angular + .module('reStart') + .directive('loading', loading); + + loading.$inject = ['$window', 'resize']; + + function loading($window, resize) { + // return directive + return { + restrict: 'EA', + replace: true, + templateUrl: 'reStart-app/core/ui/loading.tpl.html', + transclude: true, + controller: loadingCtrl, + controllerAs: 'loading', + bindToController: true, + link: loadingLink + }; + + /** + * loading LINK + * Disables page scrolling when loading overlay is open + * + * @param $scope + * @param $element + * @param $attrs + * @param loading {controller} + */ + function loadingLink($scope, $element, $attrs, loading) { + // private variables + var _$body = angular.element('body'); + var _winHeight = $window.innerHeight + 'px'; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // initialize debounced resize + var _rs = resize.init({ + scope: $scope, + resizedFn: _resized, + debounce: 200 + }); + + // $watch active state + $scope.$watch('loading.active', _$watchActive); + } + + /** + * Window resized + * If loading, reapply body height + * to prevent scrollbar + * + * @private + */ + function _resized() { + _winHeight = $window.innerHeight + 'px'; + + if (loading.active) { + _$body.css({ + height: _winHeight, + overflowY: 'hidden' + }); + } + } + + /** + * $watch loading.active + * + * @param newVal {boolean} + * @param oldVal {undefined|boolean} + * @private + */ + function _$watchActive(newVal, oldVal) { + if (newVal) { + _open(); + } else { + _close(); + } + } + + /** + * Open loading + * Disable scroll + * + * @private + */ + function _open() { + _$body.css({ + height: _winHeight, + overflowY: 'hidden' + }); + } + + /** + * Close loading + * Enable scroll + * + * @private + */ + function _close() { + _$body.css({ + height: 'auto', + overflowY: 'auto' + }); + } + } + } + + loadingCtrl.$inject = ['$scope']; + /** + * loading CONTROLLER + * Update the loading status based + * on routeChange state + */ + function loadingCtrl($scope) { + var loading = this; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // turn on loading for initial page load + _loadingActive(); + + $scope.$on('loading-on', _loadingActive); + $scope.$on('loading-off', _loadingInactive); + } + + /** + * Set loading to active + * + * @private + */ + function _loadingActive() { + loading.active = true; + } + + /** + * Set loading to inactive + * + * @private + */ + function _loadingInactive() { + loading.active = false; + } + } + })(); -(function() { - 'use strict'; - - angular - .module('reStart') - .directive('loading', loading); - - loading.$inject = ['$window', 'resize']; - - function loading($window, resize) { - // return directive - return { - restrict: 'EA', - replace: true, - templateUrl: 'reStart-app/core/ui/loading.tpl.html', - transclude: true, - controller: loadingCtrl, - controllerAs: 'loading', - bindToController: true, - link: loadingLink - }; - - /** - * loading LINK - * Disables page scrolling when loading overlay is open - * - * @param $scope - * @param $element - * @param $attrs - * @param loading {controller} - */ - function loadingLink($scope, $element, $attrs, loading) { - // private variables - var _$body = angular.element('body'); - var _winHeight = $window.innerHeight + 'px'; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // initialize debounced resize - var _rs = resize.init({ - scope: $scope, - resizedFn: _resized, - debounce: 200 - }); - - // $watch active state - $scope.$watch('loading.active', _$watchActive); - } - - /** - * Window resized - * If loading, reapply body height - * to prevent scrollbar - * - * @private - */ - function _resized() { - _winHeight = $window.innerHeight + 'px'; - - if (loading.active) { - _$body.css({ - height: _winHeight, - overflowY: 'hidden' - }); - } - } - - /** - * $watch loading.active - * - * @param newVal {boolean} - * @param oldVal {undefined|boolean} - * @private - */ - function _$watchActive(newVal, oldVal) { - if (newVal) { - _open(); - } else { - _close(); - } - } - - /** - * Open loading - * Disable scroll - * - * @private - */ - function _open() { - _$body.css({ - height: _winHeight, - overflowY: 'hidden' - }); - } - - /** - * Close loading - * Enable scroll - * - * @private - */ - function _close() { - _$body.css({ - height: 'auto', - overflowY: 'auto' - }); - } - } - } - - loadingCtrl.$inject = ['$scope']; - /** - * loading CONTROLLER - * Update the loading status based - * on routeChange state - */ - function loadingCtrl($scope) { - var loading = this; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // turn on loading for initial page load - _loadingActive(); - - $scope.$on('loading-on', _loadingActive); - $scope.$on('loading-off', _loadingInactive); - } - - /** - * Set loading to active - * - * @private - */ - function _loadingActive() { - loading.active = true; - } - - /** - * Set loading to inactive - * - * @private - */ - function _loadingInactive() { - loading.active = false; - } - } - +(function() { + 'use strict'; + + // media query constants + var MQ = { + SMALL: '(max-width: 767px)', + LARGE: '(min-width: 768px)' + }; + + angular + .module('reStart') + .constant('MQ', MQ); })(); -(function() { - 'use strict'; - - angular - .module('reStart') - .filter('trustAsHTML', trustAsHTML); - - trustAsHTML.$inject = ['$sce']; - - function trustAsHTML($sce) { - return function(text) { - return $sce.trustAsHtml(text); - }; - } +(function() { + 'use strict'; + + angular + .module('reStart') + .filter('trustAsHTML', trustAsHTML); + + trustAsHTML.$inject = ['$sce']; + + function trustAsHTML($sce) { + return function(text) { + return $sce.trustAsHtml(text); + }; + } })(); (function() { 'use strict'; @@ -591,171 +597,177 @@ } })(); -(function() { - 'use strict'; - - angular - .module('reStart') - .directive('navControl', navControl); - - navControl.$inject = ['$window', 'resize']; - - function navControl($window, resize) { - // return directive - return { - restrict: 'EA', - link: navControlLink - }; - - /** - * navControl LINK function - * - * @param $scope - */ - function navControlLink($scope) { - // data model - $scope.nav = {}; - - // private variables - var _$body = angular.element('body'); - var _layoutCanvas = _$body.find('.layout-canvas'); - var _navOpen; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // initialize debounced resize - var _rs = resize.init({ - scope: $scope, - resizedFn: _resized, - debounce: 100 - }); - - $scope.$on('$locationChangeStart', _$locationChangeStart); - $scope.$on('enter-mobile', _enterMobile); - $scope.$on('exit-mobile', _exitMobile); - } - - /** - * Resized window (debounced) - * - * @private - */ - function _resized() { - _layoutCanvas.css({ - minHeight: $window.innerHeight + 'px' - }); - } - - /** - * Open mobile navigation - * - * @private - */ - function _openNav() { - _$body - .removeClass('nav-closed') - .addClass('nav-open'); - - _navOpen = true; - } - - /** - * Close mobile navigation - * - * @private - */ - function _closeNav() { - _$body - .removeClass('nav-open') - .addClass('nav-closed'); - - _navOpen = false; - } - - /** - * Toggle nav open/closed - */ - function toggleNav() { - if (!_navOpen) { - _openNav(); - } else { - _closeNav(); - } - } - - /** - * When changing location, close the nav if it's open - */ - function _$locationChangeStart() { - if (_navOpen) { - _closeNav(); - } - } - - /** - * Function to execute when entering mobile media query - * Close nav and set up menu toggling functionality - * - * @private - */ - function _enterMobile(mq) { - _closeNav(); - - // bind function to toggle mobile navigation open/closed - $scope.nav.toggleNav = toggleNav; - } - - /** - * Function to execute when exiting mobile media query - * Disable menu toggling and remove body classes - * - * @private - */ - function _exitMobile(mq) { - // unbind function to toggle mobile navigation open/closed - $scope.nav.toggleNav = null; - - _$body.removeClass('nav-closed nav-open'); - } - } - } - +(function() { + 'use strict'; + + angular + .module('reStart') + .directive('navControl', navControl); + + navControl.$inject = ['$window', 'resize']; + + function navControl($window, resize) { + // return directive + return { + restrict: 'EA', + link: navControlLink + }; + + /** + * navControl LINK function + * + * @param $scope + */ + function navControlLink($scope) { + // data model + $scope.nav = {}; + + // private variables + var _$body = angular.element('body'); + var _layoutCanvas = _$body.find('.layout-canvas'); + var _navOpen; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // initialize debounced resize + var _rs = resize.init({ + scope: $scope, + resizedFn: _resized, + debounce: 100 + }); + + $scope.$on('$locationChangeStart', _$locationChangeStart); + $scope.$on('enter-mobile', _enterMobile); + $scope.$on('exit-mobile', _exitMobile); + } + + /** + * Resized window (debounced) + * + * @private + */ + function _resized() { + _layoutCanvas.css({ + minHeight: $window.innerHeight + 'px' + }); + } + + /** + * Open mobile navigation + * + * @private + */ + function _openNav() { + _$body + .removeClass('nav-closed') + .addClass('nav-open'); + + _navOpen = true; + } + + /** + * Close mobile navigation + * + * @private + */ + function _closeNav() { + _$body + .removeClass('nav-open') + .addClass('nav-closed'); + + _navOpen = false; + } + + /** + * Toggle nav open/closed + */ + function toggleNav() { + console.log("yo"); + if (!_navOpen) { + _openNav(); + } else { + _closeNav(); + } + } + + /** + * When changing location, close the nav if it's open + */ + function _$locationChangeStart() { + if (_navOpen) { + _closeNav(); + } + } + + /** + * Function to execute when entering mobile media query + * Close nav and set up menu toggling functionality + * + * @private + */ + function _enterMobile(mq) { + _closeNav(); + + // bind function to toggle mobile navigation open/closed + $scope.nav.toggleNav = toggleNav; + } + + /** + * Function to execute when exiting mobile media query + * Disable menu toggling and remove body classes + * + * @private + */ + function _exitMobile(mq) { + // unbind function to toggle mobile navigation open/closed + $scope.nav.toggleNav = null; + + _$body.removeClass('nav-closed nav-open'); + } + } + } + })(); -(function() { - 'use strict'; - - angular - .module('reStart') - .controller('Error404Ctrl', Error404Ctrl); - - Error404Ctrl.$inject = ['$scope', 'Page']; - - function Error404Ctrl($scope, Page) { - var e404 = this; - - // bindable members - e404.title = '404 - Page Not Found'; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // set page <title> - Page.setTitle(e404.title); - - $scope.$emit('loading-off'); - } - } +(function() { + 'use strict'; + + angular + .module('reStart') + .controller('Error404Ctrl', Error404Ctrl); + + Error404Ctrl.$inject = ['$scope', 'Page']; + + function Error404Ctrl($scope, Page) { + var e404 = this; + + // bindable members + e404.title = '404 - Page Not Found'; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // set page <title> + Page.setTitle(e404.title); + + // no data to load, but loading state might be on + $scope.$emit('loading-off'); + } + + return { //test code + init: _init //test code + } //test code + } })(); (function() { 'use strict'; @@ -846,37 +858,18 @@ function _exitMobile() { home.viewformat = 'large'; } - } -})(); -(function() { - 'use strict'; - - angular - .module('reStart') - .controller('SubCtrl', SubCtrl); - - SubCtrl.$inject = ['Utils', 'Page', 'resolveLocalData']; - function SubCtrl(Utils, Page, resolveLocalData) { - // controllerAs ViewModel - var sub = this; - - // bindable members - sub.title = 'Subpage'; - sub.global = Utils; - sub.json = resolveLocalData; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // set page <title> - Page.setTitle(sub.title); - } + function getView() { //test code + return home.viewformat; //test code + } //test code + + return { //test code + enterMobile: _enterMobile, //test code + exitMobile: _exitMobile, //test code + getJsonSucess: _getJsonSuccess, //test code + activate: _activate, //test code + getView: getView //test code + } //test code } })(); /** @@ -962,4 +955,35 @@ } })(); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5tb2R1bGUuanMiLCJjb3JlL1BhZ2UuY3RybC5qcyIsImNvcmUvUGFnZS5mYWN0b3J5LmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LmpzIiwiY29yZS9hcHAtc2V0dXAvYXBwLmNvbmZpZy5qcyIsImNvcmUvZ2V0LWRhdGEvSlNPTkRhdGEuZmFjdG9yeS5qcyIsImNvcmUvZ2V0LWRhdGEvUmVzLmZhY3RvcnkuanMiLCJjb3JlL3VpL01RLmNvbnN0YW50LmpzIiwiY29yZS91aS9sb2FkaW5nLmRpci5qcyIsImNvcmUvdWkvdHJ1c3RBc0hUTUwuZmlsdGVyLmpzIiwibW9kdWxlcy9oZWFkZXIvSGVhZGVyLmN0cmwuanMiLCJtb2R1bGVzL2hlYWRlci9uYXZDb250cm9sLmRpci5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuanMiLCJwYWdlcy9ob21lL0hvbWUuY3RybC5qcyIsInBhZ2VzL3N1Yi9TdWIuY3RybC5qcyIsInBhZ2VzL3N1Yi9zYW1wbGUuZGlyLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDTkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM3SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3JDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDL0pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDeEVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDdklBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzdCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzFGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzlCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InJlU3RhcnQtYXBwLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gYXBwbGljYXRpb24gbW9kdWxlIHNldHRlclxuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnLCBbJ25nUm91dGUnLCAnbmdSZXNvdXJjZScsICduZ1Nhbml0aXplJywgJ21lZGlhQ2hlY2snLCAncmVzaXplJ10pO1xufSkoKTsiLCIoZnVuY3Rpb24oKSB7XG5cdCd1c2Ugc3RyaWN0JztcblxuXHRhbmd1bGFyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXG5cdFx0LmNvbnRyb2xsZXIoJ1BhZ2VDdHJsJywgUGFnZUN0cmwpO1xuXG5cdFBhZ2VDdHJsLiRpbmplY3QgPSBbJ1BhZ2UnLCAnJHNjb3BlJywgJ01RJywgJ21lZGlhQ2hlY2snXTtcblxuXHRmdW5jdGlvbiBQYWdlQ3RybChQYWdlLCAkc2NvcGUsIE1RLCBtZWRpYUNoZWNrKSB7XG5cdFx0dmFyIHBhZ2UgPSB0aGlzO1xuXG5cdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcblx0XHR2YXIgX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvciA9IGZhbHNlO1xuXHRcdC8vIFNldCB1cCBmdW5jdGlvbmFsaXR5IHRvIHJ1biBvbiBlbnRlci9leGl0IG9mIG1lZGlhIHF1ZXJ5XG5cdFx0dmFyIG1jID0gbWVkaWFDaGVjay5pbml0KHtcblx0XHRcdHNjb3BlOiAkc2NvcGUsXG5cdFx0XHRtZWRpYToge1xuXHRcdFx0XHRtcTogTVEuU01BTEwsXG5cdFx0XHRcdGVudGVyOiBfZW50ZXJNb2JpbGUsXG5cdFx0XHRcdGV4aXQ6IF9leGl0TW9iaWxlXG5cdFx0XHR9LFxuXHRcdFx0ZGVib3VuY2U6IDIwMFxuXHRcdH0pO1xuXG5cdFx0X2luaXQoKTtcblxuXHRcdC8qKlxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXG5cdFx0ICpcblx0XHQgKiBAcHJpdmF0ZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xuXHRcdFx0Ly8gYXNzb2NpYXRlIHBhZ2UgPHRpdGxlPlxuXHRcdFx0cGFnZS5wYWdlVGl0bGUgPSBQYWdlO1xuXG5cdFx0XHQkc2NvcGUuJG9uKCckcm91dGVDaGFuZ2VTdGFydCcsIF9yb3V0ZUNoYW5nZVN0YXJ0KTtcblx0XHRcdCRzY29wZS4kb24oJyRyb3V0ZUNoYW5nZVN1Y2Nlc3MnLCBfcm91dGVDaGFuZ2VTdWNjZXNzKTtcblx0XHRcdCRzY29wZS4kb24oJyRyb3V0ZUNoYW5nZUVycm9yJywgX3JvdXRlQ2hhbmdlRXJyb3IpO1xuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIEVudGVyIG1vYmlsZSBtZWRpYSBxdWVyeVxuXHRcdCAqICRicm9hZGNhc3QgJ2VudGVyLW1vYmlsZScgZXZlbnRcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2VudGVyTW9iaWxlKCkge1xuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2VudGVyLW1vYmlsZScpO1xuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIEV4aXQgbW9iaWxlIG1lZGlhIHF1ZXJ5XG5cdFx0ICogJGJyb2FkY2FzdCAnZXhpdC1tb2JpbGUnIGV2ZW50XG5cdFx0ICpcblx0XHQgKiBAcHJpdmF0ZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIF9leGl0TW9iaWxlKCkge1xuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2V4aXQtbW9iaWxlJyk7XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogVHVybiBvbiBsb2FkaW5nIHN0YXRlXG5cdFx0ICpcblx0XHQgKiBAcHJpdmF0ZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIF9sb2FkaW5nT24oKSB7XG5cdFx0XHQkc2NvcGUuJGJyb2FkY2FzdCgnbG9hZGluZy1vbicpO1xuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIFR1cm4gb2ZmIGxvYWRpbmcgc3RhdGVcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdPZmYoKSB7XG5cdFx0XHQkc2NvcGUuJGJyb2FkY2FzdCgnbG9hZGluZy1vZmYnKTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBSb3V0ZSBjaGFuZ2Ugc3RhcnQgaGFuZGxlclxuXHRcdCAqIElmIG5leHQgcm91dGUgaGFzIHJlc29sdmUsIHR1cm4gb24gbG9hZGluZ1xuXHRcdCAqXG5cdFx0ICogQHBhcmFtICRldmVudCB7b2JqZWN0fVxuXHRcdCAqIEBwYXJhbSBuZXh0IHtvYmplY3R9XG5cdFx0ICogQHBhcmFtIGN1cnJlbnQge29iamVjdH1cblx0XHQgKiBAcHJpdmF0ZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIF9yb3V0ZUNoYW5nZVN0YXJ0KCRldmVudCwgbmV4dCwgY3VycmVudCkge1xuXHRcdFx0aWYgKG5leHQuJCRyb3V0ZSAmJiBuZXh0LiQkcm91dGUucmVzb2x2ZSkge1xuXHRcdFx0XHRfbG9hZGluZ09uKCk7XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogUm91dGUgY2hhbmdlIHN1Y2Nlc3MgaGFuZGxlclxuXHRcdCAqIE1hdGNoIGN1cnJlbnQgbWVkaWEgcXVlcnkgYW5kIHJ1biBhcHByb3ByaWF0ZSBmdW5jdGlvblxuXHRcdCAqIElmIGN1cnJlbnQgcm91dGUgaGFzIGJlZW4gcmVzb2x2ZWQsIHR1cm4gb2ZmIGxvYWRpbmdcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cblx0XHQgKiBAcGFyYW0gY3VycmVudCB7b2JqZWN0fVxuXHRcdCAqIEBwYXJhbSBwcmV2aW91cyB7b2JqZWN0fVxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX3JvdXRlQ2hhbmdlU3VjY2VzcygkZXZlbnQsIGN1cnJlbnQsIHByZXZpb3VzKSB7XG5cdFx0XHRtYy5tYXRjaEN1cnJlbnQoTVEuU01BTEwpO1xuXG5cdFx0XHRpZiAoY3VycmVudC4kJHJvdXRlICYmIGN1cnJlbnQuJCRyb3V0ZS5yZXNvbHZlKSB7XG5cdFx0XHRcdF9sb2FkaW5nT2ZmKCk7XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogUm91dGUgY2hhbmdlIGVycm9yIGhhbmRsZXJcblx0XHQgKiBIYW5kbGUgcm91dGUgcmVzb2x2ZSBmYWlsdXJlc1xuXHRcdCAqXG5cdFx0ICogQHBhcmFtICRldmVudCB7b2JqZWN0fVxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XG5cdFx0ICogQHBhcmFtIHByZXZpb3VzIHtvYmplY3R9XG5cdFx0ICogQHBhcmFtIHJlamVjdGlvbiB7b2JqZWN0fVxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX3JvdXRlQ2hhbmdlRXJyb3IoJGV2ZW50LCBjdXJyZW50LCBwcmV2aW91cywgcmVqZWN0aW9uKSB7XG5cdFx0XHRpZiAoX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvcikge1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cblx0XHRcdF9oYW5kbGluZ1JvdXRlQ2hhbmdlRXJyb3IgPSB0cnVlO1xuXHRcdFx0X2xvYWRpbmdPZmYoKTtcblxuXHRcdFx0dmFyIGRlc3RpbmF0aW9uID0gKGN1cnJlbnQgJiYgKGN1cnJlbnQudGl0bGUgfHwgY3VycmVudC5uYW1lIHx8IGN1cnJlbnQubG9hZGVkVGVtcGxhdGVVcmwpKSB8fCAndW5rbm93biB0YXJnZXQnO1xuXHRcdFx0dmFyIG1zZyA9ICdFcnJvciByb3V0aW5nIHRvICcgKyBkZXN0aW5hdGlvbiArICcuICcgKyAocmVqZWN0aW9uLm1zZyB8fCAnJyk7XG5cblx0XHRcdGNvbnNvbGUubG9nKG1zZyk7XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogT24gcm91dGluZyBlcnJvciwgc2hvdyBhbiBlcnJvci5cblx0XHRcdCAqL1xuXHRcdFx0YWxlcnQoJ0FuIGVycm9yIG9jY3VycmVkLiBQbGVhc2UgdHJ5IGFnYWluLicpO1xuXHRcdH1cblx0fVxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XG5cdCd1c2Ugc3RyaWN0JztcblxuXHRhbmd1bGFyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXG5cdFx0LmZhY3RvcnkoJ1BhZ2UnLCBQYWdlKTtcblxuXHRmdW5jdGlvbiBQYWdlKCkge1xuXHRcdC8vIHByaXZhdGUgdmFyc1xuXHRcdHZhciBzaXRlVGl0bGUgPSAncmVTdGFydCBBbmd1bGFyJztcblx0XHR2YXIgcGFnZVRpdGxlID0gJ0hvbWUnO1xuXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRnZXRUaXRsZTogZ2V0VGl0bGUsXG5cdFx0XHRzZXRUaXRsZTogc2V0VGl0bGVcblx0XHR9O1xuXG5cdFx0LyoqXG5cdFx0ICogVGl0bGUgZnVuY3Rpb25cblx0XHQgKiBTZXRzIHNpdGUgdGl0bGUgYW5kIHBhZ2UgdGl0bGVcblx0XHQgKlxuXHRcdCAqIEByZXR1cm5zIHtzdHJpbmd9IHNpdGUgdGl0bGUgKyBwYWdlIHRpdGxlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gZ2V0VGl0bGUoKSB7XG5cdFx0XHRyZXR1cm4gc2l0ZVRpdGxlICsgJyB8ICcgKyBwYWdlVGl0bGU7XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogU2V0IHBhZ2UgdGl0bGVcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSBuZXdUaXRsZSB7c3RyaW5nfVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIHNldFRpdGxlKG5ld1RpdGxlKSB7XG5cdFx0XHRwYWdlVGl0bGUgPSBuZXdUaXRsZTtcblx0XHR9XG5cdH1cbn0pKCk7IiwiLy8gXCJnbG9iYWxcIiBvYmplY3QgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5mYWN0b3J5KCdVdGlscycsIFV0aWxzKTtcblxuXHRmdW5jdGlvbiBVdGlscygpIHtcblx0XHR2YXIgZ3JlZXRpbmcgPSAnSGVsbG8nO1xuXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRncmVldGluZzogZ3JlZXRpbmcsXG5cdFx0XHRhbGVydEdyZWV0aW5nOiBhbGVydEdyZWV0aW5nXG5cdFx0fTtcblxuXHRcdC8qKlxuXHRcdCAqIEFsZXJ0IGdyZWV0aW5nXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gbmFtZSB7c3RyaW5nfVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIGFsZXJ0R3JlZXRpbmcobmFtZSkge1xuXHRcdFx0YWxlcnQoZ3JlZXRpbmcgKyAnLCAnICsgbmFtZSArICchJyk7XG5cdFx0fVxuXHR9XG59KSgpOyIsIi8vIGFwcGxpY2F0aW9uIGNvbmZpZ1xuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5jb25maWcoYXBwQ29uZmlnKTtcblxuXHRhcHBDb25maWcuJGluamVjdCA9IFsnJHJvdXRlUHJvdmlkZXInLCAnJGxvY2F0aW9uUHJvdmlkZXInXTtcblxuXHRmdW5jdGlvbiBhcHBDb25maWcoJHJvdXRlUHJvdmlkZXIsICRsb2NhdGlvblByb3ZpZGVyKSB7XG5cdFx0JHJvdXRlUHJvdmlkZXJcblx0XHRcdC53aGVuKCcvJywge1xuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL2hvbWUvSG9tZS52aWV3Lmh0bWwnLFxuXHRcdFx0XHRjb250cm9sbGVyOiAnSG9tZUN0cmwnLFxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdob21lJ1xuXHRcdFx0fSlcblx0XHRcdC53aGVuKCcvc3VicGFnZScsIHtcblx0XHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvU3ViLnZpZXcuaHRtbCcsXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdTdWJDdHJsJyxcblx0XHRcdFx0Y29udHJvbGxlckFzOiAnc3ViJyxcblx0XHRcdFx0cmVzb2x2ZToge1xuXHRcdFx0XHRcdHJlc29sdmVMb2NhbERhdGE6IHJlc29sdmVMb2NhbERhdGFcblx0XHRcdFx0fVxuXHRcdFx0fSlcblx0XHRcdC5vdGhlcndpc2Uoe1xuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LnZpZXcuaHRtbCcsXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdFcnJvcjQwNEN0cmwnLFxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdlNDA0J1xuXHRcdFx0fSk7XG5cblx0XHQkbG9jYXRpb25Qcm92aWRlclxuXHRcdFx0Lmh0bWw1TW9kZSh7XG5cdFx0XHRcdGVuYWJsZWQ6IHRydWVcblx0XHRcdH0pXG5cdFx0XHQuaGFzaFByZWZpeCgnIScpO1xuXHR9XG5cblx0cmVzb2x2ZUxvY2FsRGF0YS4kaW5qZWN0ID0gWydKU09ORGF0YSddO1xuXHQvKipcblx0ICogR2V0IGxvY2FsIGRhdGEgZm9yIHJvdXRlIHJlc29sdmVcblx0ICpcblx0ICogQHBhcmFtIEpTT05EYXRhIHtmYWN0b3J5fVxuXHQgKiBAcmV0dXJucyB7cHJvbWlzZX0gZGF0YVxuXHQgKi9cblx0ZnVuY3Rpb24gcmVzb2x2ZUxvY2FsRGF0YShKU09ORGF0YSkge1xuXHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKTtcblx0fVxufSkoKTsiLCIvLyBmZXRjaCBKU09OIGRhdGEgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5mYWN0b3J5KCdKU09ORGF0YScsIEpTT05EYXRhKTtcblxuXHRKU09ORGF0YS4kaW5qZWN0ID0gWyckaHR0cCcsICdSZXMnXTtcblxuXHRmdW5jdGlvbiBKU09ORGF0YSgkaHR0cCwgUmVzKSB7XG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRnZXRMb2NhbERhdGE6IGdldExvY2FsRGF0YVxuXHRcdH07XG5cblx0XHQvKipcblx0XHQgKiBHRVQgbG9jYWwgSlNPTiBkYXRhIGZpbGUgYW5kIHJldHVybiByZXN1bHRzXG5cdFx0ICpcblx0XHQgKiBAcmV0dXJucyB7cHJvbWlzZX1cblx0XHQgKi9cblx0XHRmdW5jdGlvbiBnZXRMb2NhbERhdGEoKSB7XG5cdFx0XHRyZXR1cm4gJGh0dHBcblx0XHRcdFx0LmdldCgnL2RhdGEvZGF0YS5qc29uJylcblx0XHRcdFx0LnRoZW4oUmVzLnN1Y2Nlc3MsIFJlcy5lcnJvcik7XG5cdFx0fVxuXHR9XG59KSgpOyIsIihmdW5jdGlvbigpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG5cdGFuZ3VsYXJcblx0XHQubW9kdWxlKCdyZVN0YXJ0Jylcblx0XHQuZmFjdG9yeSgnUmVzJywgUmVzKTtcblxuXHRmdW5jdGlvbiBSZXMoKSB7XG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRzdWNjZXNzOiBzdWNjZXNzLFxuXHRcdFx0ZXJyb3I6IGVycm9yXG5cdFx0fTtcblxuXHRcdC8qKlxuXHRcdCAqIFByb21pc2UgcmVzcG9uc2UgZnVuY3Rpb25cblx0XHQgKiBDaGVja3MgdHlwZW9mIGRhdGEgcmV0dXJuZWQgYW5kIHN1Y2NlZWRzIGlmIEpTIG9iamVjdCwgdGhyb3dzIGVycm9yIGlmIG5vdFxuXHRcdCAqIFVzZWZ1bCBmb3IgQVBJcyAoaWUsIHdpdGggbmdpbngpIHdoZXJlIHNlcnZlciBlcnJvciBIVE1MIHBhZ2UgbWF5IGJlIHJldHVybmVkIGluIGVycm9yXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gcmVzcG9uc2Ugeyp9IGRhdGEgZnJvbSAkaHR0cFxuXHRcdCAqIEByZXR1cm5zIHsqfSBvYmplY3QsIGFycmF5XG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gc3VjY2VzcyhyZXNwb25zZSkge1xuXHRcdFx0aWYgKHR5cGVvZiByZXNwb25zZS5kYXRhID09PSAnb2JqZWN0Jykge1xuXHRcdFx0XHRyZXR1cm4gcmVzcG9uc2UuZGF0YTtcblx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdHRocm93IG5ldyBFcnJvcigncmV0cmlldmVkIGRhdGEgaXMgbm90IHR5cGVvZiBvYmplY3QuJyk7XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogUHJvbWlzZSByZXNwb25zZSBmdW5jdGlvbiAtIGVycm9yXG5cdFx0ICogVGhyb3dzIGFuIGVycm9yIHdpdGggZXJyb3IgZGF0YVxuXHRcdCAqXG5cdFx0ICogQHBhcmFtIGVycm9yIHtvYmplY3R9XG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gZXJyb3IoZXJyb3IpIHtcblx0XHRcdHRocm93IG5ldyBFcnJvcignRXJyb3IgcmV0cmlldmluZyBkYXRhJywgZXJyb3IpO1xuXHRcdH1cblx0fVxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XG5cdCd1c2Ugc3RyaWN0JztcblxuXHQvLyBtZWRpYSBxdWVyeSBjb25zdGFudHNcblx0dmFyIE1RID0ge1xuXHRcdFNNQUxMOiAnKG1heC13aWR0aDogNzY3cHgpJyxcblx0XHRMQVJHRTogJyhtaW4td2lkdGg6IDc2OHB4KSdcblx0fTtcblxuXHRhbmd1bGFyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXG5cdFx0LmNvbnN0YW50KCdNUScsIE1RKTtcbn0pKCk7IiwiKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5kaXJlY3RpdmUoJ2xvYWRpbmcnLCBsb2FkaW5nKTtcblxuXHRsb2FkaW5nLiRpbmplY3QgPSBbJyR3aW5kb3cnLCAncmVzaXplJ107XG5cblx0ZnVuY3Rpb24gbG9hZGluZygkd2luZG93LCByZXNpemUpIHtcblx0XHQvLyByZXR1cm4gZGlyZWN0aXZlXG5cdFx0cmV0dXJuIHtcblx0XHRcdHJlc3RyaWN0OiAnRUEnLFxuXHRcdFx0cmVwbGFjZTogdHJ1ZSxcblx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvY29yZS91aS9sb2FkaW5nLnRwbC5odG1sJyxcblx0XHRcdHRyYW5zY2x1ZGU6IHRydWUsXG5cdFx0XHRjb250cm9sbGVyOiBsb2FkaW5nQ3RybCxcblx0XHRcdGNvbnRyb2xsZXJBczogJ2xvYWRpbmcnLFxuXHRcdFx0YmluZFRvQ29udHJvbGxlcjogdHJ1ZSxcblx0XHRcdGxpbms6IGxvYWRpbmdMaW5rXG5cdFx0fTtcblxuXHRcdC8qKlxuXHRcdCAqIGxvYWRpbmcgTElOS1xuXHRcdCAqIERpc2FibGVzIHBhZ2Ugc2Nyb2xsaW5nIHdoZW4gbG9hZGluZyBvdmVybGF5IGlzIG9wZW5cblx0XHQgKlxuXHRcdCAqIEBwYXJhbSAkc2NvcGVcblx0XHQgKiBAcGFyYW0gJGVsZW1lbnRcblx0XHQgKiBAcGFyYW0gJGF0dHJzXG5cdFx0ICogQHBhcmFtIGxvYWRpbmcge2NvbnRyb2xsZXJ9XG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gbG9hZGluZ0xpbmsoJHNjb3BlLCAkZWxlbWVudCwgJGF0dHJzLCBsb2FkaW5nKSB7XG5cdFx0XHQvLyBwcml2YXRlIHZhcmlhYmxlc1xuXHRcdFx0dmFyIF8kYm9keSA9IGFuZ3VsYXIuZWxlbWVudCgnYm9keScpO1xuXHRcdFx0dmFyIF93aW5IZWlnaHQgPSAkd2luZG93LmlubmVySGVpZ2h0ICsgJ3B4JztcblxuXHRcdFx0X2luaXQoKTtcblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxuXHRcdFx0ICpcblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xuXHRcdFx0XHQvLyBpbml0aWFsaXplIGRlYm91bmNlZCByZXNpemVcblx0XHRcdFx0dmFyIF9ycyA9IHJlc2l6ZS5pbml0KHtcblx0XHRcdFx0XHRzY29wZTogJHNjb3BlLFxuXHRcdFx0XHRcdHJlc2l6ZWRGbjogX3Jlc2l6ZWQsXG5cdFx0XHRcdFx0ZGVib3VuY2U6IDIwMFxuXHRcdFx0XHR9KTtcblxuXHRcdFx0XHQvLyAkd2F0Y2ggYWN0aXZlIHN0YXRlXG5cdFx0XHRcdCRzY29wZS4kd2F0Y2goJ2xvYWRpbmcuYWN0aXZlJywgXyR3YXRjaEFjdGl2ZSk7XG5cdFx0XHR9XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogV2luZG93IHJlc2l6ZWRcblx0XHRcdCAqIElmIGxvYWRpbmcsIHJlYXBwbHkgYm9keSBoZWlnaHRcblx0XHRcdCAqIHRvIHByZXZlbnQgc2Nyb2xsYmFyXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX3Jlc2l6ZWQoKSB7XG5cdFx0XHRcdF93aW5IZWlnaHQgPSAkd2luZG93LmlubmVySGVpZ2h0ICsgJ3B4JztcblxuXHRcdFx0XHRpZiAobG9hZGluZy5hY3RpdmUpIHtcblx0XHRcdFx0XHRfJGJvZHkuY3NzKHtcblx0XHRcdFx0XHRcdGhlaWdodDogX3dpbkhlaWdodCxcblx0XHRcdFx0XHRcdG92ZXJmbG93WTogJ2hpZGRlbidcblx0XHRcdFx0XHR9KTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqICR3YXRjaCBsb2FkaW5nLmFjdGl2ZVxuXHRcdFx0ICpcblx0XHRcdCAqIEBwYXJhbSBuZXdWYWwge2Jvb2xlYW59XG5cdFx0XHQgKiBAcGFyYW0gb2xkVmFsIHt1bmRlZmluZWR8Ym9vbGVhbn1cblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF8kd2F0Y2hBY3RpdmUobmV3VmFsLCBvbGRWYWwpIHtcblx0XHRcdFx0aWYgKG5ld1ZhbCkge1xuXHRcdFx0XHRcdF9vcGVuKCk7XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0X2Nsb3NlKCk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBPcGVuIGxvYWRpbmdcblx0XHRcdCAqIERpc2FibGUgc2Nyb2xsXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX29wZW4oKSB7XG5cdFx0XHRcdF8kYm9keS5jc3Moe1xuXHRcdFx0XHRcdGhlaWdodDogX3dpbkhlaWdodCxcblx0XHRcdFx0XHRvdmVyZmxvd1k6ICdoaWRkZW4nXG5cdFx0XHRcdH0pO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIENsb3NlIGxvYWRpbmdcblx0XHRcdCAqIEVuYWJsZSBzY3JvbGxcblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfY2xvc2UoKSB7XG5cdFx0XHRcdF8kYm9keS5jc3Moe1xuXHRcdFx0XHRcdGhlaWdodDogJ2F1dG8nLFxuXHRcdFx0XHRcdG92ZXJmbG93WTogJ2F1dG8nXG5cdFx0XHRcdH0pO1xuXHRcdFx0fVxuXHRcdH1cblx0fVxuXG5cdGxvYWRpbmdDdHJsLiRpbmplY3QgPSBbJyRzY29wZSddO1xuXHQvKipcblx0ICogbG9hZGluZyBDT05UUk9MTEVSXG5cdCAqIFVwZGF0ZSB0aGUgbG9hZGluZyBzdGF0dXMgYmFzZWRcblx0ICogb24gcm91dGVDaGFuZ2Ugc3RhdGVcblx0ICovXG5cdGZ1bmN0aW9uIGxvYWRpbmdDdHJsKCRzY29wZSkge1xuXHRcdHZhciBsb2FkaW5nID0gdGhpcztcblxuXHRcdF9pbml0KCk7XG5cblx0XHQvKipcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcblx0XHRcdC8vIHR1cm4gb24gbG9hZGluZyBmb3IgaW5pdGlhbCBwYWdlIGxvYWRcblx0XHRcdF9sb2FkaW5nQWN0aXZlKCk7XG5cblx0XHRcdCRzY29wZS4kb24oJ2xvYWRpbmctb24nLCBfbG9hZGluZ0FjdGl2ZSk7XG5cdFx0XHQkc2NvcGUuJG9uKCdsb2FkaW5nLW9mZicsIF9sb2FkaW5nSW5hY3RpdmUpO1xuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIFNldCBsb2FkaW5nIHRvIGFjdGl2ZVxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfbG9hZGluZ0FjdGl2ZSgpIHtcblx0XHRcdGxvYWRpbmcuYWN0aXZlID0gdHJ1ZTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBTZXQgbG9hZGluZyB0byBpbmFjdGl2ZVxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfbG9hZGluZ0luYWN0aXZlKCkge1xuXHRcdFx0bG9hZGluZy5hY3RpdmUgPSBmYWxzZTtcblx0XHR9XG5cdH1cblxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XG5cdCd1c2Ugc3RyaWN0JztcblxuXHRhbmd1bGFyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXG5cdFx0LmZpbHRlcigndHJ1c3RBc0hUTUwnLCB0cnVzdEFzSFRNTCk7XG5cblx0dHJ1c3RBc0hUTUwuJGluamVjdCA9IFsnJHNjZSddO1xuXG5cdGZ1bmN0aW9uIHRydXN0QXNIVE1MKCRzY2UpIHtcblx0XHRyZXR1cm4gZnVuY3Rpb24odGV4dCkge1xuXHRcdFx0cmV0dXJuICRzY2UudHJ1c3RBc0h0bWwodGV4dCk7XG5cdFx0fTtcblx0fVxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdIZWFkZXJDdHJsJywgSGVhZGVyQ3RybCk7XHJcblxyXG5cdEhlYWRlckN0cmwuJGluamVjdCA9IFsnJGxvY2F0aW9uJywgJ0pTT05EYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIEhlYWRlckN0cmwoJGxvY2F0aW9uLCBKU09ORGF0YSkge1xyXG5cdFx0Ly8gY29udHJvbGxlckFzIFZpZXdNb2RlbFxyXG5cdFx0dmFyIGhlYWRlciA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gYmluZGFibGUgbWVtYmVyc1xyXG5cdFx0aGVhZGVyLmluZGV4SXNBY3RpdmUgPSBpbmRleElzQWN0aXZlO1xyXG5cdFx0aGVhZGVyLm5hdklzQWN0aXZlID0gbmF2SXNBY3RpdmU7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIGFjdGl2YXRlIGNvbnRyb2xsZXJcclxuXHRcdFx0X2FjdGl2YXRlKCk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBDb250cm9sbGVyIGFjdGl2YXRlXHJcblx0XHQgKiBHZXQgSlNPTiBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMgeyp9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfYWN0aXZhdGUoKSB7XHJcblx0XHRcdC8vIGdldCB0aGUgZGF0YSBmcm9tIEpTT05cclxuXHRcdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpLnRoZW4oX2dldEpzb25TdWNjZXNzKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFN1Y2Nlc3NmdWwgcHJvbWlzZSBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGRhdGEge2pzb259XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZ2V0SnNvblN1Y2Nlc3MoZGF0YSkge1xyXG5cdFx0XHRoZWFkZXIuanNvbiA9IGRhdGE7XHJcblx0XHRcdHJldHVybiBoZWFkZXIuanNvbjtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFwcGx5IGNsYXNzIHRvIGluZGV4IG5hdiBpZiBhY3RpdmVcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0ge3N0cmluZ30gcGF0aFxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBpbmRleElzQWN0aXZlKHBhdGgpIHtcclxuXHRcdFx0Ly8gcGF0aCBzaG91bGQgYmUgJy8nXHJcblx0XHRcdHJldHVybiAkbG9jYXRpb24ucGF0aCgpID09PSBwYXRoO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQXBwbHkgY2xhc3MgdG8gY3VycmVudGx5IGFjdGl2ZSBuYXYgaXRlbVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSB7c3RyaW5nfSBwYXRoXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIG5hdklzQWN0aXZlKHBhdGgpIHtcclxuXHRcdFx0cmV0dXJuICRsb2NhdGlvbi5wYXRoKCkuc3Vic3RyKDAsIHBhdGgubGVuZ3RoKSA9PT0gcGF0aDtcclxuXHRcdH1cclxuXHR9XHJcblxyXG59KSgpOyIsIihmdW5jdGlvbigpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG5cdGFuZ3VsYXJcblx0XHQubW9kdWxlKCdyZVN0YXJ0Jylcblx0XHQuZGlyZWN0aXZlKCduYXZDb250cm9sJywgbmF2Q29udHJvbCk7XG5cblx0bmF2Q29udHJvbC4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xuXG5cdGZ1bmN0aW9uIG5hdkNvbnRyb2woJHdpbmRvdywgcmVzaXplKSB7XG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxuXHRcdHJldHVybiB7XG5cdFx0XHRyZXN0cmljdDogJ0VBJyxcblx0XHRcdGxpbms6IG5hdkNvbnRyb2xMaW5rXG5cdFx0fTtcblxuXHRcdC8qKlxuXHRcdCAqIG5hdkNvbnRyb2wgTElOSyBmdW5jdGlvblxuXHRcdCAqXG5cdFx0ICogQHBhcmFtICRzY29wZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIG5hdkNvbnRyb2xMaW5rKCRzY29wZSkge1xuXHRcdFx0Ly8gZGF0YSBtb2RlbFxuXHRcdFx0JHNjb3BlLm5hdiA9IHt9O1xuXG5cdFx0XHQvLyBwcml2YXRlIHZhcmlhYmxlc1xuXHRcdFx0dmFyIF8kYm9keSA9IGFuZ3VsYXIuZWxlbWVudCgnYm9keScpO1xuXHRcdFx0dmFyIF9sYXlvdXRDYW52YXMgPSBfJGJvZHkuZmluZCgnLmxheW91dC1jYW52YXMnKTtcblx0XHRcdHZhciBfbmF2T3BlbjtcblxuXHRcdFx0X2luaXQoKTtcblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxuXHRcdFx0ICpcblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xuXHRcdFx0XHQvLyBpbml0aWFsaXplIGRlYm91bmNlZCByZXNpemVcblx0XHRcdFx0dmFyIF9ycyA9IHJlc2l6ZS5pbml0KHtcblx0XHRcdFx0XHRzY29wZTogJHNjb3BlLFxuXHRcdFx0XHRcdHJlc2l6ZWRGbjogX3Jlc2l6ZWQsXG5cdFx0XHRcdFx0ZGVib3VuY2U6IDEwMFxuXHRcdFx0XHR9KTtcblxuXHRcdFx0XHQkc2NvcGUuJG9uKCckbG9jYXRpb25DaGFuZ2VTdGFydCcsIF8kbG9jYXRpb25DaGFuZ2VTdGFydCk7XG5cdFx0XHRcdCRzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIF9lbnRlck1vYmlsZSk7XG5cdFx0XHRcdCRzY29wZS4kb24oJ2V4aXQtbW9iaWxlJywgX2V4aXRNb2JpbGUpO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIFJlc2l6ZWQgd2luZG93IChkZWJvdW5jZWQpXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX3Jlc2l6ZWQoKSB7XG5cdFx0XHRcdF9sYXlvdXRDYW52YXMuY3NzKHtcblx0XHRcdFx0XHRtaW5IZWlnaHQ6ICR3aW5kb3cuaW5uZXJIZWlnaHQgKyAncHgnXG5cdFx0XHRcdH0pO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIE9wZW4gbW9iaWxlIG5hdmlnYXRpb25cblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfb3Blbk5hdigpIHtcblx0XHRcdFx0XyRib2R5XG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduYXYtY2xvc2VkJylcblx0XHRcdFx0XHQuYWRkQ2xhc3MoJ25hdi1vcGVuJyk7XG5cblx0XHRcdFx0X25hdk9wZW4gPSB0cnVlO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIENsb3NlIG1vYmlsZSBuYXZpZ2F0aW9uXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX2Nsb3NlTmF2KCkge1xuXHRcdFx0XHRfJGJvZHlcblx0XHRcdFx0XHQucmVtb3ZlQ2xhc3MoJ25hdi1vcGVuJylcblx0XHRcdFx0XHQuYWRkQ2xhc3MoJ25hdi1jbG9zZWQnKTtcblxuXHRcdFx0XHRfbmF2T3BlbiA9IGZhbHNlO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIFRvZ2dsZSBuYXYgb3Blbi9jbG9zZWRcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gdG9nZ2xlTmF2KCkge1xuXHRcdFx0XHRpZiAoIV9uYXZPcGVuKSB7XG5cdFx0XHRcdFx0X29wZW5OYXYoKTtcblx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRfY2xvc2VOYXYoKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIFdoZW4gY2hhbmdpbmcgbG9jYXRpb24sIGNsb3NlIHRoZSBuYXYgaWYgaXQncyBvcGVuXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF8kbG9jYXRpb25DaGFuZ2VTdGFydCgpIHtcblx0XHRcdFx0aWYgKF9uYXZPcGVuKSB7XG5cdFx0XHRcdFx0X2Nsb3NlTmF2KCk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBGdW5jdGlvbiB0byBleGVjdXRlIHdoZW4gZW50ZXJpbmcgbW9iaWxlIG1lZGlhIHF1ZXJ5XG5cdFx0XHQgKiBDbG9zZSBuYXYgYW5kIHNldCB1cCBtZW51IHRvZ2dsaW5nIGZ1bmN0aW9uYWxpdHlcblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfZW50ZXJNb2JpbGUobXEpIHtcblx0XHRcdFx0X2Nsb3NlTmF2KCk7XG5cblx0XHRcdFx0Ly8gYmluZCBmdW5jdGlvbiB0byB0b2dnbGUgbW9iaWxlIG5hdmlnYXRpb24gb3Blbi9jbG9zZWRcblx0XHRcdFx0JHNjb3BlLm5hdi50b2dnbGVOYXYgPSB0b2dnbGVOYXY7XG5cdFx0XHR9XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogRnVuY3Rpb24gdG8gZXhlY3V0ZSB3aGVuIGV4aXRpbmcgbW9iaWxlIG1lZGlhIHF1ZXJ5XG5cdFx0XHQgKiBEaXNhYmxlIG1lbnUgdG9nZ2xpbmcgYW5kIHJlbW92ZSBib2R5IGNsYXNzZXNcblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZShtcSkge1xuXHRcdFx0XHQvLyB1bmJpbmQgZnVuY3Rpb24gdG8gdG9nZ2xlIG1vYmlsZSBuYXZpZ2F0aW9uIG9wZW4vY2xvc2VkXG5cdFx0XHRcdCRzY29wZS5uYXYudG9nZ2xlTmF2ID0gbnVsbDtcblxuXHRcdFx0XHRfJGJvZHkucmVtb3ZlQ2xhc3MoJ25hdi1jbG9zZWQgbmF2LW9wZW4nKTtcblx0XHRcdH1cblx0XHR9XG5cdH1cblxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XG5cdCd1c2Ugc3RyaWN0JztcblxuXHRhbmd1bGFyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXG5cdFx0LmNvbnRyb2xsZXIoJ0Vycm9yNDA0Q3RybCcsIEVycm9yNDA0Q3RybCk7XG5cblx0RXJyb3I0MDRDdHJsLiRpbmplY3QgPSBbJyRzY29wZScsICdQYWdlJ107XG5cblx0ZnVuY3Rpb24gRXJyb3I0MDRDdHJsKCRzY29wZSwgUGFnZSkge1xuXHRcdHZhciBlNDA0ID0gdGhpcztcblxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcblx0XHRlNDA0LnRpdGxlID0gJzQwNCAtIFBhZ2UgTm90IEZvdW5kJztcblxuXHRcdF9pbml0KCk7XG5cblx0XHQvKipcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cblx0XHRcdFBhZ2Uuc2V0VGl0bGUoZTQwNC50aXRsZSk7XG5cblx0XHRcdCRzY29wZS4kZW1pdCgnbG9hZGluZy1vZmYnKTtcblx0XHR9XG5cdH1cbn0pKCk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignSG9tZUN0cmwnLCBIb21lQ3RybCk7XHJcblxyXG5cdEhvbWVDdHJsLiRpbmplY3QgPSBbJyRzY29wZScsICdVdGlscycsICdQYWdlJywgJ0pTT05EYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIEhvbWVDdHJsKCRzY29wZSwgVXRpbHMsIFBhZ2UsIEpTT05EYXRhKSB7XHJcblx0XHQvLyBjb250cm9sbGVyQXMgVmlld01vZGVsXHJcblx0XHR2YXIgaG9tZSA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gYmluZGFibGUgbWVtYmVyc1xyXG5cdFx0aG9tZS50aXRsZSA9ICdIb21lJztcclxuXHRcdGhvbWUuZ2xvYmFsID0gVXRpbHM7XHJcblx0XHRob21lLm5hbWUgPSAnVmlzaXRvcic7XHJcblx0XHRob21lLmFsZXJ0R3JlZXRpbmcgPSBVdGlscy5hbGVydEdyZWV0aW5nO1xyXG5cdFx0aG9tZS5zdHJpbmdPZkhUTUwgPSAnPHN0cm9uZyBzdHlsZT1cImNvbG9yOiBncmVlbjtcIj5Tb21lIGdyZWVuIHRleHQ8L3N0cm9uZz4gYm91bmQgYXMgSFRNTCB3aXRoIGEgPGEgaHJlZj1cIiNcIj5saW5rPC9hPiwgdHJ1c3RlZCB3aXRoIFNDRSEnO1xyXG5cdFx0aG9tZS52aWV3Zm9ybWF0ID0gbnVsbDtcclxuXHJcblx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0Ly8gc2V0IHBhZ2UgPHRpdGxlPlxyXG5cdFx0XHRQYWdlLnNldFRpdGxlKGhvbWUudGl0bGUpO1xyXG5cclxuXHRcdFx0Ly8gYWN0aXZhdGUgY29udHJvbGxlclxyXG5cdFx0XHRfYWN0aXZhdGUoKTtcclxuXHJcblx0XHRcdC8vIG1lZGlhcXVlcnkgZXZlbnRzXHJcblx0XHRcdCRzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIF9lbnRlck1vYmlsZSk7XHJcblx0XHRcdCRzY29wZS4kb24oJ2V4aXQtbW9iaWxlJywgX2V4aXRNb2JpbGUpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQ29udHJvbGxlciBhY3RpdmF0ZVxyXG5cdFx0ICogR2V0IEpTT04gZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHsqfVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2FjdGl2YXRlKCkge1xyXG5cdFx0XHQvLyBzdGFydCBsb2FkaW5nXHJcblx0XHRcdCRzY29wZS4kZW1pdCgnbG9hZGluZy1vbicpO1xyXG5cclxuXHRcdFx0Ly8gZ2V0IHRoZSBkYXRhIGZyb20gSlNPTlxyXG5cdFx0XHRyZXR1cm4gSlNPTkRhdGEuZ2V0TG9jYWxEYXRhKCkudGhlbihfZ2V0SnNvblN1Y2Nlc3MpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogU3VjY2Vzc2Z1bCBwcm9taXNlIGRhdGFcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gZGF0YSB7anNvbn1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9nZXRKc29uU3VjY2VzcyhkYXRhKSB7XHJcblx0XHRcdGhvbWUuanNvbiA9IGRhdGE7XHJcblxyXG5cdFx0XHQvLyBzdG9wIGxvYWRpbmdcclxuXHRcdFx0JHNjb3BlLiRlbWl0KCdsb2FkaW5nLW9mZicpO1xyXG5cclxuXHRcdFx0cmV0dXJuIGhvbWUuanNvbjtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEVudGVyIHNtYWxsIG1xXHJcblx0XHQgKiBTZXQgaG9tZS52aWV3Zm9ybWF0XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2VudGVyTW9iaWxlKCkge1xyXG5cdFx0XHRob21lLnZpZXdmb3JtYXQgPSAnc21hbGwnO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogRXhpdCBzbWFsbCBtcVxyXG5cdFx0ICogU2V0IGhvbWUudmlld2Zvcm1hdFxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9leGl0TW9iaWxlKCkge1xyXG5cdFx0XHRob21lLnZpZXdmb3JtYXQgPSAnbGFyZ2UnO1xyXG5cdFx0fVxyXG5cdH1cclxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdTdWJDdHJsJywgU3ViQ3RybCk7XHJcblxyXG5cdFN1YkN0cmwuJGluamVjdCA9IFsnVXRpbHMnLCAnUGFnZScsICdyZXNvbHZlTG9jYWxEYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIFN1YkN0cmwoVXRpbHMsIFBhZ2UsIHJlc29sdmVMb2NhbERhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBzdWIgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdHN1Yi50aXRsZSA9ICdTdWJwYWdlJztcclxuXHRcdHN1Yi5nbG9iYWwgPSBVdGlscztcclxuXHRcdHN1Yi5qc29uID0gcmVzb2x2ZUxvY2FsRGF0YTtcclxuXHJcblx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0Ly8gc2V0IHBhZ2UgPHRpdGxlPlxyXG5cdFx0XHRQYWdlLnNldFRpdGxlKHN1Yi50aXRsZSk7XHJcblx0XHR9XHJcblx0fVxyXG59KSgpOyIsIi8qKlxyXG4gKiBEaXJlY3RpdmVzIChhbmQgYXNzb2NpYXRlZCBhdHRyaWJ1dGVzKSBhcmUgYWx3YXlzIGRlY2xhcmVkIGFzIGNhbWVsQ2FzZSBpbiBKUyBhbmQgc25ha2UtY2FzZSBpbiBIVE1MXHJcbiAqIEFuZ3VsYXIncyBidWlsdC1pbiA8YT4gZGlyZWN0aXZlIGF1dG9tYXRpY2FsbHkgaW1wbGVtZW50cyBwcmV2ZW50RGVmYXVsdCBvbiBsaW5rcyB0aGF0IGRvbid0IGhhdmUgYW4gaHJlZiBhdHRyaWJ1dGVcclxuICogQ29tcGxleCBKYXZhU2NyaXB0IERPTSBtYW5pcHVsYXRpb24gc2hvdWxkIGFsd2F5cyBiZSBkb25lIGluIGRpcmVjdGl2ZSBsaW5rIGZ1bmN0aW9ucywgYW5kICRhcHBseSBzaG91bGQgbmV2ZXIgYmUgdXNlZCBpbiBhIGNvbnRyb2xsZXIhIFNpbXBsZSBET00gbWFuaXB1bGF0aW9uIHNob3VsZCBiZSBpbiB0aGUgdmlldy5cclxuICovXHJcblxyXG4vKi0tLSBTYW1wbGUgRGlyZWN0aXZlIHdpdGggYSAkd2F0Y2ggLS0tKi9cclxuKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuZGlyZWN0aXZlKCdzYW1wbGVEaXJlY3RpdmUnLCBzYW1wbGVEaXJlY3RpdmUpO1xyXG5cclxuXHRzYW1wbGVEaXJlY3RpdmUuJGluamVjdCA9IFsnJHRpbWVvdXQnXTtcclxuXHJcblx0ZnVuY3Rpb24gc2FtcGxlRGlyZWN0aXZlKCR0aW1lb3V0KSB7XHJcblx0XHQvLyByZXR1cm4gZGlyZWN0aXZlXHJcblx0XHRyZXR1cm4ge1xyXG5cdFx0XHRyZXN0cmljdDogJ0VBJyxcclxuXHRcdFx0cmVwbGFjZTogdHJ1ZSxcclxuXHRcdFx0c2NvcGU6IHt9LFxyXG5cdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL3N1Yi9zYW1wbGUudHBsLmh0bWwnLFxyXG5cdFx0XHR0cmFuc2NsdWRlOiB0cnVlLFxyXG5cdFx0XHRjb250cm9sbGVyOiBTYW1wbGVEaXJlY3RpdmVDdHJsLFxyXG5cdFx0XHRjb250cm9sbGVyQXM6ICdzZCcsXHJcblx0XHRcdGJpbmRUb0NvbnRyb2xsZXI6IHtcclxuXHRcdFx0XHRqc29uRGF0YTogJz0nXHJcblx0XHRcdH0sXHJcblx0XHRcdGxpbms6IHNhbXBsZURpcmVjdGl2ZUxpbmtcclxuXHRcdH07XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBzYW1wbGVEaXJlY3RpdmUgTElOSyBmdW5jdGlvblxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkc2NvcGVcclxuXHRcdCAqIEBwYXJhbSAkZWxlbWVudFxyXG5cdFx0ICogQHBhcmFtICRhdHRyc1xyXG5cdFx0ICogQHBhcmFtIHNkIHtjb250cm9sbGVyfVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBzYW1wbGVEaXJlY3RpdmVMaW5rKCRzY29wZSwgJGVsZW1lbnQsICRhdHRycywgc2QpIHtcclxuXHRcdFx0X2luaXQoKTtcclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdFx0Ly8gd2F0Y2ggZm9yIGFzeW5jIGRhdGEgdG8gYmVjb21lIGF2YWlsYWJsZSBhbmQgdXBkYXRlXHJcblx0XHRcdFx0JHNjb3BlLiR3YXRjaCgnc2QuanNvbkRhdGEnLCBfJHdhdGNoSnNvbkRhdGEpO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogJHdhdGNoIGZvciBzZC5qc29uRGF0YSB0byBiZWNvbWUgYXZhaWxhYmxlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwYXJhbSBuZXdWYWwgeyp9XHJcblx0XHRcdCAqIEBwYXJhbSBvbGRWYWwgeyp9XHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfJHdhdGNoSnNvbkRhdGEobmV3VmFsLCBvbGRWYWwpIHtcclxuXHRcdFx0XHRpZiAobmV3VmFsKSB7XHJcblx0XHRcdFx0XHRzZC5qc29uRGF0YSA9IG5ld1ZhbDtcclxuXHJcblx0XHRcdFx0XHQkdGltZW91dChmdW5jdGlvbigpIHtcclxuXHRcdFx0XHRcdFx0Y29uc29sZS5sb2coJ2RlbW9uc3RyYXRlICR0aW1lb3V0IGluamVjdGlvbiBpbiBhIGRpcmVjdGl2ZSBsaW5rIGZ1bmN0aW9uJyk7XHJcblx0XHRcdFx0XHR9LCAxMDAwKTtcclxuXHRcdFx0XHR9XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHR9XHJcblxyXG5cdFNhbXBsZURpcmVjdGl2ZUN0cmwuJGluamVjdCA9IFtdO1xyXG5cdC8qKlxyXG5cdCAqIHNhbXBsZURpcmVjdGl2ZSBDT05UUk9MTEVSXHJcblx0ICovXHJcblx0ZnVuY3Rpb24gU2FtcGxlRGlyZWN0aXZlQ3RybCgpIHtcclxuXHRcdHZhciBzZCA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gY29udHJvbGxlciBsb2dpYyBnb2VzIGhlcmVcclxuXHR9XHJcblxyXG59KSgpOyJdLCJzb3VyY2VSb290IjoiL3NvdXJjZS8ifQ== \ No newline at end of file +(function() { + 'use strict'; + + angular + .module('reStart') + .controller('SubCtrl', SubCtrl); + + SubCtrl.$inject = ['Utils', 'Page', 'resolveLocalData']; + + function SubCtrl(Utils, Page, resolveLocalData) { + // controllerAs ViewModel + var sub = this; + + // bindable members + sub.title = 'Subpage'; + sub.global = Utils; + sub.json = resolveLocalData; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // set page <title> + Page.setTitle(sub.title); + } + } +})(); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5tb2R1bGUuanMiLCJjb3JlL1BhZ2UuY3RybC5qcyIsImNvcmUvUGFnZS5mYWN0b3J5LmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LmpzIiwiY29yZS9hcHAtc2V0dXAvYXBwLmNvbmZpZy5qcyIsImNvcmUvZ2V0LWRhdGEvSlNPTkRhdGEuZmFjdG9yeS5qcyIsImNvcmUvZ2V0LWRhdGEvUmVzLmZhY3RvcnkuanMiLCJjb3JlL3VpL2xvYWRpbmcuZGlyLmpzIiwiY29yZS91aS9NUS5jb25zdGFudC5qcyIsImNvcmUvdWkvdHJ1c3RBc0hUTUwuZmlsdGVyLmpzIiwibW9kdWxlcy9oZWFkZXIvSGVhZGVyLmN0cmwuanMiLCJtb2R1bGVzL2hlYWRlci9uYXZDb250cm9sLmRpci5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuanMiLCJwYWdlcy9ob21lL0hvbWUuY3RybC5qcyIsInBhZ2VzL3N1Yi9zYW1wbGUuZGlyLmpzIiwicGFnZXMvc3ViL1N1Yi5jdHJsLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDTkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNuSkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3JDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQy9KQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDeEVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUN4SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2xDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3RHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDbEZBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InJlU3RhcnQtYXBwLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gYXBwbGljYXRpb24gbW9kdWxlIHNldHRlclxyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JywgWyduZ1JvdXRlJywgJ25nUmVzb3VyY2UnLCAnbmdTYW5pdGl6ZScsICdtZWRpYUNoZWNrJywgJ3Jlc2l6ZSddKTtcclxufSkoKTsiLCIoZnVuY3Rpb24gKCkge1xyXG4gICAgJ3VzZSBzdHJpY3QnO1xyXG5cclxuICAgIGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnRyb2xsZXIoJ1BhZ2VDdHJsJywgUGFnZUN0cmwpO1xyXG5cclxuICAgIFBhZ2VDdHJsLiRpbmplY3QgPSBbJ1BhZ2UnLCAnJHNjb3BlJywgJ01RJywgJ21lZGlhQ2hlY2snXTtcclxuXHJcbiAgICBmdW5jdGlvbiBQYWdlQ3RybChQYWdlLCAkc2NvcGUsIE1RLCBtZWRpYUNoZWNrKSB7XHJcbiAgICAgICAgdmFyIHBhZ2UgPSB0aGlzO1xyXG5cclxuICAgICAgICAvLyBwcml2YXRlIHZhcmlhYmxlc1xyXG4gICAgICAgIHZhciBfaGFuZGxpbmdSb3V0ZUNoYW5nZUVycm9yID0gZmFsc2U7XHJcbiAgICAgICAgLy8gU2V0IHVwIGZ1bmN0aW9uYWxpdHkgdG8gcnVuIG9uIGVudGVyL2V4aXQgb2YgbWVkaWEgcXVlcnlcclxuICAgICAgICB2YXIgbWMgPSBtZWRpYUNoZWNrLmluaXQoe1xyXG4gICAgICAgICAgICBzY29wZTogJHNjb3BlLFxyXG4gICAgICAgICAgICBtZWRpYToge1xyXG4gICAgICAgICAgICAgICAgbXE6IE1RLlNNQUxMLFxyXG4gICAgICAgICAgICAgICAgZW50ZXI6IF9lbnRlck1vYmlsZSxcclxuICAgICAgICAgICAgICAgIGV4aXQ6IF9leGl0TW9iaWxlXHJcbiAgICAgICAgICAgIH0sXHJcbiAgICAgICAgICAgIGRlYm91bmNlOiAyMDBcclxuICAgICAgICB9KTtcclxuXHJcbiAgICAgICAgX2luaXQoKTtcclxuXHJcbiAgICAgICAgLyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuICAgICAgICBmdW5jdGlvbiBfaW5pdCgpIHtcclxuICAgICAgICAgICAgLy8gYXNzb2NpYXRlIHBhZ2UgPHRpdGxlPlxyXG4gICAgICAgICAgICBwYWdlLnBhZ2VUaXRsZSA9IFBhZ2U7XHJcblxyXG4gICAgICAgICAgICAkc2NvcGUuJG9uKCckcm91dGVDaGFuZ2VTdGFydCcsIF9yb3V0ZUNoYW5nZVN0YXJ0KTtcclxuICAgICAgICAgICAgJHNjb3BlLiRvbignJHJvdXRlQ2hhbmdlU3VjY2VzcycsIF9yb3V0ZUNoYW5nZVN1Y2Nlc3MpO1xyXG4gICAgICAgICAgICAkc2NvcGUuJG9uKCckcm91dGVDaGFuZ2VFcnJvcicsIF9yb3V0ZUNoYW5nZUVycm9yKTtcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIC8qKlxyXG5cdFx0ICogRW50ZXIgbW9iaWxlIG1lZGlhIHF1ZXJ5XHJcblx0XHQgKiAkYnJvYWRjYXN0ICdlbnRlci1tb2JpbGUnIGV2ZW50XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG4gICAgICAgIGZ1bmN0aW9uIF9lbnRlck1vYmlsZSgpIHtcclxuICAgICAgICAgICAgJHNjb3BlLiRicm9hZGNhc3QoJ2VudGVyLW1vYmlsZScpO1xyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgLyoqXHJcblx0XHQgKiBFeGl0IG1vYmlsZSBtZWRpYSBxdWVyeVxyXG5cdFx0ICogJGJyb2FkY2FzdCAnZXhpdC1tb2JpbGUnIGV2ZW50XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG4gICAgICAgIGZ1bmN0aW9uIF9leGl0TW9iaWxlKCkge1xyXG4gICAgICAgICAgICAkc2NvcGUuJGJyb2FkY2FzdCgnZXhpdC1tb2JpbGUnKTtcclxuICAgICAgICB9XHJcblxyXG4gICAgICAgIC8qKlxyXG5cdFx0ICogVHVybiBvbiBsb2FkaW5nIHN0YXRlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG4gICAgICAgIGZ1bmN0aW9uIF9sb2FkaW5nT24oKSB7XHJcbiAgICAgICAgICAgICRzY29wZS4kYnJvYWRjYXN0KCdsb2FkaW5nLW9uJyk7XHJcbiAgICAgICAgfVxyXG5cclxuICAgICAgICAvKipcclxuXHRcdCAqIFR1cm4gb2ZmIGxvYWRpbmcgc3RhdGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcbiAgICAgICAgZnVuY3Rpb24gX2xvYWRpbmdPZmYoKSB7XHJcbiAgICAgICAgICAgICRzY29wZS4kYnJvYWRjYXN0KCdsb2FkaW5nLW9mZicpO1xyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgLyoqXHJcblx0XHQgKiBSb3V0ZSBjaGFuZ2Ugc3RhcnQgaGFuZGxlclxyXG5cdFx0ICogSWYgbmV4dCByb3V0ZSBoYXMgcmVzb2x2ZSwgdHVybiBvbiBsb2FkaW5nXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRldmVudCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIG5leHQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcbiAgICAgICAgZnVuY3Rpb24gX3JvdXRlQ2hhbmdlU3RhcnQoJGV2ZW50LCBuZXh0LCBjdXJyZW50KSB7XHJcbiAgICAgICAgICAgIGlmIChuZXh0LiQkcm91dGUgJiYgbmV4dC4kJHJvdXRlLnJlc29sdmUpIHtcclxuICAgICAgICAgICAgICAgIF9sb2FkaW5nT24oKTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgLyoqXHJcblx0XHQgKiBSb3V0ZSBjaGFuZ2Ugc3VjY2VzcyBoYW5kbGVyXHJcblx0XHQgKiBNYXRjaCBjdXJyZW50IG1lZGlhIHF1ZXJ5IGFuZCBydW4gYXBwcm9wcmlhdGUgZnVuY3Rpb25cclxuXHRcdCAqIElmIGN1cnJlbnQgcm91dGUgaGFzIGJlZW4gcmVzb2x2ZWQsIHR1cm4gb2ZmIGxvYWRpbmdcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJGV2ZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gY3VycmVudCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIHByZXZpb3VzIHtvYmplY3R9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcbiAgICAgICAgZnVuY3Rpb24gX3JvdXRlQ2hhbmdlU3VjY2VzcygkZXZlbnQsIGN1cnJlbnQsIHByZXZpb3VzKSB7XHJcbiAgICAgICAgICAgIG1jLm1hdGNoQ3VycmVudChNUS5TTUFMTCk7XHJcblxyXG4gICAgICAgICAgICBpZiAoY3VycmVudC4kJHJvdXRlICYmIGN1cnJlbnQuJCRyb3V0ZS5yZXNvbHZlKSB7XHJcbiAgICAgICAgICAgICAgICBfbG9hZGluZ09mZigpO1xyXG4gICAgICAgICAgICB9XHJcbiAgICAgICAgfVxyXG5cclxuICAgICAgICAvKipcclxuXHRcdCAqIFJvdXRlIGNoYW5nZSBlcnJvciBoYW5kbGVyXHJcblx0XHQgKiBIYW5kbGUgcm91dGUgcmVzb2x2ZSBmYWlsdXJlc1xyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gcHJldmlvdXMge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSByZWplY3Rpb24ge29iamVjdH1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuICAgICAgICBmdW5jdGlvbiBfcm91dGVDaGFuZ2VFcnJvcigkZXZlbnQsIGN1cnJlbnQsIHByZXZpb3VzLCByZWplY3Rpb24pIHtcclxuICAgICAgICAgICAgaWYgKF9oYW5kbGluZ1JvdXRlQ2hhbmdlRXJyb3IpIHtcclxuICAgICAgICAgICAgICAgIHJldHVybjtcclxuICAgICAgICAgICAgfVxyXG5cclxuICAgICAgICAgICAgX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvciA9IHRydWU7XHJcbiAgICAgICAgICAgIF9sb2FkaW5nT2ZmKCk7XHJcblxyXG4gICAgICAgICAgICB2YXIgZGVzdGluYXRpb24gPSAoY3VycmVudCAmJiAoY3VycmVudC50aXRsZSB8fCBjdXJyZW50Lm5hbWUgfHwgY3VycmVudC5sb2FkZWRUZW1wbGF0ZVVybCkpIHx8ICd1bmtub3duIHRhcmdldCc7XHJcbiAgICAgICAgICAgIHZhciBtc2cgPSAnRXJyb3Igcm91dGluZyB0byAnICsgZGVzdGluYXRpb24gKyAnLiAnICsgKHJlamVjdGlvbi5tc2cgfHwgJycpO1xyXG5cclxuICAgICAgICAgICAgY29uc29sZS5sb2cobXNnKTtcclxuXHJcbiAgICAgICAgICAgIC8qKlxyXG5cdFx0XHQgKiBPbiByb3V0aW5nIGVycm9yLCBzaG93IGFuIGVycm9yLlxyXG5cdFx0XHQgKi9cclxuICAgICAgICAgICAgYWxlcnQoJ0FuIGVycm9yIG9jY3VycmVkLiBQbGVhc2UgdHJ5IGFnYWluLicpO1xyXG4gICAgICAgIH1cclxuXHJcbiAgICAgICAgUGFnZUN0cmwuZW50ZXJNb2JpbGUgPSBfZW50ZXJNb2JpbGU7Ly90ZXN0IGNvZGVcclxuICAgICAgICBQYWdlQ3RybC5leGl0TW9iaWxlID0gX2V4aXRNb2JpbGU7Ly90ZXN0IGNvZGVcclxuICAgICAgICBQYWdlQ3RybC5sb2FkaW5nT24gPSBfbG9hZGluZ09uOy8vdGVzdCBjb2RlXHJcbiAgICAgICAgUGFnZUN0cmwubG9hZGluZ09mZiA9IF9sb2FkaW5nT2ZmOy8vdGVzdCBjb2RlXHJcbiAgICAgICAgcmV0dXJuIFBhZ2VDdHJsOy8vdGVzdCBjb2RlXHJcbiAgICB9XHJcbn0pKCk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuZmFjdG9yeSgnUGFnZScsIFBhZ2UpO1xyXG5cclxuXHRmdW5jdGlvbiBQYWdlKCkge1xyXG5cdFx0Ly8gcHJpdmF0ZSB2YXJzXHJcblx0XHR2YXIgc2l0ZVRpdGxlID0gJ3JlU3RhcnQgQW5ndWxhcic7XHJcblx0XHR2YXIgcGFnZVRpdGxlID0gJ0hvbWUnO1xyXG5cclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdGdldFRpdGxlOiBnZXRUaXRsZSxcclxuXHRcdFx0c2V0VGl0bGU6IHNldFRpdGxlXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogVGl0bGUgZnVuY3Rpb25cclxuXHRcdCAqIFNldHMgc2l0ZSB0aXRsZSBhbmQgcGFnZSB0aXRsZVxyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHtzdHJpbmd9IHNpdGUgdGl0bGUgKyBwYWdlIHRpdGxlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGdldFRpdGxlKCkge1xyXG5cdFx0XHRyZXR1cm4gc2l0ZVRpdGxlICsgJyB8ICcgKyBwYWdlVGl0bGU7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTZXQgcGFnZSB0aXRsZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSBuZXdUaXRsZSB7c3RyaW5nfVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBzZXRUaXRsZShuZXdUaXRsZSkge1xyXG5cdFx0XHRwYWdlVGl0bGUgPSBuZXdUaXRsZTtcclxuXHRcdH1cclxuXHR9XHJcbn0pKCk7IiwiLy8gXCJnbG9iYWxcIiBvYmplY3QgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5mYWN0b3J5KCdVdGlscycsIFV0aWxzKTtcclxuXHJcblx0ZnVuY3Rpb24gVXRpbHMoKSB7XHJcblx0XHR2YXIgZ3JlZXRpbmcgPSAnSGVsbG8nO1xyXG5cclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdGdyZWV0aW5nOiBncmVldGluZyxcclxuXHRcdFx0YWxlcnRHcmVldGluZzogYWxlcnRHcmVldGluZ1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFsZXJ0IGdyZWV0aW5nXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIG5hbWUge3N0cmluZ31cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gYWxlcnRHcmVldGluZyhuYW1lKSB7XHJcblx0XHRcdGFsZXJ0KGdyZWV0aW5nICsgJywgJyArIG5hbWUgKyAnIScpO1xyXG5cdFx0fVxyXG5cdH1cclxufSkoKTsiLCIvLyBhcHBsaWNhdGlvbiBjb25maWdcclxuKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29uZmlnKGFwcENvbmZpZyk7XHJcblxyXG5cdGFwcENvbmZpZy4kaW5qZWN0ID0gWyckcm91dGVQcm92aWRlcicsICckbG9jYXRpb25Qcm92aWRlciddO1xyXG5cclxuXHRmdW5jdGlvbiBhcHBDb25maWcoJHJvdXRlUHJvdmlkZXIsICRsb2NhdGlvblByb3ZpZGVyKSB7XHJcblx0XHQkcm91dGVQcm92aWRlclxyXG5cdFx0XHQud2hlbignLycsIHtcclxuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL2hvbWUvSG9tZS52aWV3Lmh0bWwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdIb21lQ3RybCcsXHJcblx0XHRcdFx0Y29udHJvbGxlckFzOiAnaG9tZSdcclxuXHRcdFx0fSlcclxuXHRcdFx0LndoZW4oJy9zdWJwYWdlJywge1xyXG5cdFx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvcGFnZXMvc3ViL1N1Yi52aWV3Lmh0bWwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdTdWJDdHJsJyxcclxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdzdWInLFxyXG5cdFx0XHRcdHJlc29sdmU6IHtcclxuXHRcdFx0XHRcdHJlc29sdmVMb2NhbERhdGE6IHJlc29sdmVMb2NhbERhdGFcclxuXHRcdFx0XHR9XHJcblx0XHRcdH0pXHJcblx0XHRcdC5vdGhlcndpc2Uoe1xyXG5cdFx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvcGFnZXMvZXJyb3I0MDQvRXJyb3I0MDQudmlldy5odG1sJyxcclxuXHRcdFx0XHRjb250cm9sbGVyOiAnRXJyb3I0MDRDdHJsJyxcclxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdlNDA0J1xyXG5cdFx0XHR9KTtcclxuXHJcblx0XHQkbG9jYXRpb25Qcm92aWRlclxyXG5cdFx0XHQuaHRtbDVNb2RlKHtcclxuXHRcdFx0XHRlbmFibGVkOiB0cnVlXHJcblx0XHRcdH0pXHJcblx0XHRcdC5oYXNoUHJlZml4KCchJyk7XHJcblx0fVxyXG5cclxuXHRyZXNvbHZlTG9jYWxEYXRhLiRpbmplY3QgPSBbJ0pTT05EYXRhJ107XHJcblx0LyoqXHJcblx0ICogR2V0IGxvY2FsIGRhdGEgZm9yIHJvdXRlIHJlc29sdmVcclxuXHQgKlxyXG5cdCAqIEBwYXJhbSBKU09ORGF0YSB7ZmFjdG9yeX1cclxuXHQgKiBAcmV0dXJucyB7cHJvbWlzZX0gZGF0YVxyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIHJlc29sdmVMb2NhbERhdGEoSlNPTkRhdGEpIHtcclxuXHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKTtcclxuXHR9XHJcbn0pKCk7IiwiLy8gZmV0Y2ggSlNPTiBkYXRhIHRvIHNoYXJlIGJldHdlZW4gY29udHJvbGxlcnNcclxuKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuZmFjdG9yeSgnSlNPTkRhdGEnLCBKU09ORGF0YSk7XHJcblxyXG5cdEpTT05EYXRhLiRpbmplY3QgPSBbJyRodHRwJywgJ1JlcyddO1xyXG5cclxuXHRmdW5jdGlvbiBKU09ORGF0YSgkaHR0cCwgUmVzKSB7XHJcblx0XHQvLyBjYWxsYWJsZSBtZW1iZXJzXHJcblx0XHRyZXR1cm4ge1xyXG5cdFx0XHRnZXRMb2NhbERhdGE6IGdldExvY2FsRGF0YVxyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEdFVCBsb2NhbCBKU09OIGRhdGEgZmlsZSBhbmQgcmV0dXJuIHJlc3VsdHNcclxuXHRcdCAqXHJcblx0XHQgKiBAcmV0dXJucyB7cHJvbWlzZX1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gZ2V0TG9jYWxEYXRhKCkge1xyXG5cdFx0XHRyZXR1cm4gJGh0dHBcclxuXHRcdFx0XHQuZ2V0KCcvZGF0YS9kYXRhLmpzb24nKVxyXG5cdFx0XHRcdC50aGVuKFJlcy5zdWNjZXNzLCBSZXMuZXJyb3IpO1xyXG5cdFx0fVxyXG5cdH1cclxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5mYWN0b3J5KCdSZXMnLCBSZXMpO1xyXG5cclxuXHRmdW5jdGlvbiBSZXMoKSB7XHJcblx0XHQvLyBjYWxsYWJsZSBtZW1iZXJzXHJcblx0XHRyZXR1cm4ge1xyXG5cdFx0XHRzdWNjZXNzOiBzdWNjZXNzLFxyXG5cdFx0XHRlcnJvcjogZXJyb3JcclxuXHRcdH07XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBQcm9taXNlIHJlc3BvbnNlIGZ1bmN0aW9uXHJcblx0XHQgKiBDaGVja3MgdHlwZW9mIGRhdGEgcmV0dXJuZWQgYW5kIHN1Y2NlZWRzIGlmIEpTIG9iamVjdCwgdGhyb3dzIGVycm9yIGlmIG5vdFxyXG5cdFx0ICogVXNlZnVsIGZvciBBUElzIChpZSwgd2l0aCBuZ2lueCkgd2hlcmUgc2VydmVyIGVycm9yIEhUTUwgcGFnZSBtYXkgYmUgcmV0dXJuZWQgaW4gZXJyb3JcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gcmVzcG9uc2Ugeyp9IGRhdGEgZnJvbSAkaHR0cFxyXG5cdFx0ICogQHJldHVybnMgeyp9IG9iamVjdCwgYXJyYXlcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gc3VjY2VzcyhyZXNwb25zZSkge1xyXG5cdFx0XHRpZiAodHlwZW9mIHJlc3BvbnNlLmRhdGEgPT09ICdvYmplY3QnKSB7XHJcblx0XHRcdFx0cmV0dXJuIHJlc3BvbnNlLmRhdGE7XHJcblx0XHRcdH0gZWxzZSB7XHJcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKCdyZXRyaWV2ZWQgZGF0YSBpcyBub3QgdHlwZW9mIG9iamVjdC4nKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogUHJvbWlzZSByZXNwb25zZSBmdW5jdGlvbiAtIGVycm9yXHJcblx0XHQgKiBUaHJvd3MgYW4gZXJyb3Igd2l0aCBlcnJvciBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGVycm9yIHtvYmplY3R9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGVycm9yKGVycm9yKSB7XHJcblx0XHRcdHRocm93IG5ldyBFcnJvcignRXJyb3IgcmV0cmlldmluZyBkYXRhJywgZXJyb3IpO1xyXG5cdFx0fVxyXG5cdH1cclxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5kaXJlY3RpdmUoJ2xvYWRpbmcnLCBsb2FkaW5nKTtcclxuXHJcblx0bG9hZGluZy4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xyXG5cclxuXHRmdW5jdGlvbiBsb2FkaW5nKCR3aW5kb3csIHJlc2l6ZSkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdHJlcGxhY2U6IHRydWUsXHJcblx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvY29yZS91aS9sb2FkaW5nLnRwbC5odG1sJyxcclxuXHRcdFx0dHJhbnNjbHVkZTogdHJ1ZSxcclxuXHRcdFx0Y29udHJvbGxlcjogbG9hZGluZ0N0cmwsXHJcblx0XHRcdGNvbnRyb2xsZXJBczogJ2xvYWRpbmcnLFxyXG5cdFx0XHRiaW5kVG9Db250cm9sbGVyOiB0cnVlLFxyXG5cdFx0XHRsaW5rOiBsb2FkaW5nTGlua1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIGxvYWRpbmcgTElOS1xyXG5cdFx0ICogRGlzYWJsZXMgcGFnZSBzY3JvbGxpbmcgd2hlbiBsb2FkaW5nIG92ZXJsYXkgaXMgb3BlblxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkc2NvcGVcclxuXHRcdCAqIEBwYXJhbSAkZWxlbWVudFxyXG5cdFx0ICogQHBhcmFtICRhdHRyc1xyXG5cdFx0ICogQHBhcmFtIGxvYWRpbmcge2NvbnRyb2xsZXJ9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGxvYWRpbmdMaW5rKCRzY29wZSwgJGVsZW1lbnQsICRhdHRycywgbG9hZGluZykge1xyXG5cdFx0XHQvLyBwcml2YXRlIHZhcmlhYmxlc1xyXG5cdFx0XHR2YXIgXyRib2R5ID0gYW5ndWxhci5lbGVtZW50KCdib2R5Jyk7XHJcblx0XHRcdHZhciBfd2luSGVpZ2h0ID0gJHdpbmRvdy5pbm5lckhlaWdodCArICdweCc7XHJcblxyXG5cdFx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0XHQvLyBpbml0aWFsaXplIGRlYm91bmNlZCByZXNpemVcclxuXHRcdFx0XHR2YXIgX3JzID0gcmVzaXplLmluaXQoe1xyXG5cdFx0XHRcdFx0c2NvcGU6ICRzY29wZSxcclxuXHRcdFx0XHRcdHJlc2l6ZWRGbjogX3Jlc2l6ZWQsXHJcblx0XHRcdFx0XHRkZWJvdW5jZTogMjAwXHJcblx0XHRcdFx0fSk7XHJcblxyXG5cdFx0XHRcdC8vICR3YXRjaCBhY3RpdmUgc3RhdGVcclxuXHRcdFx0XHQkc2NvcGUuJHdhdGNoKCdsb2FkaW5nLmFjdGl2ZScsIF8kd2F0Y2hBY3RpdmUpO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogV2luZG93IHJlc2l6ZWRcclxuXHRcdFx0ICogSWYgbG9hZGluZywgcmVhcHBseSBib2R5IGhlaWdodFxyXG5cdFx0XHQgKiB0byBwcmV2ZW50IHNjcm9sbGJhclxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX3Jlc2l6ZWQoKSB7XHJcblx0XHRcdFx0X3dpbkhlaWdodCA9ICR3aW5kb3cuaW5uZXJIZWlnaHQgKyAncHgnO1xyXG5cclxuXHRcdFx0XHRpZiAobG9hZGluZy5hY3RpdmUpIHtcclxuXHRcdFx0XHRcdF8kYm9keS5jc3Moe1xyXG5cdFx0XHRcdFx0XHRoZWlnaHQ6IF93aW5IZWlnaHQsXHJcblx0XHRcdFx0XHRcdG92ZXJmbG93WTogJ2hpZGRlbidcclxuXHRcdFx0XHRcdH0pO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqICR3YXRjaCBsb2FkaW5nLmFjdGl2ZVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHtib29sZWFufVxyXG5cdFx0XHQgKiBAcGFyYW0gb2xkVmFsIHt1bmRlZmluZWR8Ym9vbGVhbn1cclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF8kd2F0Y2hBY3RpdmUobmV3VmFsLCBvbGRWYWwpIHtcclxuXHRcdFx0XHRpZiAobmV3VmFsKSB7XHJcblx0XHRcdFx0XHRfb3BlbigpO1xyXG5cdFx0XHRcdH0gZWxzZSB7XHJcblx0XHRcdFx0XHRfY2xvc2UoKTtcclxuXHRcdFx0XHR9XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBPcGVuIGxvYWRpbmdcclxuXHRcdFx0ICogRGlzYWJsZSBzY3JvbGxcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9vcGVuKCkge1xyXG5cdFx0XHRcdF8kYm9keS5jc3Moe1xyXG5cdFx0XHRcdFx0aGVpZ2h0OiBfd2luSGVpZ2h0LFxyXG5cdFx0XHRcdFx0b3ZlcmZsb3dZOiAnaGlkZGVuJ1xyXG5cdFx0XHRcdH0pO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogQ2xvc2UgbG9hZGluZ1xyXG5cdFx0XHQgKiBFbmFibGUgc2Nyb2xsXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfY2xvc2UoKSB7XHJcblx0XHRcdFx0XyRib2R5LmNzcyh7XHJcblx0XHRcdFx0XHRoZWlnaHQ6ICdhdXRvJyxcclxuXHRcdFx0XHRcdG92ZXJmbG93WTogJ2F1dG8nXHJcblx0XHRcdFx0fSk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHR9XHJcblxyXG5cdGxvYWRpbmdDdHJsLiRpbmplY3QgPSBbJyRzY29wZSddO1xyXG5cdC8qKlxyXG5cdCAqIGxvYWRpbmcgQ09OVFJPTExFUlxyXG5cdCAqIFVwZGF0ZSB0aGUgbG9hZGluZyBzdGF0dXMgYmFzZWRcclxuXHQgKiBvbiByb3V0ZUNoYW5nZSBzdGF0ZVxyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGxvYWRpbmdDdHJsKCRzY29wZSkge1xyXG5cdFx0dmFyIGxvYWRpbmcgPSB0aGlzO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyB0dXJuIG9uIGxvYWRpbmcgZm9yIGluaXRpYWwgcGFnZSBsb2FkXHJcblx0XHRcdF9sb2FkaW5nQWN0aXZlKCk7XHJcblxyXG5cdFx0XHQkc2NvcGUuJG9uKCdsb2FkaW5nLW9uJywgX2xvYWRpbmdBY3RpdmUpO1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdsb2FkaW5nLW9mZicsIF9sb2FkaW5nSW5hY3RpdmUpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogU2V0IGxvYWRpbmcgdG8gYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdBY3RpdmUoKSB7XHJcblx0XHRcdGxvYWRpbmcuYWN0aXZlID0gdHJ1ZTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFNldCBsb2FkaW5nIHRvIGluYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdJbmFjdGl2ZSgpIHtcclxuXHRcdFx0bG9hZGluZy5hY3RpdmUgPSBmYWxzZTtcclxuXHRcdH1cclxuXHR9XHJcblxyXG59KSgpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdC8vIG1lZGlhIHF1ZXJ5IGNvbnN0YW50c1xyXG5cdHZhciBNUSA9IHtcclxuXHRcdFNNQUxMOiAnKG1heC13aWR0aDogNzY3cHgpJyxcclxuXHRcdExBUkdFOiAnKG1pbi13aWR0aDogNzY4cHgpJ1xyXG5cdH07XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnN0YW50KCdNUScsIE1RKTtcclxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5maWx0ZXIoJ3RydXN0QXNIVE1MJywgdHJ1c3RBc0hUTUwpO1xyXG5cclxuXHR0cnVzdEFzSFRNTC4kaW5qZWN0ID0gWyckc2NlJ107XHJcblxyXG5cdGZ1bmN0aW9uIHRydXN0QXNIVE1MKCRzY2UpIHtcclxuXHRcdHJldHVybiBmdW5jdGlvbih0ZXh0KSB7XHJcblx0XHRcdHJldHVybiAkc2NlLnRydXN0QXNIdG1sKHRleHQpO1xyXG5cdFx0fTtcclxuXHR9XHJcbn0pKCk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignSGVhZGVyQ3RybCcsIEhlYWRlckN0cmwpO1xyXG5cclxuXHRIZWFkZXJDdHJsLiRpbmplY3QgPSBbJyRsb2NhdGlvbicsICdKU09ORGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBIZWFkZXJDdHJsKCRsb2NhdGlvbiwgSlNPTkRhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBoZWFkZXIgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGhlYWRlci5pbmRleElzQWN0aXZlID0gaW5kZXhJc0FjdGl2ZTtcclxuXHRcdGhlYWRlci5uYXZJc0FjdGl2ZSA9IG5hdklzQWN0aXZlO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBhY3RpdmF0ZSBjb250cm9sbGVyXHJcblx0XHRcdF9hY3RpdmF0ZSgpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQ29udHJvbGxlciBhY3RpdmF0ZVxyXG5cdFx0ICogR2V0IEpTT04gZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHsqfVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2FjdGl2YXRlKCkge1xyXG5cdFx0XHQvLyBnZXQgdGhlIGRhdGEgZnJvbSBKU09OXHJcblx0XHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKS50aGVuKF9nZXRKc29uU3VjY2Vzcyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTdWNjZXNzZnVsIHByb21pc2UgZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSBkYXRhIHtqc29ufVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2dldEpzb25TdWNjZXNzKGRhdGEpIHtcclxuXHRcdFx0aGVhZGVyLmpzb24gPSBkYXRhO1xyXG5cdFx0XHRyZXR1cm4gaGVhZGVyLmpzb247XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBBcHBseSBjbGFzcyB0byBpbmRleCBuYXYgaWYgYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIHtzdHJpbmd9IHBhdGhcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gaW5kZXhJc0FjdGl2ZShwYXRoKSB7XHJcblx0XHRcdC8vIHBhdGggc2hvdWxkIGJlICcvJ1xyXG5cdFx0XHRyZXR1cm4gJGxvY2F0aW9uLnBhdGgoKSA9PT0gcGF0aDtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFwcGx5IGNsYXNzIHRvIGN1cnJlbnRseSBhY3RpdmUgbmF2IGl0ZW1cclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0ge3N0cmluZ30gcGF0aFxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBuYXZJc0FjdGl2ZShwYXRoKSB7XHJcblx0XHRcdHJldHVybiAkbG9jYXRpb24ucGF0aCgpLnN1YnN0cigwLCBwYXRoLmxlbmd0aCkgPT09IHBhdGg7XHJcblx0XHR9XHJcblx0fVxyXG5cclxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5kaXJlY3RpdmUoJ25hdkNvbnRyb2wnLCBuYXZDb250cm9sKTtcclxuXHJcblx0bmF2Q29udHJvbC4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xyXG5cclxuXHRmdW5jdGlvbiBuYXZDb250cm9sKCR3aW5kb3csIHJlc2l6ZSkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdGxpbms6IG5hdkNvbnRyb2xMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogbmF2Q29udHJvbCBMSU5LIGZ1bmN0aW9uXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRzY29wZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBuYXZDb250cm9sTGluaygkc2NvcGUpIHtcclxuXHRcdFx0Ly8gZGF0YSBtb2RlbFxyXG5cdFx0XHQkc2NvcGUubmF2ID0ge307XHJcblxyXG5cdFx0XHQvLyBwcml2YXRlIHZhcmlhYmxlc1xyXG5cdFx0XHR2YXIgXyRib2R5ID0gYW5ndWxhci5lbGVtZW50KCdib2R5Jyk7XHJcblx0XHRcdHZhciBfbGF5b3V0Q2FudmFzID0gXyRib2R5LmZpbmQoJy5sYXlvdXQtY2FudmFzJyk7XHJcblx0XHRcdHZhciBfbmF2T3BlbjtcclxuXHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIGluaXRpYWxpemUgZGVib3VuY2VkIHJlc2l6ZVxyXG5cdFx0XHRcdHZhciBfcnMgPSByZXNpemUuaW5pdCh7XHJcblx0XHRcdFx0XHRzY29wZTogJHNjb3BlLFxyXG5cdFx0XHRcdFx0cmVzaXplZEZuOiBfcmVzaXplZCxcclxuXHRcdFx0XHRcdGRlYm91bmNlOiAxMDBcclxuXHRcdFx0XHR9KTtcclxuXHJcblx0XHRcdFx0JHNjb3BlLiRvbignJGxvY2F0aW9uQ2hhbmdlU3RhcnQnLCBfJGxvY2F0aW9uQ2hhbmdlU3RhcnQpO1xyXG5cdFx0XHRcdCRzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIF9lbnRlck1vYmlsZSk7XHJcblx0XHRcdFx0JHNjb3BlLiRvbignZXhpdC1tb2JpbGUnLCBfZXhpdE1vYmlsZSk7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBSZXNpemVkIHdpbmRvdyAoZGVib3VuY2VkKVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX3Jlc2l6ZWQoKSB7XHJcblx0XHRcdFx0X2xheW91dENhbnZhcy5jc3Moe1xyXG5cdFx0XHRcdFx0bWluSGVpZ2h0OiAkd2luZG93LmlubmVySGVpZ2h0ICsgJ3B4J1xyXG5cdFx0XHRcdH0pO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogT3BlbiBtb2JpbGUgbmF2aWdhdGlvblxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX29wZW5OYXYoKSB7XHJcblx0XHRcdFx0XyRib2R5XHJcblx0XHRcdFx0XHQucmVtb3ZlQ2xhc3MoJ25hdi1jbG9zZWQnKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCduYXYtb3BlbicpO1xyXG5cclxuXHRcdFx0XHRfbmF2T3BlbiA9IHRydWU7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBDbG9zZSBtb2JpbGUgbmF2aWdhdGlvblxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX2Nsb3NlTmF2KCkge1xyXG5cdFx0XHRcdF8kYm9keVxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduYXYtb3BlbicpXHJcblx0XHRcdFx0XHQuYWRkQ2xhc3MoJ25hdi1jbG9zZWQnKTtcclxuXHJcblx0XHRcdFx0X25hdk9wZW4gPSBmYWxzZTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIFRvZ2dsZSBuYXYgb3Blbi9jbG9zZWRcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIHRvZ2dsZU5hdigpIHtcclxuXHRcdFx0ICAgIGNvbnNvbGUubG9nKFwieW9cIik7XHJcblx0XHRcdFx0aWYgKCFfbmF2T3Blbikge1xyXG5cdFx0XHRcdFx0X29wZW5OYXYoKTtcclxuXHRcdFx0XHR9IGVsc2Uge1xyXG5cdFx0XHRcdFx0X2Nsb3NlTmF2KCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogV2hlbiBjaGFuZ2luZyBsb2NhdGlvbiwgY2xvc2UgdGhlIG5hdiBpZiBpdCdzIG9wZW5cclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF8kbG9jYXRpb25DaGFuZ2VTdGFydCgpIHtcclxuXHRcdFx0XHRpZiAoX25hdk9wZW4pIHtcclxuXHRcdFx0XHRcdF9jbG9zZU5hdigpO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIEZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2hlbiBlbnRlcmluZyBtb2JpbGUgbWVkaWEgcXVlcnlcclxuXHRcdFx0ICogQ2xvc2UgbmF2IGFuZCBzZXQgdXAgbWVudSB0b2dnbGluZyBmdW5jdGlvbmFsaXR5XHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfZW50ZXJNb2JpbGUobXEpIHtcclxuXHRcdFx0XHRfY2xvc2VOYXYoKTtcclxuXHJcblx0XHRcdFx0Ly8gYmluZCBmdW5jdGlvbiB0byB0b2dnbGUgbW9iaWxlIG5hdmlnYXRpb24gb3Blbi9jbG9zZWRcclxuXHRcdFx0XHQkc2NvcGUubmF2LnRvZ2dsZU5hdiA9IHRvZ2dsZU5hdjtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIEZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2hlbiBleGl0aW5nIG1vYmlsZSBtZWRpYSBxdWVyeVxyXG5cdFx0XHQgKiBEaXNhYmxlIG1lbnUgdG9nZ2xpbmcgYW5kIHJlbW92ZSBib2R5IGNsYXNzZXNcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9leGl0TW9iaWxlKG1xKSB7XHJcblx0XHRcdFx0Ly8gdW5iaW5kIGZ1bmN0aW9uIHRvIHRvZ2dsZSBtb2JpbGUgbmF2aWdhdGlvbiBvcGVuL2Nsb3NlZFxyXG5cdFx0XHRcdCRzY29wZS5uYXYudG9nZ2xlTmF2ID0gbnVsbDtcclxuXHJcblx0XHRcdFx0XyRib2R5LnJlbW92ZUNsYXNzKCduYXYtY2xvc2VkIG5hdi1vcGVuJyk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHR9XHJcblxyXG59KSgpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnRyb2xsZXIoJ0Vycm9yNDA0Q3RybCcsIEVycm9yNDA0Q3RybCk7XHJcblxyXG5cdEVycm9yNDA0Q3RybC4kaW5qZWN0ID0gWyckc2NvcGUnLCAnUGFnZSddO1xyXG5cclxuXHRmdW5jdGlvbiBFcnJvcjQwNEN0cmwoJHNjb3BlLCBQYWdlKSB7XHJcblx0XHR2YXIgZTQwNCA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gYmluZGFibGUgbWVtYmVyc1xyXG5cdFx0ZTQwNC50aXRsZSA9ICc0MDQgLSBQYWdlIE5vdCBGb3VuZCc7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cclxuXHRcdFx0UGFnZS5zZXRUaXRsZShlNDA0LnRpdGxlKTtcclxuXHJcblx0XHRcdC8vIG5vIGRhdGEgdG8gbG9hZCwgYnV0IGxvYWRpbmcgc3RhdGUgbWlnaHQgYmUgb25cclxuXHRcdFx0JHNjb3BlLiRlbWl0KCdsb2FkaW5nLW9mZicpO1xyXG5cdFx0fVxyXG5cclxuXHRcdHJldHVybiB7ICAgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0ICAgIGluaXQ6IF9pbml0IC8vdGVzdCBjb2RlXHJcblx0XHR9ICAgICAgICAgICAgICAgLy90ZXN0IGNvZGVcclxuXHR9XHJcbn0pKCk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignSG9tZUN0cmwnLCBIb21lQ3RybCk7XHJcblxyXG5cdEhvbWVDdHJsLiRpbmplY3QgPSBbJyRzY29wZScsICdVdGlscycsICdQYWdlJywgJ0pTT05EYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIEhvbWVDdHJsKCRzY29wZSwgVXRpbHMsIFBhZ2UsIEpTT05EYXRhKSB7XHJcblx0XHQvLyBjb250cm9sbGVyQXMgVmlld01vZGVsXHJcblx0XHR2YXIgaG9tZSA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gYmluZGFibGUgbWVtYmVyc1xyXG5cdFx0aG9tZS50aXRsZSA9ICdIb21lJztcclxuXHRcdGhvbWUuZ2xvYmFsID0gVXRpbHM7XHJcblx0XHRob21lLm5hbWUgPSAnVmlzaXRvcic7XHJcblx0XHRob21lLmFsZXJ0R3JlZXRpbmcgPSBVdGlscy5hbGVydEdyZWV0aW5nO1xyXG5cdFx0aG9tZS5zdHJpbmdPZkhUTUwgPSAnPHN0cm9uZyBzdHlsZT1cImNvbG9yOiBncmVlbjtcIj5Tb21lIGdyZWVuIHRleHQ8L3N0cm9uZz4gYm91bmQgYXMgSFRNTCB3aXRoIGEgPGEgaHJlZj1cIiNcIj5saW5rPC9hPiwgdHJ1c3RlZCB3aXRoIFNDRSEnO1xyXG5cdFx0aG9tZS52aWV3Zm9ybWF0ID0gbnVsbDtcclxuXHJcblx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0Ly8gc2V0IHBhZ2UgPHRpdGxlPlxyXG5cdFx0XHRQYWdlLnNldFRpdGxlKGhvbWUudGl0bGUpO1xyXG5cclxuXHRcdFx0Ly8gYWN0aXZhdGUgY29udHJvbGxlclxyXG5cdFx0XHRfYWN0aXZhdGUoKTtcclxuXHJcblx0XHRcdC8vIG1lZGlhcXVlcnkgZXZlbnRzXHJcblx0XHRcdCRzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIF9lbnRlck1vYmlsZSk7XHJcblx0XHRcdCRzY29wZS4kb24oJ2V4aXQtbW9iaWxlJywgX2V4aXRNb2JpbGUpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQ29udHJvbGxlciBhY3RpdmF0ZVxyXG5cdFx0ICogR2V0IEpTT04gZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHsqfVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2FjdGl2YXRlKCkge1xyXG5cdFx0XHQvLyBzdGFydCBsb2FkaW5nXHJcblx0XHRcdCRzY29wZS4kZW1pdCgnbG9hZGluZy1vbicpO1xyXG5cclxuXHRcdFx0Ly8gZ2V0IHRoZSBkYXRhIGZyb20gSlNPTlxyXG5cdFx0XHRyZXR1cm4gSlNPTkRhdGEuZ2V0TG9jYWxEYXRhKCkudGhlbihfZ2V0SnNvblN1Y2Nlc3MpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogU3VjY2Vzc2Z1bCBwcm9taXNlIGRhdGFcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gZGF0YSB7anNvbn1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9nZXRKc29uU3VjY2VzcyhkYXRhKSB7XHJcblx0XHRcdGhvbWUuanNvbiA9IGRhdGE7XHJcblxyXG5cdFx0XHQvLyBzdG9wIGxvYWRpbmdcclxuXHRcdFx0JHNjb3BlLiRlbWl0KCdsb2FkaW5nLW9mZicpO1xyXG5cclxuXHRcdFx0cmV0dXJuIGhvbWUuanNvbjtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEVudGVyIHNtYWxsIG1xXHJcblx0XHQgKiBTZXQgaG9tZS52aWV3Zm9ybWF0XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2VudGVyTW9iaWxlKCkge1xyXG5cdFx0XHRob21lLnZpZXdmb3JtYXQgPSAnc21hbGwnO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogRXhpdCBzbWFsbCBtcVxyXG5cdFx0ICogU2V0IGhvbWUudmlld2Zvcm1hdFxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9leGl0TW9iaWxlKCkge1xyXG5cdFx0XHRob21lLnZpZXdmb3JtYXQgPSAnbGFyZ2UnO1xyXG5cdFx0fVxyXG5cclxuXHRcdGZ1bmN0aW9uIGdldFZpZXcoKSB7ICAgICAgIC8vdGVzdCBjb2RlXHJcblx0XHQgICAgcmV0dXJuIGhvbWUudmlld2Zvcm1hdDsgLy90ZXN0IGNvZGVcclxuXHRcdH0gICAgICAgICAgICAgICAgICAgICAgICAgICAvL3Rlc3QgY29kZVxyXG5cclxuXHRcdHJldHVybiB7ICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vdGVzdCBjb2RlXHJcblx0XHQgICAgZW50ZXJNb2JpbGU6IF9lbnRlck1vYmlsZSwgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0ICAgIGV4aXRNb2JpbGU6IF9leGl0TW9iaWxlLCAgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdCAgICBnZXRKc29uU3VjZXNzOiBfZ2V0SnNvblN1Y2Nlc3MsIC8vdGVzdCBjb2RlXHJcblx0XHQgICAgYWN0aXZhdGU6IF9hY3RpdmF0ZSwgICAgICAgICAgICAvL3Rlc3QgY29kZVxyXG4gICAgICAgICAgICBnZXRWaWV3OiBnZXRWaWV3ICAgICAgICAgICAgICAgIC8vdGVzdCBjb2RlXHJcblx0XHR9ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvL3Rlc3QgY29kZVxyXG5cdH1cclxufSkoKTsiLCIvKipcclxuICogRGlyZWN0aXZlcyAoYW5kIGFzc29jaWF0ZWQgYXR0cmlidXRlcykgYXJlIGFsd2F5cyBkZWNsYXJlZCBhcyBjYW1lbENhc2UgaW4gSlMgYW5kIHNuYWtlLWNhc2UgaW4gSFRNTFxyXG4gKiBBbmd1bGFyJ3MgYnVpbHQtaW4gPGE+IGRpcmVjdGl2ZSBhdXRvbWF0aWNhbGx5IGltcGxlbWVudHMgcHJldmVudERlZmF1bHQgb24gbGlua3MgdGhhdCBkb24ndCBoYXZlIGFuIGhyZWYgYXR0cmlidXRlXHJcbiAqIENvbXBsZXggSmF2YVNjcmlwdCBET00gbWFuaXB1bGF0aW9uIHNob3VsZCBhbHdheXMgYmUgZG9uZSBpbiBkaXJlY3RpdmUgbGluayBmdW5jdGlvbnMsIGFuZCAkYXBwbHkgc2hvdWxkIG5ldmVyIGJlIHVzZWQgaW4gYSBjb250cm9sbGVyISBTaW1wbGUgRE9NIG1hbmlwdWxhdGlvbiBzaG91bGQgYmUgaW4gdGhlIHZpZXcuXHJcbiAqL1xyXG5cclxuLyotLS0gU2FtcGxlIERpcmVjdGl2ZSB3aXRoIGEgJHdhdGNoIC0tLSovXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmRpcmVjdGl2ZSgnc2FtcGxlRGlyZWN0aXZlJywgc2FtcGxlRGlyZWN0aXZlKTtcclxuXHJcblx0c2FtcGxlRGlyZWN0aXZlLiRpbmplY3QgPSBbJyR0aW1lb3V0J107XHJcblxyXG5cdGZ1bmN0aW9uIHNhbXBsZURpcmVjdGl2ZSgkdGltZW91dCkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdHJlcGxhY2U6IHRydWUsXHJcblx0XHRcdHNjb3BlOiB7fSxcclxuXHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvc2FtcGxlLnRwbC5odG1sJyxcclxuXHRcdFx0dHJhbnNjbHVkZTogdHJ1ZSxcclxuXHRcdFx0Y29udHJvbGxlcjogU2FtcGxlRGlyZWN0aXZlQ3RybCxcclxuXHRcdFx0Y29udHJvbGxlckFzOiAnc2QnLFxyXG5cdFx0XHRiaW5kVG9Db250cm9sbGVyOiB7XHJcblx0XHRcdFx0anNvbkRhdGE6ICc9J1xyXG5cdFx0XHR9LFxyXG5cdFx0XHRsaW5rOiBzYW1wbGVEaXJlY3RpdmVMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogc2FtcGxlRGlyZWN0aXZlIExJTksgZnVuY3Rpb25cclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJHNjb3BlXHJcblx0XHQgKiBAcGFyYW0gJGVsZW1lbnRcclxuXHRcdCAqIEBwYXJhbSAkYXR0cnNcclxuXHRcdCAqIEBwYXJhbSBzZCB7Y29udHJvbGxlcn1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gc2FtcGxlRGlyZWN0aXZlTGluaygkc2NvcGUsICRlbGVtZW50LCAkYXR0cnMsIHNkKSB7XHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIHdhdGNoIGZvciBhc3luYyBkYXRhIHRvIGJlY29tZSBhdmFpbGFibGUgYW5kIHVwZGF0ZVxyXG5cdFx0XHRcdCRzY29wZS4kd2F0Y2goJ3NkLmpzb25EYXRhJywgXyR3YXRjaEpzb25EYXRhKTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqICR3YXRjaCBmb3Igc2QuanNvbkRhdGEgdG8gYmVjb21lIGF2YWlsYWJsZVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHsqfVxyXG5cdFx0XHQgKiBAcGFyYW0gb2xkVmFsIHsqfVxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gXyR3YXRjaEpzb25EYXRhKG5ld1ZhbCwgb2xkVmFsKSB7XHJcblx0XHRcdFx0aWYgKG5ld1ZhbCkge1xyXG5cdFx0XHRcdFx0c2QuanNvbkRhdGEgPSBuZXdWYWw7XHJcblxyXG5cdFx0XHRcdFx0JHRpbWVvdXQoZnVuY3Rpb24oKSB7XHJcblx0XHRcdFx0XHRcdGNvbnNvbGUubG9nKCdkZW1vbnN0cmF0ZSAkdGltZW91dCBpbmplY3Rpb24gaW4gYSBkaXJlY3RpdmUgbGluayBmdW5jdGlvbicpO1xyXG5cdFx0XHRcdFx0fSwgMTAwMCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fVxyXG5cclxuXHRTYW1wbGVEaXJlY3RpdmVDdHJsLiRpbmplY3QgPSBbXTtcclxuXHQvKipcclxuXHQgKiBzYW1wbGVEaXJlY3RpdmUgQ09OVFJPTExFUlxyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIFNhbXBsZURpcmVjdGl2ZUN0cmwoKSB7XHJcblx0XHR2YXIgc2QgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGNvbnRyb2xsZXIgbG9naWMgZ29lcyBoZXJlXHJcblx0fVxyXG5cclxufSkoKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdTdWJDdHJsJywgU3ViQ3RybCk7XHJcblxyXG5cdFN1YkN0cmwuJGluamVjdCA9IFsnVXRpbHMnLCAnUGFnZScsICdyZXNvbHZlTG9jYWxEYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIFN1YkN0cmwoVXRpbHMsIFBhZ2UsIHJlc29sdmVMb2NhbERhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBzdWIgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdHN1Yi50aXRsZSA9ICdTdWJwYWdlJztcclxuXHRcdHN1Yi5nbG9iYWwgPSBVdGlscztcclxuXHRcdHN1Yi5qc29uID0gcmVzb2x2ZUxvY2FsRGF0YTtcclxuXHJcblx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0Ly8gc2V0IHBhZ2UgPHRpdGxlPlxyXG5cdFx0XHRQYWdlLnNldFRpdGxlKHN1Yi50aXRsZSk7XHJcblx0XHR9XHJcblx0fVxyXG59KSgpOyJdLCJzb3VyY2VSb290IjoiL3NvdXJjZS8ifQ== diff --git a/src/reStart-app/reStart-app.spec.js b/src/reStart-app/reStart-app.spec.js new file mode 100644 index 0000000..c8a7817 --- /dev/null +++ b/src/reStart-app/reStart-app.spec.js @@ -0,0 +1,411 @@ +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: PageCtrl', function () { + + var scope, pageVm, broadcast; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + pageVm = $controller('PageCtrl as pageVm', { $scope: scope }); + broadcast = false; + })); + + it("Broadcasts loading-on event", function () { + scope.$on('loading-on', broadcastRecieved); + pageVm.loadingOn(); + expect(broadcast).toBeTruthy(); + }) + it("Broadcasts loading-off event", function () { + scope.$on('loading-off', broadcastRecieved); + pageVm.loadingOff(); + expect(broadcast).toBeTruthy(); + }) + it("Broadcasts enter-mobile event", function () { + scope.$on('enter-mobile', broadcastRecieved); + pageVm.enterMobile(); + expect(broadcast).toBeTruthy(); + }) + it("Broadcasts exit-mobile event", function () { + scope.$on('exit-mobile', broadcastRecieved); + pageVm.exitMobile(); + expect(broadcast).toBeTruthy(); + }) + + function broadcastRecieved() { + broadcast = true; + } + + }); + }); +})(); +; (function () { + describe('Module: reStart', function () { + var $factory; + beforeEach(function () { + module('reStart'); + }); + describe('Factory: Page', function () { + + var page; + beforeEach(inject(function ($injector) { + page = $injector.get('Page'); + })); + + it('Gets the page title', function () { + var title=page.getTitle(); + expect(title).toEqual('reStart Angular | Home'); + }) + it('Sets the page title', function () { + page.setTitle('New'); + expect(page.getTitle()).toEqual('reStart Angular | New'); + }) + + }); + }); +})(); +; (function () { + describe('Module: reStart', function () { + var $factory; + beforeEach(function () { + module('reStart'); + }); + describe('Factory: Utils', function () { + + var utils; + beforeEach(inject(function ($injector) { + utils = $injector.get('Utils'); + })); + + it('Gets the greeting', function () { + var greeting = utils.greeting; + expect(greeting).toEqual('Hello'); + }) + it('Greets given name', function () { + spyOn(utils,'alertGreeting') + utils.alertGreeting('Named'); + expect(utils.alertGreeting).toHaveBeenCalledWith("Named") + }) + + }); + }); +})(); +; (function () { + describe('Module: reStart', function () { + var $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + }); + }); + describe('Factory: JSONData', function () { + + var JSONData; + beforeEach(inject(function (_JSONData_,_$q_) { + var deferred = _$q_.defer(); + JSONData = _JSONData_; + rootScope = $rootScope; + + deferred.resolve({'local':'data'}); + spyOn(JSONData, 'getLocalData').and.returnValue(deferred.promise); + + })); + + it('Gets local data', function () { + var data; + JSONData.getLocalData().then(function (json) { + data = json; + }); + rootScope.$apply() + expect(data.local).toEqual('data'); + }) + + }); + }); +})(); +; (function () { + describe('Module: reStart', function () { + var $factory; + beforeEach(function () { + module('reStart'); + }); + describe('Factory: Res', function () { + + var res; + beforeEach(inject(function ($injector) { + res = $injector.get('Res'); + })); + + it('Checks if response is of type object', function () { + var obj = { data: {Iam:'obj'} } + var response = res.success(obj); + expect(response.Iam).toEqual('obj'); + }) + it('Throws an error if data is not of type obj', function () { + expect(res.success).toThrow(); + }) + it('Responds to Error', function () { + expect(res.error).toThrow(); + }) + + }); + }); +})(); +; (function () { + var $compile, $rootScope; + beforeEach(function () { + module('reStart'); + }); + + describe('Directive: Loading', function () { + var element; + beforeEach(function () { + module('templates'); + inject( + ['$compile', '$rootScope', function ($c, $r) { + $compile = $c; + $rootScope = $r; + }] + ) + element = $compile('<loading></loading>')($rootScope); + angular.element(document.body).append(element); + //trigger directive to be injected + $rootScope.$digest(); + }); + + it("should load the loading template", function () { + expect(element.text()).toBeFalsy(); + }) + + afterEach(function () { + element.remove(); + }); + }); +})(); +; (function () { + beforeEach(function () { + module('reStart'); + }); + describe('Filter: Trust As HTML', function () { + + it('has a tust as html filter', inject(function ($filter) { + expect($filter('trustAsHTML')).not.toBeNull(); + })); + + + it('uses sce to trust text as html', inject(function ($filter) { + var trustAsHTML = $filter('trustAsHTML'); + expect(trustAsHTML("<div>")).toBeTruthy(); + })); + }); +})(); +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: HeaderCtrl', function () { + + var scope, headerVm; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + headerVm = $controller('HeaderCtrl as headerVm', { $scope: scope }); + })); + + it("Apply class to index nav if active", function () { + expect(headerVm.indexIsActive('/')).toBeTruthy(); + }) + it("Apply class to currently active nav item", function () { + expect(headerVm.navIsActive('/')).toBeTruthy(); + }) + + }); + }); +})(); +; (function () { + var $compile, $rootScope,scope,element; + beforeEach(function () { + module('reStart'); + }); + + describe('Directive: navControl', function () { + beforeEach(function () { + module('templates'); + + //disable loading directive? + angular.module("reStart").directive("loading", function (loading) { + return { + priority: 100000, + terminal: true, + link: function () { + // do nothing + } + } + }); + + inject( + ['$compile', '$rootScope', function ($c, $r) { + $compile = $c; + $rootScope = $r; + scope = $rootScope.$new(); + }] + ) + element = $compile('<div nav-control><a class="toggle-offcanvas" ng-click="nav.toggleNav()"><span></span></a></div>')($rootScope); + angular.element(document.body).append(element); + $('loading').remove(); + //trigger directive to be injected + $rootScope.$digest(); + }); + + xit("toggles nav on and off", function () { + angular.element('.toggle-offcanvas').click(); + $rootScope.$digest(); + console.log(angular.element('body')[0]) + expect(angular.element('body').hasClass('nav-closed')).toBeFalsy(); + }) + }); +})(); +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: Error404Ctrl', function () { + + var scope, Error404Vm, broadcast; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + Error404Vm = $controller('Error404Ctrl as Error404Vm', { $scope: scope }); + broadcast = false; + })); + + it("Sends a loading-off message", function () { + scope.$on('loading-off', broadcastRecieved); + Error404Vm.init(); + expect(broadcast).toBeTruthy(); + }) + + function broadcastRecieved() { + broadcast = true; + } + + }); + }); +})(); +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: HomeCtrl', function () { + + var scope, homeVm, broadcast; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + homeVm = $controller('HomeCtrl as homeVm', { $scope: scope }); + broadcast = false; + })); + + it("Broadcasts loading-on event", function () { + scope.$on('loading-on', broadcastRecieved); + homeVm.activate(); + expect(broadcast).toBeTruthy(); + }) + it("loads data into controller", function () { + expect(homeVm.getJsonSucess({ data: "yo" })).toBeTruthy(); + }) + it("Sets view format to small", function () { + homeVm.enterMobile(); + expect(homeVm.getView()).toEqual("small") + }) + it("Sets view format to large", function () { + homeVm.exitMobile(); + expect(homeVm.getView()).toEqual("large") + }) + + function broadcastRecieved() { + broadcast = true; + } + + }); + }); +})(); +; (function () { + var $compile, $rootScope, scope, element; + beforeEach(function () { + module('reStart'); + }); + + describe('Directive: navControl', function () { + beforeEach(function () { + module('templates'); + + inject( + ['$compile', '$rootScope', function ($c, $r) { + $compile = $c; + $rootScope = $r; + scope = $rootScope.$new(); + }] + ) + element = $compile('<sample-directive json-data="sub.json">'+"I've been transcluded!"+'</sample-directive>')($rootScope); + angular.element(document.body).append(element); + //trigger directive to be injected + $rootScope.$digest(); + }); + + it("injects a template via directive", function () { + expect(angular.element('body').html()).toContain("included by a directive"); + }); + it("transcludes data to the injected template", function () { + expect(angular.element('.transclude').html()).toContain("I've been transcluded!"); + }); + }); +})(); +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: SubCtrl', function () { + + var scope, subVm; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + subVm = $controller('SubCtrl as subVm', { $scope: scope, 'Utils':'', 'resolveLocalData':{stuff:"And Things"}}); + })); + + it("Sets the page title", function () { + expect(subVm.title).toEqual("Subpage"); + }) + + }); + }); +})(); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNvcmUvUGFnZS5jdHJsLnNwZWMuanMiLCJjb3JlL1BhZ2UuZmFjdG9yeS5zcGVjLmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LnNwZWMuanMiLCJjb3JlL2dldC1kYXRhL0pTT05EYXRhLmZhY3Rvcnkuc3BlYy5qcyIsImNvcmUvZ2V0LWRhdGEvUmVzLmZhY3Rvcnkuc3BlYy5qcyIsImNvcmUvdWkvbG9hZGluZy5kaXIuc3BlYy5qcyIsImNvcmUvdWkvdHJ1c3RBc0hUTUwuZmlsdGVyLnNwZWMuanMiLCJtb2R1bGVzL2hlYWRlci9IZWFkZXIuY3RybC5zcGVjLmpzIiwibW9kdWxlcy9oZWFkZXIvbmF2Q29udHJvbC5kaXIuc3BlYy5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuc3BlYy5qcyIsInBhZ2VzL2hvbWUvSG9tZS5jdHJsLnNwZWMuanMiLCJwYWdlcy9zdWIvc2FtcGxlLmRpci5zcGVjLmpzIiwicGFnZXMvc3ViL1N1Yi5jdHJsLnNwZWMuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzlDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDakNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDM0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDOUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQy9CQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzlCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJyZVN0YXJ0LWFwcC5zcGVjLmpzIiwic291cmNlc0NvbnRlbnQiOlsiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgZGVzY3JpYmUoJ01vZHVsZTogcmVTdGFydCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgJGNvbnRyb2xsZXIsICRyb290U2NvcGU7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgICAgICAgICBpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgJHJvb3RTY29wZSA9ICRpbmplY3Rvci5nZXQoJyRyb290U2NvcGUnKTtcclxuICAgICAgICAgICAgICAgICRjb250cm9sbGVyID0gJGluamVjdG9yLmdldCgnJGNvbnRyb2xsZXInKTtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0NvbnRyb2xsZXI6IFBhZ2VDdHJsJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHNjb3BlLCBwYWdlVm0sIGJyb2FkY2FzdDtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKCRjb250cm9sbGVyLCAkcm9vdFNjb3BlKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZSA9ICRyb290U2NvcGUuJG5ldygpO1xyXG4gICAgICAgICAgICAgICAgcGFnZVZtID0gJGNvbnRyb2xsZXIoJ1BhZ2VDdHJsIGFzIHBhZ2VWbScsIHsgJHNjb3BlOiBzY29wZSB9KTtcclxuICAgICAgICAgICAgICAgIGJyb2FkY2FzdCA9IGZhbHNlO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdChcIkJyb2FkY2FzdHMgbG9hZGluZy1vbiBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2xvYWRpbmctb24nLCBicm9hZGNhc3RSZWNpZXZlZCk7XHJcbiAgICAgICAgICAgICAgICBwYWdlVm0ubG9hZGluZ09uKCk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoYnJvYWRjYXN0KS50b0JlVHJ1dGh5KCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KFwiQnJvYWRjYXN0cyBsb2FkaW5nLW9mZiBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2xvYWRpbmctb2ZmJywgYnJvYWRjYXN0UmVjaWV2ZWQpO1xyXG4gICAgICAgICAgICAgICAgcGFnZVZtLmxvYWRpbmdPZmYoKTtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChicm9hZGNhc3QpLnRvQmVUcnV0aHkoKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgaXQoXCJCcm9hZGNhc3RzIGVudGVyLW1vYmlsZSBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIGJyb2FkY2FzdFJlY2lldmVkKTtcclxuICAgICAgICAgICAgICAgIHBhZ2VWbS5lbnRlck1vYmlsZSgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGJyb2FkY2FzdCkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdChcIkJyb2FkY2FzdHMgZXhpdC1tb2JpbGUgZXZlbnRcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUuJG9uKCdleGl0LW1vYmlsZScsIGJyb2FkY2FzdFJlY2lldmVkKTtcclxuICAgICAgICAgICAgICAgIHBhZ2VWbS5leGl0TW9iaWxlKCk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoYnJvYWRjYXN0KS50b0JlVHJ1dGh5KCk7XHJcbiAgICAgICAgICAgIH0pXHJcblxyXG4gICAgICAgICAgICBmdW5jdGlvbiBicm9hZGNhc3RSZWNpZXZlZCgpIHtcclxuICAgICAgICAgICAgICAgIGJyb2FkY2FzdCA9IHRydWU7XHJcbiAgICAgICAgICAgIH1cclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkZmFjdG9yeTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0ZhY3Rvcnk6IFBhZ2UnLCBmdW5jdGlvbiAoKSB7XHJcblxyXG4gICAgICAgICAgICB2YXIgcGFnZTtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgcGFnZSA9ICRpbmplY3Rvci5nZXQoJ1BhZ2UnKTtcclxuICAgICAgICAgICAgfSkpO1xyXG5cclxuICAgICAgICAgICAgaXQoJ0dldHMgdGhlIHBhZ2UgdGl0bGUnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICB2YXIgdGl0bGU9cGFnZS5nZXRUaXRsZSgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHRpdGxlKS50b0VxdWFsKCdyZVN0YXJ0IEFuZ3VsYXIgfCBIb21lJyk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KCdTZXRzIHRoZSBwYWdlIHRpdGxlJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgcGFnZS5zZXRUaXRsZSgnTmV3Jyk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QocGFnZS5nZXRUaXRsZSgpKS50b0VxdWFsKCdyZVN0YXJ0IEFuZ3VsYXIgfCBOZXcnKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkZmFjdG9yeTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0ZhY3Rvcnk6IFV0aWxzJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHV0aWxzO1xyXG4gICAgICAgICAgICBiZWZvcmVFYWNoKGluamVjdChmdW5jdGlvbiAoJGluamVjdG9yKSB7XHJcbiAgICAgICAgICAgICAgICB1dGlscyA9ICRpbmplY3Rvci5nZXQoJ1V0aWxzJyk7XHJcbiAgICAgICAgICAgIH0pKTtcclxuXHJcbiAgICAgICAgICAgIGl0KCdHZXRzIHRoZSBncmVldGluZycsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgICAgIHZhciBncmVldGluZyA9IHV0aWxzLmdyZWV0aW5nO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGdyZWV0aW5nKS50b0VxdWFsKCdIZWxsbycpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdCgnR3JlZXRzIGdpdmVuIG5hbWUnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzcHlPbih1dGlscywnYWxlcnRHcmVldGluZycpXHJcbiAgICAgICAgICAgICAgICB1dGlscy5hbGVydEdyZWV0aW5nKCdOYW1lZCcpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHV0aWxzLmFsZXJ0R3JlZXRpbmcpLnRvSGF2ZUJlZW5DYWxsZWRXaXRoKFwiTmFtZWRcIilcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkcm9vdFNjb3BlO1xyXG4gICAgICAgIGJlZm9yZUVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBtb2R1bGUoJ3JlU3RhcnQnKTtcclxuICAgICAgICAgICAgaW5qZWN0KGZ1bmN0aW9uICgkaW5qZWN0b3IpIHtcclxuICAgICAgICAgICAgICAgICRyb290U2NvcGUgPSAkaW5qZWN0b3IuZ2V0KCckcm9vdFNjb3BlJyk7XHJcbiAgICAgICAgICAgIH0pO1xyXG4gICAgICAgIH0pO1xyXG4gICAgICAgIGRlc2NyaWJlKCdGYWN0b3J5OiBKU09ORGF0YScsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgICAgIHZhciBKU09ORGF0YTtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKF9KU09ORGF0YV8sXyRxXykge1xyXG4gICAgICAgICAgICAgICAgdmFyIGRlZmVycmVkID0gXyRxXy5kZWZlcigpO1xyXG4gICAgICAgICAgICAgICAgSlNPTkRhdGEgPSBfSlNPTkRhdGFfO1xyXG4gICAgICAgICAgICAgICAgcm9vdFNjb3BlID0gJHJvb3RTY29wZTtcclxuXHJcbiAgICAgICAgICAgICAgICBkZWZlcnJlZC5yZXNvbHZlKHsnbG9jYWwnOidkYXRhJ30pO1xyXG4gICAgICAgICAgICAgICAgc3B5T24oSlNPTkRhdGEsICdnZXRMb2NhbERhdGEnKS5hbmQucmV0dXJuVmFsdWUoZGVmZXJyZWQucHJvbWlzZSk7XHJcblxyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdCgnR2V0cyBsb2NhbCBkYXRhJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgdmFyIGRhdGE7XHJcbiAgICAgICAgICAgICAgICBKU09ORGF0YS5nZXRMb2NhbERhdGEoKS50aGVuKGZ1bmN0aW9uIChqc29uKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGpzb247XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIHJvb3RTY29wZS4kYXBwbHkoKVxyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGRhdGEubG9jYWwpLnRvRXF1YWwoJ2RhdGEnKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkZmFjdG9yeTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0ZhY3Rvcnk6IFJlcycsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgICAgIHZhciByZXM7XHJcbiAgICAgICAgICAgIGJlZm9yZUVhY2goaW5qZWN0KGZ1bmN0aW9uICgkaW5qZWN0b3IpIHtcclxuICAgICAgICAgICAgICAgIHJlcyA9ICRpbmplY3Rvci5nZXQoJ1JlcycpO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdCgnQ2hlY2tzIGlmIHJlc3BvbnNlIGlzIG9mIHR5cGUgb2JqZWN0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgdmFyIG9iaiA9IHsgZGF0YToge0lhbTonb2JqJ30gfVxyXG4gICAgICAgICAgICAgICAgdmFyIHJlc3BvbnNlID0gcmVzLnN1Y2Nlc3Mob2JqKTtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChyZXNwb25zZS5JYW0pLnRvRXF1YWwoJ29iaicpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdCgnVGhyb3dzIGFuIGVycm9yIGlmIGRhdGEgaXMgbm90IG9mIHR5cGUgb2JqJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHJlcy5zdWNjZXNzKS50b1Rocm93KCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KCdSZXNwb25kcyB0byBFcnJvcicsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChyZXMuZXJyb3IpLnRvVGhyb3coKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICB2YXIgJGNvbXBpbGUsICRyb290U2NvcGU7XHJcbiAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICBtb2R1bGUoJ3JlU3RhcnQnKTtcclxuICAgIH0pO1xyXG5cclxuICAgIGRlc2NyaWJlKCdEaXJlY3RpdmU6IExvYWRpbmcnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgdmFyIGVsZW1lbnQ7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgndGVtcGxhdGVzJyk7XHJcbiAgICAgICAgICAgIGluamVjdChcclxuICAgICAgICAgICAgICAgIFsnJGNvbXBpbGUnLCAnJHJvb3RTY29wZScsIGZ1bmN0aW9uICgkYywgJHIpIHtcclxuICAgICAgICAgICAgICAgICAgICAkY29tcGlsZSA9ICRjO1xyXG4gICAgICAgICAgICAgICAgICAgICRyb290U2NvcGUgPSAkcjtcclxuICAgICAgICAgICAgICAgIH1dXHJcbiAgICAgICAgICAgIClcclxuICAgICAgICAgICAgZWxlbWVudCA9ICRjb21waWxlKCc8bG9hZGluZz48L2xvYWRpbmc+JykoJHJvb3RTY29wZSk7XHJcbiAgICAgICAgICAgIGFuZ3VsYXIuZWxlbWVudChkb2N1bWVudC5ib2R5KS5hcHBlbmQoZWxlbWVudCk7XHJcbiAgICAgICAgICAgIC8vdHJpZ2dlciBkaXJlY3RpdmUgdG8gYmUgaW5qZWN0ZWRcclxuICAgICAgICAgICAgJHJvb3RTY29wZS4kZGlnZXN0KCk7XHJcbiAgICAgICAgfSk7XHJcblxyXG4gICAgICAgIGl0KFwic2hvdWxkIGxvYWQgdGhlIGxvYWRpbmcgdGVtcGxhdGVcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBleHBlY3QoZWxlbWVudC50ZXh0KCkpLnRvQmVGYWxzeSgpO1xyXG4gICAgICAgIH0pXHJcblxyXG4gICAgICAgIGFmdGVyRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGVsZW1lbnQucmVtb3ZlKCk7XHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICBtb2R1bGUoJ3JlU3RhcnQnKTtcclxuICAgIH0pO1xyXG4gICAgZGVzY3JpYmUoJ0ZpbHRlcjogVHJ1c3QgQXMgSFRNTCcsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgaXQoJ2hhcyBhIHR1c3QgYXMgaHRtbCBmaWx0ZXInLCBpbmplY3QoZnVuY3Rpb24gKCRmaWx0ZXIpIHtcclxuICAgICAgICAgICAgZXhwZWN0KCRmaWx0ZXIoJ3RydXN0QXNIVE1MJykpLm5vdC50b0JlTnVsbCgpO1xyXG4gICAgICAgIH0pKTtcclxuXHJcblxyXG4gICAgICAgIGl0KCd1c2VzIHNjZSB0byB0cnVzdCB0ZXh0IGFzIGh0bWwnLCBpbmplY3QoZnVuY3Rpb24gKCRmaWx0ZXIpIHtcclxuICAgICAgICAgICAgdmFyIHRydXN0QXNIVE1MID0gJGZpbHRlcigndHJ1c3RBc0hUTUwnKTtcclxuICAgICAgICAgICAgZXhwZWN0KHRydXN0QXNIVE1MKFwiPGRpdj5cIikpLnRvQmVUcnV0aHkoKTtcclxuICAgICAgICB9KSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkY29udHJvbGxlciwgJHJvb3RTY29wZTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgICAgIGluamVjdChmdW5jdGlvbiAoJGluamVjdG9yKSB7XHJcbiAgICAgICAgICAgICAgICAkcm9vdFNjb3BlID0gJGluamVjdG9yLmdldCgnJHJvb3RTY29wZScpO1xyXG4gICAgICAgICAgICAgICAgJGNvbnRyb2xsZXIgPSAkaW5qZWN0b3IuZ2V0KCckY29udHJvbGxlcicpO1xyXG4gICAgICAgICAgICB9KTtcclxuICAgICAgICB9KTtcclxuICAgICAgICBkZXNjcmliZSgnQ29udHJvbGxlcjogSGVhZGVyQ3RybCcsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgICAgIHZhciBzY29wZSwgaGVhZGVyVm07XHJcbiAgICAgICAgICAgIGJlZm9yZUVhY2goaW5qZWN0KGZ1bmN0aW9uICgkY29udHJvbGxlciwgJHJvb3RTY29wZSkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUgPSAkcm9vdFNjb3BlLiRuZXcoKTtcclxuICAgICAgICAgICAgICAgIGhlYWRlclZtID0gJGNvbnRyb2xsZXIoJ0hlYWRlckN0cmwgYXMgaGVhZGVyVm0nLCB7ICRzY29wZTogc2NvcGUgfSk7XHJcbiAgICAgICAgICAgIH0pKTtcclxuXHJcbiAgICAgICAgICAgIGl0KFwiQXBwbHkgY2xhc3MgdG8gaW5kZXggbmF2IGlmIGFjdGl2ZVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoaGVhZGVyVm0uaW5kZXhJc0FjdGl2ZSgnLycpKS50b0JlVHJ1dGh5KCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KFwiQXBwbHkgY2xhc3MgdG8gY3VycmVudGx5IGFjdGl2ZSBuYXYgaXRlbVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoaGVhZGVyVm0ubmF2SXNBY3RpdmUoJy8nKSkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICB9KTtcclxuICAgIH0pO1xyXG59KSgpOyIsIjsgKGZ1bmN0aW9uICgpIHtcclxuICAgIHZhciAkY29tcGlsZSwgJHJvb3RTY29wZSxzY29wZSxlbGVtZW50O1xyXG4gICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICB9KTtcclxuXHJcbiAgICBkZXNjcmliZSgnRGlyZWN0aXZlOiBuYXZDb250cm9sJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIGJlZm9yZUVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBtb2R1bGUoJ3RlbXBsYXRlcycpO1xyXG5cclxuICAgICAgICAgICAgLy9kaXNhYmxlIGxvYWRpbmcgZGlyZWN0aXZlP1xyXG4gICAgICAgICAgICBhbmd1bGFyLm1vZHVsZShcInJlU3RhcnRcIikuZGlyZWN0aXZlKFwibG9hZGluZ1wiLCBmdW5jdGlvbiAobG9hZGluZykge1xyXG4gICAgICAgICAgICAgICAgcmV0dXJuIHtcclxuICAgICAgICAgICAgICAgICAgICBwcmlvcml0eTogMTAwMDAwLFxyXG4gICAgICAgICAgICAgICAgICAgIHRlcm1pbmFsOiB0cnVlLFxyXG4gICAgICAgICAgICAgICAgICAgIGxpbms6IGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gZG8gbm90aGluZ1xyXG4gICAgICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgfSk7XHJcblxyXG4gICAgICAgICAgICBpbmplY3QoXHJcbiAgICAgICAgICAgICAgICBbJyRjb21waWxlJywgJyRyb290U2NvcGUnLCBmdW5jdGlvbiAoJGMsICRyKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgJGNvbXBpbGUgPSAkYztcclxuICAgICAgICAgICAgICAgICAgICAkcm9vdFNjb3BlID0gJHI7XHJcbiAgICAgICAgICAgICAgICAgICAgc2NvcGUgPSAkcm9vdFNjb3BlLiRuZXcoKTtcclxuICAgICAgICAgICAgICAgIH1dXHJcbiAgICAgICAgICAgIClcclxuICAgICAgICAgICAgZWxlbWVudCA9ICRjb21waWxlKCc8ZGl2IG5hdi1jb250cm9sPjxhIGNsYXNzPVwidG9nZ2xlLW9mZmNhbnZhc1wiIG5nLWNsaWNrPVwibmF2LnRvZ2dsZU5hdigpXCI+PHNwYW4+PC9zcGFuPjwvYT48L2Rpdj4nKSgkcm9vdFNjb3BlKTtcclxuICAgICAgICAgICAgYW5ndWxhci5lbGVtZW50KGRvY3VtZW50LmJvZHkpLmFwcGVuZChlbGVtZW50KTtcclxuICAgICAgICAgICAgJCgnbG9hZGluZycpLnJlbW92ZSgpO1xyXG4gICAgICAgICAgICAvL3RyaWdnZXIgZGlyZWN0aXZlIHRvIGJlIGluamVjdGVkXHJcbiAgICAgICAgICAgICRyb290U2NvcGUuJGRpZ2VzdCgpO1xyXG4gICAgICAgIH0pO1xyXG5cclxuICAgICAgICB4aXQoXCJ0b2dnbGVzIG5hdiBvbiBhbmQgb2ZmXCIsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgYW5ndWxhci5lbGVtZW50KCcudG9nZ2xlLW9mZmNhbnZhcycpLmNsaWNrKCk7XHJcbiAgICAgICAgICAgICRyb290U2NvcGUuJGRpZ2VzdCgpO1xyXG4gICAgICAgICAgICBjb25zb2xlLmxvZyhhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKVswXSlcclxuICAgICAgICAgICAgZXhwZWN0KGFuZ3VsYXIuZWxlbWVudCgnYm9keScpLmhhc0NsYXNzKCduYXYtY2xvc2VkJykpLnRvQmVGYWxzeSgpO1xyXG4gICAgICAgIH0pXHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkY29udHJvbGxlciwgJHJvb3RTY29wZTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgICAgIGluamVjdChmdW5jdGlvbiAoJGluamVjdG9yKSB7XHJcbiAgICAgICAgICAgICAgICAkcm9vdFNjb3BlID0gJGluamVjdG9yLmdldCgnJHJvb3RTY29wZScpO1xyXG4gICAgICAgICAgICAgICAgJGNvbnRyb2xsZXIgPSAkaW5qZWN0b3IuZ2V0KCckY29udHJvbGxlcicpO1xyXG4gICAgICAgICAgICB9KTtcclxuICAgICAgICB9KTtcclxuICAgICAgICBkZXNjcmliZSgnQ29udHJvbGxlcjogRXJyb3I0MDRDdHJsJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHNjb3BlLCBFcnJvcjQwNFZtLCBicm9hZGNhc3Q7XHJcbiAgICAgICAgICAgIGJlZm9yZUVhY2goaW5qZWN0KGZ1bmN0aW9uICgkY29udHJvbGxlciwgJHJvb3RTY29wZSkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUgPSAkcm9vdFNjb3BlLiRuZXcoKTtcclxuICAgICAgICAgICAgICAgIEVycm9yNDA0Vm0gPSAkY29udHJvbGxlcignRXJyb3I0MDRDdHJsIGFzIEVycm9yNDA0Vm0nLCB7ICRzY29wZTogc2NvcGUgfSk7XHJcbiAgICAgICAgICAgICAgICBicm9hZGNhc3QgPSBmYWxzZTtcclxuICAgICAgICAgICAgfSkpO1xyXG5cclxuICAgICAgICAgICAgaXQoXCJTZW5kcyBhIGxvYWRpbmctb2ZmIG1lc3NhZ2VcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUuJG9uKCdsb2FkaW5nLW9mZicsIGJyb2FkY2FzdFJlY2lldmVkKTtcclxuICAgICAgICAgICAgICAgIEVycm9yNDA0Vm0uaW5pdCgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGJyb2FkY2FzdCkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICAgICAgZnVuY3Rpb24gYnJvYWRjYXN0UmVjaWV2ZWQoKSB7XHJcbiAgICAgICAgICAgICAgICBicm9hZGNhc3QgPSB0cnVlO1xyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgIH0pO1xyXG4gICAgfSk7XHJcbn0pKCk7IiwiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgZGVzY3JpYmUoJ01vZHVsZTogcmVTdGFydCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgJGNvbnRyb2xsZXIsICRyb290U2NvcGU7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgICAgICAgICBpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgJHJvb3RTY29wZSA9ICRpbmplY3Rvci5nZXQoJyRyb290U2NvcGUnKTtcclxuICAgICAgICAgICAgICAgICRjb250cm9sbGVyID0gJGluamVjdG9yLmdldCgnJGNvbnRyb2xsZXInKTtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0NvbnRyb2xsZXI6IEhvbWVDdHJsJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHNjb3BlLCBob21lVm0sIGJyb2FkY2FzdDtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKCRjb250cm9sbGVyLCAkcm9vdFNjb3BlKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZSA9ICRyb290U2NvcGUuJG5ldygpO1xyXG4gICAgICAgICAgICAgICAgaG9tZVZtID0gJGNvbnRyb2xsZXIoJ0hvbWVDdHJsIGFzIGhvbWVWbScsIHsgJHNjb3BlOiBzY29wZSB9KTtcclxuICAgICAgICAgICAgICAgIGJyb2FkY2FzdCA9IGZhbHNlO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdChcIkJyb2FkY2FzdHMgbG9hZGluZy1vbiBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2xvYWRpbmctb24nLCBicm9hZGNhc3RSZWNpZXZlZCk7XHJcbiAgICAgICAgICAgICAgICBob21lVm0uYWN0aXZhdGUoKTtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChicm9hZGNhc3QpLnRvQmVUcnV0aHkoKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgaXQoXCJsb2FkcyBkYXRhIGludG8gY29udHJvbGxlclwiLCBmdW5jdGlvbiAoKSB7ICAgICAgICAgICAgICBcclxuICAgICAgICAgICAgICAgIGV4cGVjdChob21lVm0uZ2V0SnNvblN1Y2Vzcyh7IGRhdGE6IFwieW9cIiB9KSkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdChcIlNldHMgdmlldyBmb3JtYXQgdG8gc21hbGxcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgaG9tZVZtLmVudGVyTW9iaWxlKCk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoaG9tZVZtLmdldFZpZXcoKSkudG9FcXVhbChcInNtYWxsXCIpXHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KFwiU2V0cyB2aWV3IGZvcm1hdCB0byBsYXJnZVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBob21lVm0uZXhpdE1vYmlsZSgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGhvbWVWbS5nZXRWaWV3KCkpLnRvRXF1YWwoXCJsYXJnZVwiKVxyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICAgICAgZnVuY3Rpb24gYnJvYWRjYXN0UmVjaWV2ZWQoKSB7XHJcbiAgICAgICAgICAgICAgICBicm9hZGNhc3QgPSB0cnVlO1xyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgIH0pO1xyXG4gICAgfSk7XHJcbn0pKCk7IiwiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgdmFyICRjb21waWxlLCAkcm9vdFNjb3BlLCBzY29wZSwgZWxlbWVudDtcclxuICAgIGJlZm9yZUVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgfSk7XHJcblxyXG4gICAgZGVzY3JpYmUoJ0RpcmVjdGl2ZTogbmF2Q29udHJvbCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCd0ZW1wbGF0ZXMnKTtcclxuXHJcbiAgICAgICAgICAgIGluamVjdChcclxuICAgICAgICAgICAgICAgIFsnJGNvbXBpbGUnLCAnJHJvb3RTY29wZScsIGZ1bmN0aW9uICgkYywgJHIpIHtcclxuICAgICAgICAgICAgICAgICAgICAkY29tcGlsZSA9ICRjO1xyXG4gICAgICAgICAgICAgICAgICAgICRyb290U2NvcGUgPSAkcjtcclxuICAgICAgICAgICAgICAgICAgICBzY29wZSA9ICRyb290U2NvcGUuJG5ldygpO1xyXG4gICAgICAgICAgICAgICAgfV1cclxuICAgICAgICAgICAgKVxyXG4gICAgICAgICAgICBlbGVtZW50ID0gJGNvbXBpbGUoJzxzYW1wbGUtZGlyZWN0aXZlIGpzb24tZGF0YT1cInN1Yi5qc29uXCI+JytcIkkndmUgYmVlbiB0cmFuc2NsdWRlZCFcIisnPC9zYW1wbGUtZGlyZWN0aXZlPicpKCRyb290U2NvcGUpO1xyXG4gICAgICAgICAgICBhbmd1bGFyLmVsZW1lbnQoZG9jdW1lbnQuYm9keSkuYXBwZW5kKGVsZW1lbnQpO1xyXG4gICAgICAgICAgICAvL3RyaWdnZXIgZGlyZWN0aXZlIHRvIGJlIGluamVjdGVkXHJcbiAgICAgICAgICAgICRyb290U2NvcGUuJGRpZ2VzdCgpO1xyXG4gICAgICAgIH0pO1xyXG5cclxuICAgICAgICBpdChcImluamVjdHMgYSB0ZW1wbGF0ZSB2aWEgZGlyZWN0aXZlXCIsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgZXhwZWN0KGFuZ3VsYXIuZWxlbWVudCgnYm9keScpLmh0bWwoKSkudG9Db250YWluKFwiaW5jbHVkZWQgYnkgYSBkaXJlY3RpdmVcIik7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgaXQoXCJ0cmFuc2NsdWRlcyBkYXRhIHRvIHRoZSBpbmplY3RlZCB0ZW1wbGF0ZVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGV4cGVjdChhbmd1bGFyLmVsZW1lbnQoJy50cmFuc2NsdWRlJykuaHRtbCgpKS50b0NvbnRhaW4oXCJJJ3ZlIGJlZW4gdHJhbnNjbHVkZWQhXCIpO1xyXG4gICAgICAgIH0pO1xyXG4gICAgfSk7XHJcbn0pKCk7IiwiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgZGVzY3JpYmUoJ01vZHVsZTogcmVTdGFydCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgJGNvbnRyb2xsZXIsICRyb290U2NvcGU7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgICAgICAgICBpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgJHJvb3RTY29wZSA9ICRpbmplY3Rvci5nZXQoJyRyb290U2NvcGUnKTtcclxuICAgICAgICAgICAgICAgICRjb250cm9sbGVyID0gJGluamVjdG9yLmdldCgnJGNvbnRyb2xsZXInKTtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0NvbnRyb2xsZXI6IFN1YkN0cmwnLCBmdW5jdGlvbiAoKSB7XHJcblxyXG4gICAgICAgICAgICB2YXIgc2NvcGUsIHN1YlZtO1xyXG4gICAgICAgICAgICBiZWZvcmVFYWNoKGluamVjdChmdW5jdGlvbiAoJGNvbnRyb2xsZXIsICRyb290U2NvcGUpIHtcclxuICAgICAgICAgICAgICAgIHNjb3BlID0gJHJvb3RTY29wZS4kbmV3KCk7XHJcbiAgICAgICAgICAgICAgICBzdWJWbSA9ICRjb250cm9sbGVyKCdTdWJDdHJsIGFzIHN1YlZtJywgeyAkc2NvcGU6IHNjb3BlLCAnVXRpbHMnOicnLCAncmVzb2x2ZUxvY2FsRGF0YSc6e3N0dWZmOlwiQW5kIFRoaW5nc1wifX0pO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdChcIlNldHMgdGhlIHBhZ2UgdGl0bGVcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHN1YlZtLnRpdGxlKS50b0VxdWFsKFwiU3VicGFnZVwiKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= diff --git a/tests/e2e.js b/tests/e2e.js new file mode 100644 index 0000000..fa69eae --- /dev/null +++ b/tests/e2e.js @@ -0,0 +1,105 @@ +(function () { + var Page = function () { + this.get = function () { + browser.get('http://localhost:8000/'); + browser.waitForAngular(); + }; + this.greet = element(by.buttonText("Greet")); + this.heading = element(by.css('.content-heading')); + this.homeString = element(by.css('.home-string')); + this.type = element(by.css('.display')); + this.name = element(by.model('home.name')); + this.navSub = element(by.css('.sub-nav')); + } + describe('reStart-Angular: Home', function () { + var page; + var width = 800; + var height = 600; + var newText = "Bob" + + beforeEach(function () { + page = new Page(); + page.get(); + browser.driver.manage().window().setSize(width, height); + }) + + it('Loads the home page', function () { + expect(page.heading.getText()).toContain('Home'); + }); + it('Greets a name', function () { + page.greet.click(); + var alertDialog = browser.driver.switchTo().alert(); + expect(alertDialog.getText()).toContain("Hello,"); + }); + it('Updates text in alert if input changes', function () { + page.name.clear(); + page.name.sendKeys(newText); + page.greet.click(); + var alertDialog = browser.driver.switchTo().alert(); + expect(alertDialog.getText()).toContain(newText); + }); + it('Updates text on page if input changes', function () { + page.name.clear(); + page.name.sendKeys(newText); + expect(page.homeString.getText()).toContain(newText); + }); + it('Displays a table above a certain size', function () { + browser.driver.manage().window().setSize(width, height); + expect(page.type.getText()).toContain('Table'); + }); + it('Display a list below a certain size', function () { + browser.driver.manage().window().setSize(600, height); + expect(page.type.getText()).toContain('List'); + }); + it('Navigates to subpage', function () { + page.navSub.click(); + expect(page.heading.getText()).toContain('Subpage'); + }); + + afterEach(function () { + //clear alert box if open + browser.driver.switchTo().alert().then( + function (alert) { + alert.dismiss(); + }, + function (err) { + }); + }) + }); +})(); +(function () { + var Page = function () { + this.get = function () { + browser.get('http://localhost:8000/subpage'); + browser.waitForAngular(); + }; + this.heading = element(by.css('.content-heading')); + this.template = element(by.css('.template')); + this.transclude = element(by.css('.transclude')); + this.navHome = element(by.css('.home-nav')); + } + + describe('reStart-Angular: Subpage', function () { + var page; + beforeEach(function () { + page = new Page(); + page.get(); + }) + + it('Should load the sub page', function () { + expect(page.heading.getText()).toContain('Subpage'); + }); + it('has a template included by a directive', function () { + expect(page.template.getText()).toContain('template'); + }); + it('has a template that was transcluded', function () { + expect(page.transclude.getText()).toContain('transclude'); + }); + it('Navigates to the home page', function () { + page.navHome.click(); + expect(page.heading.getText()).toContain('Home'); + }); + + }); +})(); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhvbWUuc3BlYy5qcyIsInN1YnBhZ2Uuc3BlYy5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNwRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJlMmUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gKCkge1xyXG4gICAgdmFyIFBhZ2UgPSBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgdGhpcy5nZXQgPSBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGJyb3dzZXIuZ2V0KCdodHRwOi8vbG9jYWxob3N0OjgwMDAvJyk7XHJcbiAgICAgICAgICAgIGJyb3dzZXIud2FpdEZvckFuZ3VsYXIoKTtcclxuICAgICAgICB9O1xyXG4gICAgICAgIHRoaXMuZ3JlZXQgPSBlbGVtZW50KGJ5LmJ1dHRvblRleHQoXCJHcmVldFwiKSk7XHJcbiAgICAgICAgdGhpcy5oZWFkaW5nID0gZWxlbWVudChieS5jc3MoJy5jb250ZW50LWhlYWRpbmcnKSk7XHJcbiAgICAgICAgdGhpcy5ob21lU3RyaW5nID0gZWxlbWVudChieS5jc3MoJy5ob21lLXN0cmluZycpKTtcclxuICAgICAgICB0aGlzLnR5cGUgPSBlbGVtZW50KGJ5LmNzcygnLmRpc3BsYXknKSk7XHJcbiAgICAgICAgdGhpcy5uYW1lID0gZWxlbWVudChieS5tb2RlbCgnaG9tZS5uYW1lJykpO1xyXG4gICAgICAgIHRoaXMubmF2U3ViID0gZWxlbWVudChieS5jc3MoJy5zdWItbmF2JykpO1xyXG4gICAgfVxyXG4gICAgZGVzY3JpYmUoJ3JlU3RhcnQtQW5ndWxhcjogSG9tZScsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgcGFnZTtcclxuICAgICAgICB2YXIgd2lkdGggPSA4MDA7XHJcbiAgICAgICAgdmFyIGhlaWdodCA9IDYwMDtcclxuICAgICAgICB2YXIgbmV3VGV4dCA9IFwiQm9iXCJcclxuXHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIHBhZ2UgPSBuZXcgUGFnZSgpO1xyXG4gICAgICAgICAgICBwYWdlLmdldCgpO1xyXG4gICAgICAgICAgICBicm93c2VyLmRyaXZlci5tYW5hZ2UoKS53aW5kb3coKS5zZXRTaXplKHdpZHRoLCBoZWlnaHQpO1xyXG4gICAgICAgIH0pXHJcblxyXG4gICAgICAgIGl0KCdMb2FkcyB0aGUgaG9tZSBwYWdlJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBleHBlY3QocGFnZS5oZWFkaW5nLmdldFRleHQoKSkudG9Db250YWluKCdIb21lJyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgaXQoJ0dyZWV0cyBhIG5hbWUnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIHBhZ2UuZ3JlZXQuY2xpY2soKTtcclxuICAgICAgICAgICAgdmFyIGFsZXJ0RGlhbG9nID0gYnJvd3Nlci5kcml2ZXIuc3dpdGNoVG8oKS5hbGVydCgpO1xyXG4gICAgICAgICAgICBleHBlY3QoYWxlcnREaWFsb2cuZ2V0VGV4dCgpKS50b0NvbnRhaW4oXCJIZWxsbyxcIik7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgaXQoJ1VwZGF0ZXMgdGV4dCBpbiBhbGVydCBpZiBpbnB1dCBjaGFuZ2VzJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBwYWdlLm5hbWUuY2xlYXIoKTtcclxuICAgICAgICAgICAgcGFnZS5uYW1lLnNlbmRLZXlzKG5ld1RleHQpO1xyXG4gICAgICAgICAgICBwYWdlLmdyZWV0LmNsaWNrKCk7XHJcbiAgICAgICAgICAgIHZhciBhbGVydERpYWxvZyA9IGJyb3dzZXIuZHJpdmVyLnN3aXRjaFRvKCkuYWxlcnQoKTtcclxuICAgICAgICAgICAgZXhwZWN0KGFsZXJ0RGlhbG9nLmdldFRleHQoKSkudG9Db250YWluKG5ld1RleHQpO1xyXG4gICAgICAgIH0pO1xyXG4gICAgICAgIGl0KCdVcGRhdGVzIHRleHQgb24gcGFnZSBpZiBpbnB1dCBjaGFuZ2VzJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBwYWdlLm5hbWUuY2xlYXIoKTtcclxuICAgICAgICAgICAgcGFnZS5uYW1lLnNlbmRLZXlzKG5ld1RleHQpO1xyXG4gICAgICAgICAgICBleHBlY3QocGFnZS5ob21lU3RyaW5nLmdldFRleHQoKSkudG9Db250YWluKG5ld1RleHQpO1xyXG4gICAgICAgIH0pO1xyXG4gICAgICAgIGl0KCdEaXNwbGF5cyBhIHRhYmxlIGFib3ZlIGEgY2VydGFpbiBzaXplJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBicm93c2VyLmRyaXZlci5tYW5hZ2UoKS53aW5kb3coKS5zZXRTaXplKHdpZHRoLCBoZWlnaHQpO1xyXG4gICAgICAgICAgICBleHBlY3QocGFnZS50eXBlLmdldFRleHQoKSkudG9Db250YWluKCdUYWJsZScpO1xyXG4gICAgICAgIH0pO1xyXG4gICAgICAgIGl0KCdEaXNwbGF5IGEgbGlzdCBiZWxvdyBhIGNlcnRhaW4gc2l6ZScsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgYnJvd3Nlci5kcml2ZXIubWFuYWdlKCkud2luZG93KCkuc2V0U2l6ZSg2MDAsIGhlaWdodCk7XHJcbiAgICAgICAgICAgIGV4cGVjdChwYWdlLnR5cGUuZ2V0VGV4dCgpKS50b0NvbnRhaW4oJ0xpc3QnKTtcclxuICAgICAgICB9KTtcclxuICAgICAgICBpdCgnTmF2aWdhdGVzIHRvIHN1YnBhZ2UnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIHBhZ2UubmF2U3ViLmNsaWNrKCk7XHJcbiAgICAgICAgICAgIGV4cGVjdChwYWdlLmhlYWRpbmcuZ2V0VGV4dCgpKS50b0NvbnRhaW4oJ1N1YnBhZ2UnKTtcclxuICAgICAgICB9KTtcclxuXHJcbiAgICAgICAgYWZ0ZXJFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgLy9jbGVhciBhbGVydCBib3ggaWYgb3BlblxyXG4gICAgICAgICAgICBicm93c2VyLmRyaXZlci5zd2l0Y2hUbygpLmFsZXJ0KCkudGhlbihcclxuICAgICAgICAgICAgZnVuY3Rpb24gKGFsZXJ0KSB7XHJcbiAgICAgICAgICAgICAgICBhbGVydC5kaXNtaXNzKCk7XHJcbiAgICAgICAgICAgIH0sXHJcbiAgICAgICAgICAgIGZ1bmN0aW9uIChlcnIpIHtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSlcclxuICAgIH0pO1xyXG59KSgpOyIsIihmdW5jdGlvbiAoKSB7XHJcbiAgICB2YXIgUGFnZSA9IGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB0aGlzLmdldCA9IGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgYnJvd3Nlci5nZXQoJ2h0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9zdWJwYWdlJyk7XHJcbiAgICAgICAgICAgIGJyb3dzZXIud2FpdEZvckFuZ3VsYXIoKTtcclxuICAgICAgICB9O1xyXG4gICAgICAgIHRoaXMuaGVhZGluZyA9IGVsZW1lbnQoYnkuY3NzKCcuY29udGVudC1oZWFkaW5nJykpO1xyXG4gICAgICAgIHRoaXMudGVtcGxhdGUgPSBlbGVtZW50KGJ5LmNzcygnLnRlbXBsYXRlJykpO1xyXG4gICAgICAgIHRoaXMudHJhbnNjbHVkZSA9IGVsZW1lbnQoYnkuY3NzKCcudHJhbnNjbHVkZScpKTtcclxuICAgICAgICB0aGlzLm5hdkhvbWUgPSBlbGVtZW50KGJ5LmNzcygnLmhvbWUtbmF2JykpO1xyXG4gICAgfVxyXG5cclxuICAgIGRlc2NyaWJlKCdyZVN0YXJ0LUFuZ3VsYXI6IFN1YnBhZ2UnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgdmFyIHBhZ2U7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIHBhZ2UgPSBuZXcgUGFnZSgpO1xyXG4gICAgICAgICAgICBwYWdlLmdldCgpO1xyXG4gICAgICAgIH0pXHJcblxyXG4gICAgICAgIGl0KCdTaG91bGQgbG9hZCB0aGUgc3ViIHBhZ2UnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGV4cGVjdChwYWdlLmhlYWRpbmcuZ2V0VGV4dCgpKS50b0NvbnRhaW4oJ1N1YnBhZ2UnKTtcclxuICAgICAgICB9KTtcclxuICAgICAgICBpdCgnaGFzIGEgdGVtcGxhdGUgaW5jbHVkZWQgYnkgYSBkaXJlY3RpdmUnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGV4cGVjdChwYWdlLnRlbXBsYXRlLmdldFRleHQoKSkudG9Db250YWluKCd0ZW1wbGF0ZScpO1xyXG4gICAgICAgIH0pO1xyXG4gICAgICAgIGl0KCdoYXMgYSB0ZW1wbGF0ZSB0aGF0IHdhcyB0cmFuc2NsdWRlZCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgZXhwZWN0KHBhZ2UudHJhbnNjbHVkZS5nZXRUZXh0KCkpLnRvQ29udGFpbigndHJhbnNjbHVkZScpO1xyXG4gICAgICAgIH0pO1xyXG4gICAgICAgIGl0KCdOYXZpZ2F0ZXMgdG8gdGhlIGhvbWUgcGFnZScsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgcGFnZS5uYXZIb21lLmNsaWNrKCk7XHJcbiAgICAgICAgICAgIGV4cGVjdChwYWdlLmhlYWRpbmcuZ2V0VGV4dCgpKS50b0NvbnRhaW4oJ0hvbWUnKTtcclxuICAgICAgICB9KTtcclxuXHJcbiAgICB9KTtcclxufSkoKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= diff --git a/tests/integration/home.spec.js b/tests/integration/home.spec.js new file mode 100644 index 0000000..b4efde1 --- /dev/null +++ b/tests/integration/home.spec.js @@ -0,0 +1,69 @@ +(function () { + var Page = function () { + this.get = function () { + browser.get('http://localhost:8000/'); + browser.waitForAngular(); + }; + this.greet = element(by.buttonText("Greet")); + this.heading = element(by.css('.content-heading')); + this.homeString = element(by.css('.home-string')); + this.type = element(by.css('.display')); + this.name = element(by.model('home.name')); + this.navSub = element(by.css('.sub-nav')); + } + describe('reStart-Angular: Home', function () { + var page; + var width = 800; + var height = 600; + var newText = "Bob" + + beforeEach(function () { + page = new Page(); + page.get(); + browser.driver.manage().window().setSize(width, height); + }) + + it('Loads the home page', function () { + expect(page.heading.getText()).toContain('Home'); + }); + it('Greets a name', function () { + page.greet.click(); + var alertDialog = browser.driver.switchTo().alert(); + expect(alertDialog.getText()).toContain("Hello,"); + }); + it('Updates text in alert if input changes', function () { + page.name.clear(); + page.name.sendKeys(newText); + page.greet.click(); + var alertDialog = browser.driver.switchTo().alert(); + expect(alertDialog.getText()).toContain(newText); + }); + it('Updates text on page if input changes', function () { + page.name.clear(); + page.name.sendKeys(newText); + expect(page.homeString.getText()).toContain(newText); + }); + it('Displays a table above a certain size', function () { + browser.driver.manage().window().setSize(width, height); + expect(page.type.getText()).toContain('Table'); + }); + it('Display a list below a certain size', function () { + browser.driver.manage().window().setSize(600, height); + expect(page.type.getText()).toContain('List'); + }); + it('Navigates to subpage', function () { + page.navSub.click(); + expect(page.heading.getText()).toContain('Subpage'); + }); + + afterEach(function () { + //clear alert box if open + browser.driver.switchTo().alert().then( + function (alert) { + alert.dismiss(); + }, + function (err) { + }); + }) + }); +})(); \ No newline at end of file diff --git a/tests/integration/subpage.spec.js b/tests/integration/subpage.spec.js new file mode 100644 index 0000000..56cc5a8 --- /dev/null +++ b/tests/integration/subpage.spec.js @@ -0,0 +1,35 @@ +(function () { + var Page = function () { + this.get = function () { + browser.get('http://localhost:8000/subpage'); + browser.waitForAngular(); + }; + this.heading = element(by.css('.content-heading')); + this.template = element(by.css('.template')); + this.transclude = element(by.css('.transclude')); + this.navHome = element(by.css('.home-nav')); + } + + describe('reStart-Angular: Subpage', function () { + var page; + beforeEach(function () { + page = new Page(); + page.get(); + }) + + it('Should load the sub page', function () { + expect(page.heading.getText()).toContain('Subpage'); + }); + it('has a template included by a directive', function () { + expect(page.template.getText()).toContain('template'); + }); + it('has a template that was transcluded', function () { + expect(page.transclude.getText()).toContain('transclude'); + }); + it('Navigates to the home page', function () { + page.navHome.click(); + expect(page.heading.getText()).toContain('Home'); + }); + + }); +})(); \ No newline at end of file From 17d42283f8f7864a690d6acd25cc553af2503bab Mon Sep 17 00:00:00 2001 From: Damian Strong <thewhitewolf079@gmail.com> Date: Thu, 10 Dec 2015 10:05:14 -0500 Subject: [PATCH 2/4] Updated gulpfile and tests to match new version of reStart --- Gulpfile.js | 20 +- src/assets/js/scripts.js | 4 +- src/assets/js/vendor/vendor.js | 9905 +++++++++++++---- src/reStart-app/core/Page.ctrl.js | 5 + .../modules/header/navControl.dir.js | 3 +- .../pages/error404/Error404.ctrl.js | 6 +- src/reStart-app/pages/home/Home.ctrl.js | 17 +- src/reStart-app/reStart-app.js | 1421 +-- 8 files changed, 8591 insertions(+), 2790 deletions(-) diff --git a/Gulpfile.js b/Gulpfile.js index d071526..db07116 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -14,7 +14,7 @@ var gulp = require('gulp'), Server = require('karma').Server, fpath = require('path'), child_process = require('child_process'); - + eslint = require('gulp-eslint'); /** * File paths * @@ -62,8 +62,10 @@ var jsModuleFile = path.jsAngular.src + 'core/app-setup/app.module.js'; var files = {}; +files.e2e = [path.e2e.src]; +files.specs= [path.jsAngular.src + '**/*.spec.js','!'+path.jsAngular.src+'reStart-app.spec.js']; files.scssSrc = [path.css.src + '**/*.scss']; -files.jsUserSrcAngular = [path.jsAngular.src + '**/*.js', '!' + path.jsAngular.src + jsAngularScript]; +files.jsUserSrcAngular = [path.jsAngular.src + '**/*.js', '!' + path.jsAngular.src + jsAngularScript , '!'+path.jsAngular.src + '**/*.spec.js']; files.jsUserSrcAssets = [path.js.src + '**/*.js', '!' + path.js.src + jsUserScript, '!' + path.js.src + 'vendor/*']; files.jsUserSrcAll = files.jsUserSrcAngular.concat(files.jsUserSrcAssets); files.jsVendorSrc = [path.jsVendor.src + 'jquery.js', path.jsVendor.src + 'angular.js', path.jsVendor.src + '**/*.js', '!' + path.jsVendor.src + 'modernizr.min.js', '!' + path.jsVendor.src + 'vendor.js']; @@ -202,7 +204,7 @@ function jsAngular() { * Save */ function tests() { - return gulp.src([path.jsAngular.src + '**/*.spec.js','!'+path.jsAngular.src+'reStart-app.spec.js']) + return gulp.src(files.specs) .pipe(sourcemaps.init()) .pipe(concat('reStart-app.spec.js')) .pipe(sourcemaps.write()) @@ -218,7 +220,7 @@ function tests() { * Save */ function e2e() { - return gulp.src([path.e2e.src + '**/*.spec.js']) + return gulp.src(files.e2e) .pipe(sourcemaps.init()) .pipe(concat('e2e.js')) .pipe(sourcemaps.write()) @@ -305,6 +307,12 @@ function defaultTask() { // compile JS Angular files gulp.watch(files.jsUserSrcAngular, ['jsAngular']); + + //compile jasmine unit tests + gulp.watch(files.specs, ['tests']); + + //compile protractor tests + gulp.watch(files.e2e, ['e2e']); } /** * Gulp tasks @@ -319,7 +327,7 @@ gulp.task('tests', tests); gulp.task('e2e', e2e); gulp.task('serve', serve); //Start karma after files have been rebuilt and test compiled -gulp.task('karma',['js','jsVendor','jsAngular','tests'],karma); +gulp.task('karma',['jsUser','jsVendor','jsAngular','tests'],karma); //Start protractor after karma runs gulp.task('protractor',['e2e','serve'],e2eTests); @@ -332,4 +340,4 @@ gulp.task('protractor',['e2e','serve'],e2eTests); */ gulp.task('js',['jsVendor', 'jsValidate', 'jsUser', 'jsAngular']); -gulp.task('default',['serve', 'styles', 'js', 'karma', 'protractor'], defaultTask); \ No newline at end of file +gulp.task('default',['serve', 'styles', 'js'], defaultTask); \ No newline at end of file diff --git a/src/assets/js/scripts.js b/src/assets/js/scripts.js index 84f0f1e..9837896 100644 --- a/src/assets/js/scripts.js +++ b/src/assets/js/scripts.js @@ -36,5 +36,5 @@ window.helpers = (function() { } } } -}()); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhlbHBlcnMuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InNjcmlwdHMuanMiLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XHJcblxyXG53aW5kb3cuaGVscGVycyA9IChmdW5jdGlvbigpIHtcclxuXHJcblx0aW5pdCgpO1xyXG5cclxuXHQvKipcclxuXHQgKiBmdW5jdGlvbiBpbml0KClcclxuXHQgKlxyXG5cdCAqIEluaXRpYWxpemUgcHVibGljIHdpbmRvdy5oZWxwZXJzIGZ1bmN0aW9uc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGluaXQoKSB7XHJcblx0XHRmaXhCcm93c2VycygpO1xyXG5cdH1cclxuXHJcblx0LyoqXHJcblx0ICogZnVuY3Rpb24gZml4QnJvd3NlcnMoKVxyXG5cdCAqXHJcblx0ICogRml4IGJyb3dzZXIgd2VpcmRuZXNzXHJcblx0ICogQ29ycmVjdCBNb2Rlcm5penIgYnVnc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGZpeEJyb3dzZXJzKCkge1xyXG5cdFx0dmFyIHVhID0gbmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpO1xyXG5cdFx0dmFyIGNocm9tZSA9IHVhLmxhc3RJbmRleE9mKCdjaHJvbWUvJykgPiAwO1xyXG5cdFx0dmFyIGNocm9tZXZlcnNpb24gPSBudWxsO1xyXG5cdFx0dmFyICRodG1sID0gJCgnaHRtbCcpO1xyXG5cdFx0XHJcblx0XHQvLyBNb2Rlcm5penIgMiBidWc6IENocm9tZSBvbiBXaW5kb3dzIDggZ2l2ZXMgYSBmYWxzZSBuZWdhdGl2ZSBmb3IgdHJhbnNmb3JtczNkIHN1cHBvcnRcclxuXHRcdC8vIEdvb2dsZSBkb2VzIG5vdCBwbGFuIHRvIGZpeCB0aGlzOyBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9MTI5MDA0XHJcblx0XHRpZiAoY2hyb21lKSB7XHJcblx0XHRcdGNocm9tZXZlcnNpb24gPSB1YS5zdWJzdHIodWEubGFzdEluZGV4T2YoJ2Nocm9tZS8nKSArIDcsIDIpO1xyXG5cdFx0XHRpZiAoY2hyb21ldmVyc2lvbiA+PSAxMiAmJiAkaHRtbC5oYXNDbGFzcygnbm8tY3NzdHJhbnNmb3JtczNkJykpIHtcclxuXHRcdFx0XHQkaHRtbFxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduby1jc3N0cmFuc2Zvcm1zM2QnKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCdjc3N0cmFuc2Zvcm1zM2QnKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= \ No newline at end of file +}()); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImhlbHBlcnMuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InNjcmlwdHMuanMiLCJzb3VyY2VzQ29udGVudCI6WyIndXNlIHN0cmljdCc7XHJcblxyXG53aW5kb3cuaGVscGVycyA9IChmdW5jdGlvbigpIHtcclxuXHJcblx0aW5pdCgpO1xyXG5cclxuXHQvKipcclxuXHQgKiBmdW5jdGlvbiBpbml0KClcclxuXHQgKlxyXG5cdCAqIEluaXRpYWxpemUgcHVibGljIHdpbmRvdy5oZWxwZXJzIGZ1bmN0aW9uc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGluaXQoKSB7XHJcblx0XHRmaXhCcm93c2VycygpO1xyXG5cdH1cclxuXHJcblx0LyoqXHJcblx0ICogZnVuY3Rpb24gZml4QnJvd3NlcnMoKVxyXG5cdCAqXHJcblx0ICogRml4IGJyb3dzZXIgd2VpcmRuZXNzXHJcblx0ICogQ29ycmVjdCBNb2Rlcm5penIgYnVnc1xyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGZpeEJyb3dzZXJzKCkge1xyXG5cdFx0dmFyIHVhID0gbmF2aWdhdG9yLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpO1xyXG5cdFx0dmFyIGNocm9tZSA9IHVhLmxhc3RJbmRleE9mKCdjaHJvbWUvJykgPiAwO1xyXG5cdFx0dmFyIGNocm9tZXZlcnNpb24gPSBudWxsO1xyXG5cdFx0dmFyICRodG1sID0gJCgnaHRtbCcpO1xyXG5cdFx0XHJcblx0XHQvLyBNb2Rlcm5penIgMiBidWc6IENocm9tZSBvbiBXaW5kb3dzIDggZ2l2ZXMgYSBmYWxzZSBuZWdhdGl2ZSBmb3IgdHJhbnNmb3JtczNkIHN1cHBvcnRcclxuXHRcdC8vIEdvb2dsZSBkb2VzIG5vdCBwbGFuIHRvIGZpeCB0aGlzOyBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL2Nocm9taXVtL2lzc3Vlcy9kZXRhaWw/aWQ9MTI5MDA0XHJcblx0XHRpZiAoY2hyb21lKSB7XHJcblx0XHRcdGNocm9tZXZlcnNpb24gPSB1YS5zdWJzdHIodWEubGFzdEluZGV4T2YoJ2Nocm9tZS8nKSArIDcsIDIpO1xyXG5cdFx0XHRpZiAoY2hyb21ldmVyc2lvbiA+PSAxMiAmJiAkaHRtbC5oYXNDbGFzcygnbm8tY3NzdHJhbnNmb3JtczNkJykpIHtcclxuXHRcdFx0XHQkaHRtbFxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduby1jc3N0cmFuc2Zvcm1zM2QnKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCdjc3N0cmFuc2Zvcm1zM2QnKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= diff --git a/src/assets/js/vendor/vendor.js b/src/assets/js/vendor/vendor.js index a290e9c..a3ca9d3 100644 --- a/src/assets/js/vendor/vendor.js +++ b/src/assets/js/vendor/vendor.js @@ -38228,757 +38228,3227 @@ !window.angular.$$csp().noInlineStyle && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>'); /** - * @license AngularJS v1.4.8 + * @license AngularJS v1.4.7 * (c) 2010-2015 Google, Inc. http://angularjs.org * License: MIT */ -(function(window, angular, undefined) {'use strict'; +(function (window, angular, undefined) { - var $resourceMinErr = angular.$$minErr('$resource'); + 'use strict'; -// Helper functions and regex to lookup a dotted path on an object -// stopping at undefined/null. The path must be composed of ASCII -// identifiers (just like $parse) - var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/; + /** + * @ngdoc object + * @name angular.mock + * @description + * + * Namespace from 'angular-mocks.js' which contains testing related code. + */ + angular.mock = {}; - function isValidDottedPath(path) { - return (path != null && path !== '' && path !== 'hasOwnProperty' && - MEMBER_NAME_REGEX.test('.' + path)); - } + /** + * ! This is a private undocumented service ! + * + * @name $browser + * + * @description + * This service is a mock implementation of {@link ng.$browser}. It provides fake + * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, + * cookies, etc... + * + * The api of this service is the same as that of the real {@link ng.$browser $browser}, except + * that there are several helper methods available which can be used in tests. + */ + angular.mock.$BrowserProvider = function () { + this.$get = function () { + return new angular.mock.$Browser(); + }; + }; - function lookupDottedPath(obj, path) { - if (!isValidDottedPath(path)) { - throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); - } - var keys = path.split('.'); - for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) { - var key = keys[i]; - obj = (obj !== null) ? obj[key] : undefined; - } - return obj; - } + angular.mock.$Browser = function () { + var self = this; - /** - * Create a shallow copy of an object and clear other fields from the destination - */ - function shallowClearAndCopy(src, dst) { - dst = dst || {}; + this.isMock = true; + self.$$url = "http://server/"; + self.$$lastUrl = self.$$url; // used by url polling fn + self.pollFns = []; - angular.forEach(dst, function(value, key) { - delete dst[key]; - }); + // TODO(vojta): remove this temporary api + self.$$completeOutstandingRequest = angular.noop; + self.$$incOutstandingRequestCount = angular.noop; - for (var key in src) { - if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - return dst; - } + // register url polling fn - /** - * @ngdoc module - * @name ngResource - * @description - * - * # ngResource - * - * The `ngResource` module provides interaction support with RESTful services - * via the $resource service. - * - * - * <div doc-module-components="ngResource"></div> - * - * See {@link ngResource.$resource `$resource`} for usage. - */ + self.onUrlChange = function (listener) { + self.pollFns.push( + function () { + if (self.$$lastUrl !== self.$$url || self.$$state !== self.$$lastState) { + self.$$lastUrl = self.$$url; + self.$$lastState = self.$$state; + listener(self.$$url, self.$$state); + } + } + ); - /** - * @ngdoc service - * @name $resource - * @requires $http - * - * @description - * A factory which creates a resource object that lets you interact with - * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. - * - * The returned resource object has action methods which provide high-level behaviors without - * the need to interact with the low level {@link ng.$http $http} service. - * - * Requires the {@link ngResource `ngResource`} module to be installed. - * - * By default, trailing slashes will be stripped from the calculated URLs, - * which can pose problems with server backends that do not expect that - * behavior. This can be disabled by configuring the `$resourceProvider` like - * this: - * - * ```js - app.config(['$resourceProvider', function($resourceProvider) { - // Don't strip trailing slashes from calculated URLs - $resourceProvider.defaults.stripTrailingSlashes = false; - }]); - * ``` - * - * @param {string} url A parameterized URL template with parameters prefixed by `:` as in - * `/user/:username`. If you are using a URL with a port number (e.g. - * `http://example.com:8080/api`), it will be respected. - * - * If you are using a url with a suffix, just add the suffix, like this: - * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` - * or even `$resource('http://example.com/resource/:resource_id.:format')` - * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be - * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you - * can escape it with `/\.`. - * - * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in - * `actions` methods. If any of the parameter value is a function, it will be executed every time - * when a param value needs to be obtained for a request (unless the param was overridden). - * - * Each key value in the parameter object is first bound to url template if present and then any - * excess keys are appended to the url search query after the `?`. - * - * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in - * URL `/path/greet?salutation=Hello`. - * - * If the parameter value is prefixed with `@` then the value for that parameter will be extracted - * from the corresponding property on the `data` object (provided when calling an action method). For - * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam` - * will be `data.someProp`. - * - * @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend - * the default set of resource actions. The declaration should be created in the format of {@link - * ng.$http#usage $http.config}: - * - * {action1: {method:?, params:?, isArray:?, headers:?, ...}, - * action2: {method:?, params:?, isArray:?, headers:?, ...}, - * ...} - * - * Where: - * - * - **`action`** – {string} – The name of action. This name becomes the name of the method on - * your resource object. - * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, - * `DELETE`, `JSONP`, etc). - * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of - * the parameter value is a function, it will be executed every time when a param value needs to - * be obtained for a request (unless the param was overridden). - * - **`url`** – {string} – action specific `url` override. The url templating is supported just - * like for the resource-level urls. - * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, - * see `returns` section. - * - **`transformRequest`** – - * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – - * transform function or an array of such functions. The transform function takes the http - * request body and headers and returns its transformed (typically serialized) version. - * By default, transformRequest will contain one function that checks if the request data is - * an object and serializes to using `angular.toJson`. To prevent this behavior, set - * `transformRequest` to an empty array: `transformRequest: []` - * - **`transformResponse`** – - * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – - * transform function or an array of such functions. The transform function takes the http - * response body and headers and returns its transformed (typically deserialized) version. - * By default, transformResponse will contain one function that checks if the response looks like - * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set - * `transformResponse` to an empty array: `transformResponse: []` - * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the - * GET request, otherwise if a cache instance built with - * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for - * caching. - * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that - * should abort the request when resolved. - * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the - * XHR object. See - * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) - * for more information. - * - **`responseType`** - `{string}` - see - * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). - * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - - * `response` and `responseError`. Both `response` and `responseError` interceptors get called - * with `http response` object. See {@link ng.$http $http interceptors}. - * - * @param {Object} options Hash with custom settings that should extend the - * default `$resourceProvider` behavior. The only supported option is - * - * Where: - * - * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing - * slashes from any calculated URL will be stripped. (Defaults to true.) - * - * @returns {Object} A resource "class" object with methods for the default set of resource actions - * optionally extended with custom `actions`. The default set contains these actions: - * ```js - * { 'get': {method:'GET'}, - * 'save': {method:'POST'}, - * 'query': {method:'GET', isArray:true}, - * 'remove': {method:'DELETE'}, - * 'delete': {method:'DELETE'} }; - * ``` - * - * Calling these methods invoke an {@link ng.$http} with the specified http method, - * destination and parameters. When the data is returned from the server then the object is an - * instance of the resource class. The actions `save`, `remove` and `delete` are available on it - * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, - * read, update, delete) on server-side data like this: - * ```js - * var User = $resource('/user/:userId', {userId:'@id'}); - * var user = User.get({userId:123}, function() { - * user.abc = true; - * user.$save(); - * }); - * ``` - * - * It is important to realize that invoking a $resource object method immediately returns an - * empty reference (object or array depending on `isArray`). Once the data is returned from the - * server the existing reference is populated with the actual data. This is a useful trick since - * usually the resource is assigned to a model which is then rendered by the view. Having an empty - * object results in no rendering, once the data arrives from the server then the object is - * populated with the data and the view automatically re-renders itself showing the new data. This - * means that in most cases one never has to write a callback function for the action methods. - * - * The action methods on the class object or instance object can be invoked with the following - * parameters: - * - * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` - * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` - * - non-GET instance actions: `instance.$action([parameters], [success], [error])` - * - * - * Success callback is called with (value, responseHeaders) arguments, where the value is - * the populated resource instance or collection object. The error callback is called - * with (httpResponse) argument. - * - * Class actions return empty instance (with additional properties below). - * Instance actions return promise of the action. - * - * The Resource instances and collection have these additional properties: - * - * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this - * instance or collection. - * - * On success, the promise is resolved with the same resource instance or collection object, - * updated with data from server. This makes it easy to use in - * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view - * rendering until the resource(s) are loaded. - * - * On failure, the promise is resolved with the {@link ng.$http http response} object, without - * the `resource` property. - * - * If an interceptor object was provided, the promise will instead be resolved with the value - * returned by the interceptor. - * - * - `$resolved`: `true` after first server interaction is completed (either with success or - * rejection), `false` before that. Knowing if the Resource has been resolved is useful in - * data-binding. - * - * @example - * - * # Credit card resource - * - * ```js - // Define CreditCard class - var CreditCard = $resource('/user/:userId/card/:cardId', - {userId:123, cardId:'@id'}, { - charge: {method:'POST', params:{charge:true}} - }); + return listener; + }; - // We can retrieve a collection from the server - var cards = CreditCard.query(function() { - // GET: /user/123/card - // server returns: [ {id:456, number:'1234', name:'Smith'} ]; + self.$$applicationDestroyed = angular.noop; + self.$$checkUrlChange = angular.noop; - var card = cards[0]; - // each item is an instance of CreditCard - expect(card instanceof CreditCard).toEqual(true); - card.name = "J. Smith"; - // non GET methods are mapped onto the instances - card.$save(); - // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} - // server returns: {id:456, number:'1234', name: 'J. Smith'}; + self.deferredFns = []; + self.deferredNextId = 0; - // our custom method is mapped as well. - card.$charge({amount:9.99}); - // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} - }); + self.defer = function (fn, delay) { + delay = delay || 0; + self.deferredFns.push({ time: (self.defer.now + delay), fn: fn, id: self.deferredNextId }); + self.deferredFns.sort(function (a, b) { return a.time - b.time; }); + return self.deferredNextId++; + }; - // we can create an instance as well - var newCard = new CreditCard({number:'0123'}); - newCard.name = "Mike Smith"; - newCard.$save(); - // POST: /user/123/card {number:'0123', name:'Mike Smith'} - // server returns: {id:789, number:'0123', name: 'Mike Smith'}; - expect(newCard.id).toEqual(789); - * ``` - * - * The object returned from this function execution is a resource "class" which has "static" method - * for each action in the definition. - * - * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and - * `headers`. - * When the data is returned from the server then the object is an instance of the resource type and - * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD - * operations (create, read, update, delete) on server-side data. - ```js - var User = $resource('/user/:userId', {userId:'@id'}); - User.get({userId:123}, function(user) { - user.abc = true; - user.$save(); - }); - ``` - * - * It's worth noting that the success callback for `get`, `query` and other methods gets passed - * in the response that came from the server as well as $http header getter function, so one - * could rewrite the above example and get access to http headers as: - * - ```js - var User = $resource('/user/:userId', {userId:'@id'}); - User.get({userId:123}, function(u, getResponseHeaders){ - u.abc = true; - u.$save(function(u, putResponseHeaders) { - //u => saved user object - //putResponseHeaders => $http header getter - }); - }); - ``` - * - * You can also access the raw `$http` promise via the `$promise` property on the object returned - * - ``` - var User = $resource('/user/:userId', {userId:'@id'}); - User.get({userId:123}) - .$promise.then(function(user) { - $scope.user = user; - }); - ``` + /** + * @name $browser#defer.now + * + * @description + * Current milliseconds mock time. + */ + self.defer.now = 0; - * # Creating a custom 'PUT' request - * In this example we create a custom method on our resource to make a PUT request - * ```js - * var app = angular.module('app', ['ngResource', 'ngRoute']); - * - * // Some APIs expect a PUT request in the format URL/object/ID - * // Here we are creating an 'update' method - * app.factory('Notes', ['$resource', function($resource) { - * return $resource('/notes/:id', null, - * { - * 'update': { method:'PUT' } - * }); - * }]); - * - * // In our controller we get the ID from the URL using ngRoute and $routeParams - * // We pass in $routeParams and our Notes factory along with $scope - * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', - function($scope, $routeParams, Notes) { - * // First get a note object from the factory - * var note = Notes.get({ id:$routeParams.id }); - * $id = note.id; - * - * // Now call update passing in the ID first then the object you are updating - * Notes.update({ id:$id }, note); - * - * // This will PUT /notes/ID with the note object in the request payload - * }]); - * ``` - */ - angular.module('ngResource', ['ng']). - provider('$resource', function() { - var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/; - var provider = this; - this.defaults = { - // Strip slashes by default - stripTrailingSlashes: true, + self.defer.cancel = function (deferId) { + var fnIndex; - // Default actions configuration - actions: { - 'get': {method: 'GET'}, - 'save': {method: 'POST'}, - 'query': {method: 'GET', isArray: true}, - 'remove': {method: 'DELETE'}, - 'delete': {method: 'DELETE'} - } - }; + angular.forEach(self.deferredFns, function (fn, index) { + if (fn.id === deferId) fnIndex = index; + }); - this.$get = ['$http', '$q', function($http, $q) { + if (angular.isDefined(fnIndex)) { + self.deferredFns.splice(fnIndex, 1); + return true; + } - var noop = angular.noop, - forEach = angular.forEach, - extend = angular.extend, - copy = angular.copy, - isFunction = angular.isFunction; + return false; + }; - /** - * We need our custom method because encodeURIComponent is too aggressive and doesn't follow - * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set - * (pchar) allowed in path segments: - * segment = *pchar - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * pct-encoded = "%" HEXDIG HEXDIG - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ - function encodeUriSegment(val) { - return encodeUriQuery(val, true). - replace(/%26/gi, '&'). - replace(/%3D/gi, '='). - replace(/%2B/gi, '+'); - } + /** + * @name $browser#defer.flush + * + * @description + * Flushes all pending requests and executes the defer callbacks. + * + * @param {number=} number of milliseconds to flush. See {@link #defer.now} + */ + self.defer.flush = function (delay) { + if (angular.isDefined(delay)) { + self.defer.now += delay; + } else { + if (self.deferredFns.length) { + self.defer.now = self.deferredFns[self.deferredFns.length - 1].time; + } else { + throw new Error('No deferred tasks to be flushed'); + } + } - /** - * This method is intended for encoding *key* or *value* parts of query component. We need a - * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't - * have to be encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * pct-encoded = "%" HEXDIG HEXDIG - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ - function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val). - replace(/%40/gi, '@'). - replace(/%3A/gi, ':'). - replace(/%24/g, '$'). - replace(/%2C/gi, ','). - replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); - } + while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { + self.deferredFns.shift().fn(); + } + }; - function Route(template, defaults) { - this.template = template; - this.defaults = extend({}, provider.defaults, defaults); - this.urlParams = {}; - } + self.$$baseHref = '/'; + self.baseHref = function () { + return this.$$baseHref; + }; + }; + angular.mock.$Browser.prototype = { + + /** + * @name $browser#poll + * + * @description + * run all fns in pollFns + */ + poll: function poll() { + angular.forEach(this.pollFns, function (pollFn) { + pollFn(); + }); + }, - Route.prototype = { - setUrlParams: function(config, params, actionUrl) { - var self = this, - url = actionUrl || self.template, - val, - encodedVal, - protocolAndDomain = ''; + url: function (url, replace, state) { + if (angular.isUndefined(state)) { + state = null; + } + if (url) { + this.$$url = url; + // Native pushState serializes & copies the object; simulate it. + this.$$state = angular.copy(state); + return this; + } - var urlParams = self.urlParams = {}; - forEach(url.split(/\W/), function(param) { - if (param === 'hasOwnProperty') { - throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); - } - if (!(new RegExp("^\\d+$").test(param)) && param && - (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { - urlParams[param] = true; - } - }); - url = url.replace(/\\:/g, ':'); - url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) { - protocolAndDomain = match; - return ''; - }); + return this.$$url; + }, - params = params || {}; - forEach(self.urlParams, function(_, urlParam) { - val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; - if (angular.isDefined(val) && val !== null) { - encodedVal = encodeUriSegment(val); - url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { - return encodedVal + p1; - }); - } else { - url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, - leadingSlashes, tail) { - if (tail.charAt(0) == '/') { - return tail; - } else { - return leadingSlashes + tail; - } - }); - } - }); + state: function () { + return this.$$state; + }, - // strip trailing slashes and set the url (unless this behavior is specifically disabled) - if (self.defaults.stripTrailingSlashes) { - url = url.replace(/\/+$/, '') || '/'; - } + notifyWhenNoOutstandingRequests: function (fn) { + fn(); + } + }; - // then replace collapse `/.` if found in the last URL path segment before the query - // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` - url = url.replace(/\/\.(?=\w+($|\?))/, '.'); - // replace escaped `/\.` with `/.` - config.url = protocolAndDomain + url.replace(/\/\\\./, '/.'); + /** + * @ngdoc provider + * @name $exceptionHandlerProvider + * + * @description + * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors + * passed to the `$exceptionHandler`. + */ + + /** + * @ngdoc service + * @name $exceptionHandler + * + * @description + * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed + * to it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration + * information. + * + * + * ```js + * describe('$exceptionHandlerProvider', function() { + * + * it('should capture log messages and exceptions', function() { + * + * module(function($exceptionHandlerProvider) { + * $exceptionHandlerProvider.mode('log'); + * }); + * + * inject(function($log, $exceptionHandler, $timeout) { + * $timeout(function() { $log.log(1); }); + * $timeout(function() { $log.log(2); throw 'banana peel'; }); + * $timeout(function() { $log.log(3); }); + * expect($exceptionHandler.errors).toEqual([]); + * expect($log.assertEmpty()); + * $timeout.flush(); + * expect($exceptionHandler.errors).toEqual(['banana peel']); + * expect($log.log.logs).toEqual([[1], [2], [3]]); + * }); + * }); + * }); + * ``` + */ + + angular.mock.$ExceptionHandlerProvider = function () { + var handler; + + /** + * @ngdoc method + * @name $exceptionHandlerProvider#mode + * + * @description + * Sets the logging mode. + * + * @param {string} mode Mode of operation, defaults to `rethrow`. + * + * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` + * mode stores an array of errors in `$exceptionHandler.errors`, to allow later + * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and + * {@link ngMock.$log#reset reset()} + * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there + * is a bug in the application or test, so this mock will make these tests fail. + * For any implementations that expect exceptions to be thrown, the `rethrow` mode + * will also maintain a log of thrown errors. + */ + this.mode = function (mode) { + + switch (mode) { + case 'log': + case 'rethrow': + var errors = []; + handler = function (e) { + if (arguments.length == 1) { + errors.push(e); + } else { + errors.push([].slice.call(arguments, 0)); + } + if (mode === "rethrow") { + throw e; + } + }; + handler.errors = errors; + break; + default: + throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); + } + }; - // set params - delegate param encoding to $http - forEach(params, function(value, key) { - if (!self.urlParams[key]) { - config.params = config.params || {}; - config.params[key] = value; - } - }); - } - }; + this.$get = function () { + return handler; + }; + this.mode('rethrow'); + }; - function resourceFactory(url, paramDefaults, actions, options) { - var route = new Route(url, options); - actions = extend({}, provider.defaults.actions, actions); + /** + * @ngdoc service + * @name $log + * + * @description + * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays + * (one array per logging level). These arrays are exposed as `logs` property of each of the + * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. + * + */ + angular.mock.$LogProvider = function () { + var debug = true; - function extractParams(data, actionParams) { - var ids = {}; - actionParams = extend({}, paramDefaults, actionParams); - forEach(actionParams, function(value, key) { - if (isFunction(value)) { value = value(); } - ids[key] = value && value.charAt && value.charAt(0) == '@' ? - lookupDottedPath(data, value.substr(1)) : value; - }); - return ids; - } + function concat(array1, array2, index) { + return array1.concat(Array.prototype.slice.call(array2, index)); + } - function defaultResponseInterceptor(response) { - return response.resource; - } + this.debugEnabled = function (flag) { + if (angular.isDefined(flag)) { + debug = flag; + return this; + } else { + return debug; + } + }; - function Resource(value) { - shallowClearAndCopy(value || {}, this); - } + this.$get = function () { + var $log = { + log: function () { $log.log.logs.push(concat([], arguments, 0)); }, + warn: function () { $log.warn.logs.push(concat([], arguments, 0)); }, + info: function () { $log.info.logs.push(concat([], arguments, 0)); }, + error: function () { $log.error.logs.push(concat([], arguments, 0)); }, + debug: function () { + if (debug) { + $log.debug.logs.push(concat([], arguments, 0)); + } + } + }; - Resource.prototype.toJSON = function() { - var data = extend({}, this); - delete data.$promise; - delete data.$resolved; - return data; - }; + /** + * @ngdoc method + * @name $log#reset + * + * @description + * Reset all of the logging arrays to empty. + */ + $log.reset = function () { + /** + * @ngdoc property + * @name $log#log.logs + * + * @description + * Array of messages logged using {@link ng.$log#log `log()`}. + * + * @example + * ```js + * $log.log('Some Log'); + * var first = $log.log.logs.unshift(); + * ``` + */ + $log.log.logs = []; + /** + * @ngdoc property + * @name $log#info.logs + * + * @description + * Array of messages logged using {@link ng.$log#info `info()`}. + * + * @example + * ```js + * $log.info('Some Info'); + * var first = $log.info.logs.unshift(); + * ``` + */ + $log.info.logs = []; + /** + * @ngdoc property + * @name $log#warn.logs + * + * @description + * Array of messages logged using {@link ng.$log#warn `warn()`}. + * + * @example + * ```js + * $log.warn('Some Warning'); + * var first = $log.warn.logs.unshift(); + * ``` + */ + $log.warn.logs = []; + /** + * @ngdoc property + * @name $log#error.logs + * + * @description + * Array of messages logged using {@link ng.$log#error `error()`}. + * + * @example + * ```js + * $log.error('Some Error'); + * var first = $log.error.logs.unshift(); + * ``` + */ + $log.error.logs = []; + /** + * @ngdoc property + * @name $log#debug.logs + * + * @description + * Array of messages logged using {@link ng.$log#debug `debug()`}. + * + * @example + * ```js + * $log.debug('Some Error'); + * var first = $log.debug.logs.unshift(); + * ``` + */ + $log.debug.logs = []; + }; - forEach(actions, function(action, name) { - var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); + /** + * @ngdoc method + * @name $log#assertEmpty + * + * @description + * Assert that all of the logging methods have no logged messages. If any messages are present, + * an exception is thrown. + */ + $log.assertEmpty = function () { + var errors = []; + angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function (logLevel) { + angular.forEach($log[logLevel].logs, function (log) { + angular.forEach(log, function (logItem) { + errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + + (logItem.stack || '')); + }); + }); + }); + if (errors.length) { + errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " + + "an expected log message was not checked and removed:"); + errors.push(''); + throw new Error(errors.join('\n---------\n')); + } + }; - Resource[name] = function(a1, a2, a3, a4) { - var params = {}, data, success, error; + $log.reset(); + return $log; + }; + }; - /* jshint -W086 */ /* (purposefully fall through case statements) */ - switch (arguments.length) { - case 4: - error = a4; - success = a3; - //fallthrough - case 3: - case 2: - if (isFunction(a2)) { - if (isFunction(a1)) { - success = a1; - error = a2; - break; - } - success = a2; - error = a3; - //fallthrough - } else { - params = a1; - data = a2; - success = a3; - break; - } - case 1: - if (isFunction(a1)) success = a1; - else if (hasBody) data = a1; - else params = a1; - break; - case 0: break; - default: - throw $resourceMinErr('badargs', - "Expected up to 4 arguments [params, data, success, error], got {0} arguments", - arguments.length); - } - /* jshint +W086 */ /* (purposefully fall through case statements) */ + /** + * @ngdoc service + * @name $interval + * + * @description + * Mock implementation of the $interval service. + * + * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to + * move forward by `millis` milliseconds and trigger any functions scheduled to run in that + * time. + * + * @param {function()} fn A function that should be called repeatedly. + * @param {number} delay Number of milliseconds between each function call. + * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat + * indefinitely. + * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise + * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. + * @param {...*=} Pass additional parameters to the executed function. + * @returns {promise} A promise which will be notified on each iteration. + */ + angular.mock.$IntervalProvider = function () { + this.$get = ['$browser', '$rootScope', '$q', '$$q', + function ($browser, $rootScope, $q, $$q) { + var repeatFns = [], + nextRepeatId = 0, + now = 0; + + var $interval = function (fn, delay, count, invokeApply) { + var hasParams = arguments.length > 4, + args = hasParams ? Array.prototype.slice.call(arguments, 4) : [], + iteration = 0, + skipApply = (angular.isDefined(invokeApply) && !invokeApply), + deferred = (skipApply ? $$q : $q).defer(), + promise = deferred.promise; + + count = (angular.isDefined(count)) ? count : 0; + promise.then(null, null, (!hasParams) ? fn : function () { + fn.apply(null, args); + }); + + promise.$$intervalId = nextRepeatId; + + function tick() { + deferred.notify(iteration++); + + if (count > 0 && iteration >= count) { + var fnIndex; + deferred.resolve(iteration); + + angular.forEach(repeatFns, function (fn, index) { + if (fn.id === promise.$$intervalId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + repeatFns.splice(fnIndex, 1); + } + } + + if (skipApply) { + $browser.defer.flush(); + } else { + $rootScope.$apply(); + } + } + + repeatFns.push({ + nextTime: (now + delay), + delay: delay, + fn: tick, + id: nextRepeatId, + deferred: deferred + }); + repeatFns.sort(function (a, b) { return a.nextTime - b.nextTime; }); + + nextRepeatId++; + return promise; + }; + /** + * @ngdoc method + * @name $interval#cancel + * + * @description + * Cancels a task associated with the `promise`. + * + * @param {promise} promise A promise from calling the `$interval` function. + * @returns {boolean} Returns `true` if the task was successfully cancelled. + */ + $interval.cancel = function (promise) { + if (!promise) return false; + var fnIndex; + + angular.forEach(repeatFns, function (fn, index) { + if (fn.id === promise.$$intervalId) fnIndex = index; + }); + + if (angular.isDefined(fnIndex)) { + repeatFns[fnIndex].deferred.reject('canceled'); + repeatFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + /** + * @ngdoc method + * @name $interval#flush + * @description + * + * Runs interval tasks scheduled to be run in the next `millis` milliseconds. + * + * @param {number=} millis maximum timeout amount to flush up until. + * + * @return {number} The amount of time moved forward. + */ + $interval.flush = function (millis) { + now += millis; + while (repeatFns.length && repeatFns[0].nextTime <= now) { + var task = repeatFns[0]; + task.fn(); + task.nextTime += task.delay; + repeatFns.sort(function (a, b) { return a.nextTime - b.nextTime; }); + } + return millis; + }; + + return $interval; + }]; + }; + + + /* jshint -W101 */ + /* The R_ISO8061_STR regex is never going to fit into the 100 char limit! + * This directive should go inside the anonymous function but a bug in JSHint means that it would + * not be enacted early enough to prevent the warning. + */ + var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; + + function jsonStringToDate(string) { + var match; + if (match = string.match(R_ISO8061_STR)) { + var date = new Date(0), + tzHour = 0, + tzMin = 0; + if (match[9]) { + tzHour = toInt(match[9] + match[10]); + tzMin = toInt(match[9] + match[11]); + } + date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); + date.setUTCHours(toInt(match[4] || 0) - tzHour, + toInt(match[5] || 0) - tzMin, + toInt(match[6] || 0), + toInt(match[7] || 0)); + return date; + } + return string; + } + + function toInt(str) { + return parseInt(str, 10); + } + + function padNumber(num, digits, trim) { + var neg = ''; + if (num < 0) { + neg = '-'; + num = -num; + } + num = '' + num; + while (num.length < digits) num = '0' + num; + if (trim) { + num = num.substr(num.length - digits); + } + return neg + num; + } - var isInstanceCall = this instanceof Resource; - var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); - var httpConfig = {}; - var responseInterceptor = action.interceptor && action.interceptor.response || - defaultResponseInterceptor; - var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || - undefined; - forEach(action, function(value, key) { - switch (key) { - default: - httpConfig[key] = copy(value); - break; - case 'params': - case 'isArray': - case 'interceptor': - break; - case 'timeout': - httpConfig[key] = value; - break; - } - }); + /** + * @ngdoc type + * @name angular.mock.TzDate + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. + * + * Mock of the Date type which has its timezone specified via constructor arg. + * + * The main purpose is to create Date-like instances with timezone fixed to the specified timezone + * offset, so that we can test code that depends on local timezone settings without dependency on + * the time zone settings of the machine where the code is running. + * + * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) + * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* + * + * @example + * !!!! WARNING !!!!! + * This is not a complete Date object so only methods that were implemented can be called safely. + * To make matters worse, TzDate instances inherit stuff from Date via a prototype. + * + * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is + * incomplete we might be missing some non-standard methods. This can result in errors like: + * "Date.prototype.foo called on incompatible Object". + * + * ```js + * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); + * newYearInBratislava.getTimezoneOffset() => -60; + * newYearInBratislava.getFullYear() => 2010; + * newYearInBratislava.getMonth() => 0; + * newYearInBratislava.getDate() => 1; + * newYearInBratislava.getHours() => 0; + * newYearInBratislava.getMinutes() => 0; + * newYearInBratislava.getSeconds() => 0; + * ``` + * + */ + angular.mock.TzDate = function (offset, timestamp) { + var self = new Date(0); + if (angular.isString(timestamp)) { + var tsStr = timestamp; + + self.origDate = jsonStringToDate(timestamp); + + timestamp = self.origDate.getTime(); + if (isNaN(timestamp)) { + throw { + name: "Illegal Argument", + message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" + }; + } + } else { + self.origDate = new Date(timestamp); + } - if (hasBody) httpConfig.data = data; - route.setUrlParams(httpConfig, - extend({}, extractParams(data, action.params || {}), params), - action.url); + var localOffset = new Date(timestamp).getTimezoneOffset(); + self.offsetDiff = localOffset * 60 * 1000 - offset * 1000 * 60 * 60; + self.date = new Date(timestamp + self.offsetDiff); - var promise = $http(httpConfig).then(function(response) { - var data = response.data, - promise = value.$promise; + self.getTime = function () { + return self.date.getTime() - self.offsetDiff; + }; - if (data) { - // Need to convert action.isArray to boolean in case it is undefined - // jshint -W018 - if (angular.isArray(data) !== (!!action.isArray)) { - throw $resourceMinErr('badcfg', - 'Error in resource configuration for action `{0}`. Expected response to ' + - 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object', - angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url); - } - // jshint +W018 - if (action.isArray) { - value.length = 0; - forEach(data, function(item) { - if (typeof item === "object") { - value.push(new Resource(item)); - } else { - // Valid JSON values may be string literals, and these should not be converted - // into objects. These items will not have access to the Resource prototype - // methods, but unfortunately there - value.push(item); - } - }); - } else { - shallowClearAndCopy(data, value); - value.$promise = promise; - } - } + self.toLocaleDateString = function () { + return self.date.toLocaleDateString(); + }; - value.$resolved = true; + self.getFullYear = function () { + return self.date.getFullYear(); + }; - response.resource = value; + self.getMonth = function () { + return self.date.getMonth(); + }; - return response; - }, function(response) { - value.$resolved = true; + self.getDate = function () { + return self.date.getDate(); + }; - (error || noop)(response); + self.getHours = function () { + return self.date.getHours(); + }; - return $q.reject(response); - }); + self.getMinutes = function () { + return self.date.getMinutes(); + }; - promise = promise.then( - function(response) { - var value = responseInterceptor(response); - (success || noop)(value, response.headers); - return value; - }, - responseErrorInterceptor); + self.getSeconds = function () { + return self.date.getSeconds(); + }; - if (!isInstanceCall) { - // we are creating instance / collection - // - set the initial promise - // - return the instance / collection - value.$promise = promise; - value.$resolved = false; + self.getMilliseconds = function () { + return self.date.getMilliseconds(); + }; - return value; - } + self.getTimezoneOffset = function () { + return offset * 60; + }; - // instance call - return promise; - }; + self.getUTCFullYear = function () { + return self.origDate.getUTCFullYear(); + }; + self.getUTCMonth = function () { + return self.origDate.getUTCMonth(); + }; - Resource.prototype['$' + name] = function(params, success, error) { - if (isFunction(params)) { - error = success; success = params; params = {}; - } - var result = Resource[name].call(this, params, this, success, error); - return result.$promise || result; - }; - }); + self.getUTCDate = function () { + return self.origDate.getUTCDate(); + }; - Resource.bind = function(additionalParamDefaults) { - return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); - }; + self.getUTCHours = function () { + return self.origDate.getUTCHours(); + }; - return Resource; - } + self.getUTCMinutes = function () { + return self.origDate.getUTCMinutes(); + }; - return resourceFactory; - }]; - }); + self.getUTCSeconds = function () { + return self.origDate.getUTCSeconds(); + }; + self.getUTCMilliseconds = function () { + return self.origDate.getUTCMilliseconds(); + }; -})(window, window.angular); + self.getDay = function () { + return self.date.getDay(); + }; -/** - * @license AngularJS v1.4.8 - * (c) 2010-2015 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; + // provide this method only on browsers that already have it + if (self.toISOString) { + self.toISOString = function () { + return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + + padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + + padNumber(self.origDate.getUTCDate(), 2) + 'T' + + padNumber(self.origDate.getUTCHours(), 2) + ':' + + padNumber(self.origDate.getUTCMinutes(), 2) + ':' + + padNumber(self.origDate.getUTCSeconds(), 2) + '.' + + padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'; + }; + } - /** - * @ngdoc module - * @name ngRoute - * @description - * - * # ngRoute - * - * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. - * - * ## Example - * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. - * - * - * <div doc-module-components="ngRoute"></div> - */ - /* global -ngRouteModule */ - var ngRouteModule = angular.module('ngRoute', ['ng']). - provider('$route', $RouteProvider), - $routeMinErr = angular.$$minErr('ngRoute'); + //hide all methods not implemented in this mock that the Date prototype exposes + var unimplementedMethods = ['getUTCDay', + 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', + 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', + 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', + 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', + 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; + + angular.forEach(unimplementedMethods, function (methodName) { + self[methodName] = function () { + throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock"); + }; + }); - /** - * @ngdoc provider - * @name $routeProvider - * - * @description - * - * Used for configuring routes. - * - * ## Example - * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. - * - * ## Dependencies - * Requires the {@link ngRoute `ngRoute`} module to be installed. - */ - function $RouteProvider() { - function inherit(parent, extra) { - return angular.extend(Object.create(parent), extra); - } + return self; + }; - var routes = {}; + //make "tzDateInstance instanceof Date" return true + angular.mock.TzDate.prototype = Date.prototype; + /* jshint +W101 */ - /** - * @ngdoc method - * @name $routeProvider#when - * - * @param {string} path Route path (matched against `$location.path`). If `$location.path` - * contains redundant trailing slash or is missing one, the route will still match and the - * `$location.path` will be updated to add or drop the trailing slash to exactly match the - * route definition. - * - * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up - * to the next slash are matched and stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain named groups starting with a colon and ending with a star: - * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` - * when the route matches. - * * `path` can contain optional named groups with a question mark: e.g.`:name?`. - * - * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match - * `/color/brown/largecode/code/with/slashes/edit` and extract: + angular.mock.animate = angular.module('ngAnimateMock', ['ng']) + + .config(['$provide', function ($provide) { + + $provide.factory('$$forceReflow', function () { + function reflowFn() { + reflowFn.totalReflows++; + } + reflowFn.totalReflows = 0; + return reflowFn; + }); + + $provide.factory('$$animateAsyncRun', function () { + var queue = []; + var queueFn = function () { + return function (fn) { + queue.push(fn); + }; + }; + queueFn.flush = function () { + if (queue.length === 0) return false; + + for (var i = 0; i < queue.length; i++) { + queue[i](); + } + queue = []; + + return true; + }; + return queueFn; + }); + + $provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', + '$$forceReflow', '$$animateAsyncRun', '$rootScope', + function ($delegate, $timeout, $browser, $$rAF, + $$forceReflow, $$animateAsyncRun, $rootScope) { + var animate = { + queue: [], + cancel: $delegate.cancel, + on: $delegate.on, + off: $delegate.off, + pin: $delegate.pin, + get reflows() { + return $$forceReflow.totalReflows; + }, + enabled: $delegate.enabled, + flush: function () { + $rootScope.$digest(); + + var doNextRun, somethingFlushed = false; + do { + doNextRun = false; + + if ($$rAF.queue.length) { + $$rAF.flush(); + doNextRun = somethingFlushed = true; + } + + if ($$animateAsyncRun.flush()) { + doNextRun = somethingFlushed = true; + } + } while (doNextRun); + + if (!somethingFlushed) { + throw new Error('No pending animations ready to be closed or flushed'); + } + + $rootScope.$digest(); + } + }; + + angular.forEach( + ['animate', 'enter', 'leave', 'move', 'addClass', 'removeClass', 'setClass'], function (method) { + animate[method] = function () { + animate.queue.push({ + event: method, + element: arguments[0], + options: arguments[arguments.length - 1], + args: arguments + }); + return $delegate[method].apply($delegate, arguments); + }; + }); + + return animate; + }]); + + }]); + + + /** + * @ngdoc function + * @name angular.mock.dump + * @description + * + * *NOTE*: this is not an injectable instance, just a globally available function. + * + * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for + * debugging. + * + * This method is also available on window, where it can be used to display objects on debug + * console. + * + * @param {*} object - any object to turn into string. + * @return {string} a serialized string of the argument + */ + angular.mock.dump = function (object) { + return serialize(object); + + function serialize(object) { + var out; + + if (angular.isElement(object)) { + object = angular.element(object); + out = angular.element('<div></div>'); + angular.forEach(object, function (element) { + out.append(angular.element(element).clone()); + }); + out = out.html(); + } else if (angular.isArray(object)) { + out = []; + angular.forEach(object, function (o) { + out.push(serialize(o)); + }); + out = '[ ' + out.join(', ') + ' ]'; + } else if (angular.isObject(object)) { + if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { + out = serializeScope(object); + } else if (object instanceof Error) { + out = object.stack || ('' + object.name + ': ' + object.message); + } else { + // TODO(i): this prevents methods being logged, + // we should have a better way to serialize objects + out = angular.toJson(object, true); + } + } else { + out = String(object); + } + + return out; + } + + function serializeScope(scope, offset) { + offset = offset || ' '; + var log = [offset + 'Scope(' + scope.$id + '): {']; + for (var key in scope) { + if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) { + log.push(' ' + key + ': ' + angular.toJson(scope[key])); + } + } + var child = scope.$$childHead; + while (child) { + log.push(serializeScope(child, offset + ' ')); + child = child.$$nextSibling; + } + log.push('}'); + return log.join('\n' + offset); + } + }; + + /** + * @ngdoc service + * @name $httpBackend + * @description + * Fake HTTP backend implementation suitable for unit testing applications that use the + * {@link ng.$http $http service}. + * + * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less + * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. + * + * During unit testing, we want our unit tests to run quickly and have no external dependencies so + * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or + * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is + * to verify whether a certain request has been sent or not, or alternatively just let the + * application make requests, respond with pre-trained responses and assert that the end result is + * what we expect it to be. + * + * This mock implementation can be used to respond with static or dynamic responses via the + * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). + * + * When an Angular application needs some data from a server, it calls the $http service, which + * sends the request to a real server using $httpBackend service. With dependency injection, it is + * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify + * the requests and respond with some testing data without sending a request to a real server. + * + * There are two ways to specify what test data should be returned as http responses by the mock + * backend when the code under test makes http requests: + * + * - `$httpBackend.expect` - specifies a request expectation + * - `$httpBackend.when` - specifies a backend definition + * + * + * # Request Expectations vs Backend Definitions + * + * Request expectations provide a way to make assertions about requests made by the application and + * to define responses for those requests. The test will fail if the expected requests are not made + * or they are made in the wrong order. + * + * Backend definitions allow you to define a fake backend for your application which doesn't assert + * if a particular request was made or not, it just returns a trained response if a request is made. + * The test will pass whether or not the request gets made during testing. + * + * + * <table class="table"> + * <tr><th width="220px"></th><th>Request expectations</th><th>Backend definitions</th></tr> + * <tr> + * <th>Syntax</th> + * <td>.expect(...).respond(...)</td> + * <td>.when(...).respond(...)</td> + * </tr> + * <tr> + * <th>Typical usage</th> + * <td>strict unit tests</td> + * <td>loose (black-box) unit testing</td> + * </tr> + * <tr> + * <th>Fulfills multiple requests</th> + * <td>NO</td> + * <td>YES</td> + * </tr> + * <tr> + * <th>Order of requests matters</th> + * <td>YES</td> + * <td>NO</td> + * </tr> + * <tr> + * <th>Request required</th> + * <td>YES</td> + * <td>NO</td> + * </tr> + * <tr> + * <th>Response required</th> + * <td>optional (see below)</td> + * <td>YES</td> + * </tr> + * </table> + * + * In cases where both backend definitions and request expectations are specified during unit + * testing, the request expectations are evaluated first. + * + * If a request expectation has no response specified, the algorithm will search your backend + * definitions for an appropriate response. + * + * If a request didn't match any expectation or if the expectation doesn't have the response + * defined, the backend definitions are evaluated in sequential order to see if any of them match + * the request. The response from the first matched definition is returned. + * + * + * # Flushing HTTP requests + * + * The $httpBackend used in production always responds to requests asynchronously. If we preserved + * this behavior in unit testing, we'd have to create async unit tests, which are hard to write, + * to follow and to maintain. But neither can the testing mock respond synchronously; that would + * change the execution of the code under test. For this reason, the mock $httpBackend has a + * `flush()` method, which allows the test to explicitly flush pending requests. This preserves + * the async api of the backend, while allowing the test to execute synchronously. + * + * + * # Unit testing with mock $httpBackend + * The following code shows how to setup and use the mock backend when unit testing a controller. + * First we create the controller under test: + * + ```js + // The module code + angular + .module('MyApp', []) + .controller('MyController', MyController); + + // The controller code + function MyController($scope, $http) { + var authToken; + + $http.get('/auth.py').success(function(data, status, headers) { + authToken = headers('A-Token'); + $scope.user = data; + }); + + $scope.saveMessage = function(message) { + var headers = { 'Authorization': authToken }; + $scope.status = 'Saving...'; + + $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) { + $scope.status = ''; + }).error(function() { + $scope.status = 'Failed...'; + }); + }; + } + ``` + * + * Now we setup the mock backend and create the test specs: + * + ```js + // testing controller + describe('MyController', function() { + var $httpBackend, $rootScope, createController, authRequestHandler; + + // Set up the module + beforeEach(module('MyApp')); + + beforeEach(inject(function($injector) { + // Set up the mock http service responses + $httpBackend = $injector.get('$httpBackend'); + // backend definition common for all tests + authRequestHandler = $httpBackend.when('GET', '/auth.py') + .respond({userId: 'userX'}, {'A-Token': 'xxx'}); + + // Get hold of a scope (i.e. the root scope) + $rootScope = $injector.get('$rootScope'); + // The $controller service is used to create instances of controllers + var $controller = $injector.get('$controller'); + + createController = function() { + return $controller('MyController', {'$scope' : $rootScope }); + }; + })); + + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + + it('should fetch authentication token', function() { + $httpBackend.expectGET('/auth.py'); + var controller = createController(); + $httpBackend.flush(); + }); + + + it('should fail authentication', function() { + + // Notice how you can change the response even after it was set + authRequestHandler.respond(401, ''); + + $httpBackend.expectGET('/auth.py'); + var controller = createController(); + $httpBackend.flush(); + expect($rootScope.status).toBe('Failed...'); + }); + + + it('should send msg to server', function() { + var controller = createController(); + $httpBackend.flush(); + + // now you don’t care about the authentication, but + // the controller will still send the request and + // $httpBackend will respond without you having to + // specify the expectation and response for this request + + $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); + $rootScope.saveMessage('message content'); + expect($rootScope.status).toBe('Saving...'); + $httpBackend.flush(); + expect($rootScope.status).toBe(''); + }); + + + it('should send auth header', function() { + var controller = createController(); + $httpBackend.flush(); + + $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { + // check if the header was sent, if it wasn't the expectation won't + // match the request and the test will fail + return headers['Authorization'] == 'xxx'; + }).respond(201, ''); + + $rootScope.saveMessage('whatever'); + $httpBackend.flush(); + }); + }); + ``` + */ + angular.mock.$HttpBackendProvider = function () { + this.$get = ['$rootScope', '$timeout', createHttpBackendMock]; + }; + + /** + * General factory function for $httpBackend mock. + * Returns instance for unit testing (when no arguments specified): + * - passing through is disabled + * - auto flushing is disabled + * + * Returns instance for e2e testing (when `$delegate` and `$browser` specified): + * - passing through (delegating request to real backend) is enabled + * - auto flushing is enabled + * + * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) + * @param {Object=} $browser Auto-flushing enabled if specified + * @return {Object} Instance of $httpBackend mock + */ + function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { + var definitions = [], + expectations = [], + responses = [], + responsesPush = angular.bind(responses, responses.push), + copy = angular.copy; + + function createResponse(status, data, headers, statusText) { + if (angular.isFunction(status)) return status; + + return function () { + return angular.isNumber(status) + ? [status, data, headers, statusText] + : [200, status, data, headers]; + }; + } + + // TODO(vojta): change params to: method, url, data, headers, callback + function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) { + var xhr = new MockXhr(), + expectation = expectations[0], + wasExpected = false; + + function prettyPrint(data) { + return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) + ? data + : angular.toJson(data); + } + + function wrapResponse(wrapped) { + if (!$browser && timeout) { + timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout); + } + + return handleResponse; + + function handleResponse() { + var response = wrapped.response(method, url, data, headers); + xhr.$$respHeaders = response[2]; + callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(), + copy(response[3] || '')); + } + + function handleTimeout() { + for (var i = 0, ii = responses.length; i < ii; i++) { + if (responses[i] === handleResponse) { + responses.splice(i, 1); + callback(-1, undefined, ''); + break; + } + } + } + } + + if (expectation && expectation.match(method, url)) { + if (!expectation.matchData(data)) { + throw new Error('Expected ' + expectation + ' with different data\n' + + 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); + } + + if (!expectation.matchHeaders(headers)) { + throw new Error('Expected ' + expectation + ' with different headers\n' + + 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + + prettyPrint(headers)); + } + + expectations.shift(); + + if (expectation.response) { + responses.push(wrapResponse(expectation)); + return; + } + wasExpected = true; + } + + var i = -1, definition; + while ((definition = definitions[++i])) { + if (definition.match(method, url, data, headers || {})) { + if (definition.response) { + // if $browser specified, we do auto flush all requests + ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); + } else if (definition.passThrough) { + $delegate(method, url, data, callback, headers, timeout, withCredentials); + } else throw new Error('No response defined !'); + return; + } + } + throw wasExpected ? + new Error('No response defined !') : + new Error('Unexpected request: ' + method + ' ' + url + '\n' + + (expectation ? 'Expected ' + expectation : 'No more request expected')); + } + + /** + * @ngdoc method + * @name $httpBackend#when + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + * + * - respond – + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can + * return an array containing response status (number), response data (string), response + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. + */ + $httpBackend.when = function (method, url, data, headers) { + var definition = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function (status, data, headers, statusText) { + definition.passThrough = undefined; + definition.response = createResponse(status, data, headers, statusText); + return chain; + } + }; + + if ($browser) { + chain.passThrough = function () { + definition.response = undefined; + definition.passThrough = true; + return chain; + }; + } + + definitions.push(definition); + return chain; + }; + + /** + * @ngdoc method + * @name $httpBackend#whenGET + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenHEAD + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenDELETE + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPOST + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPUT + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives + * data string and returns true if the data is as expected. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenJSONP + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + createShortMethods('when'); + + + /** + * @ngdoc method + * @name $httpBackend#expect + * @description + * Creates a new request expectation. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current expectation. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + * + * - respond – + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can + * return an array containing response status (number), response data (string), response + * headers (Object), and the text for the status (string). The respond method returns the + * `requestHandler` object for possible overrides. + */ + $httpBackend.expect = function (method, url, data, headers) { + var expectation = new MockHttpExpectation(method, url, data, headers), + chain = { + respond: function (status, data, headers, statusText) { + expectation.response = createResponse(status, data, headers, statusText); + return chain; + } + }; + + expectations.push(expectation); + return chain; + }; + + + /** + * @ngdoc method + * @name $httpBackend#expectGET + * @description + * Creates a new request expectation for GET requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. See #expect for more info. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectHEAD + * @description + * Creates a new request expectation for HEAD requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectDELETE + * @description + * Creates a new request expectation for DELETE requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPOST + * @description + * Creates a new request expectation for POST requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPUT + * @description + * Creates a new request expectation for PUT requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectPATCH + * @description + * Creates a new request expectation for PATCH requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that + * receives data string and returns true if the data is as expected, or Object if request body + * is in JSON format. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#expectJSONP + * @description + * Creates a new request expectation for JSONP requests. For more info see `expect()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives an url + * and returns true if the url matches the current definition. + * @returns {requestHandler} Returns an object with `respond` method that controls how a matched + * request is handled. You can save this object for later use and invoke `respond` again in + * order to change how a matched request is handled. + */ + createShortMethods('expect'); + + + /** + * @ngdoc method + * @name $httpBackend#flush + * @description + * Flushes all pending requests using the trained responses. + * + * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, + * all pending requests will be flushed. If there are no pending requests when the flush method + * is called an exception is thrown (as this typically a sign of programming error). + */ + $httpBackend.flush = function (count, digest) { + if (digest !== false) $rootScope.$digest(); + if (!responses.length) throw new Error('No pending request to flush !'); + + if (angular.isDefined(count) && count !== null) { + while (count--) { + if (!responses.length) throw new Error('No more pending request to flush !'); + responses.shift()(); + } + } else { + while (responses.length) { + responses.shift()(); + } + } + $httpBackend.verifyNoOutstandingExpectation(digest); + }; + + + /** + * @ngdoc method + * @name $httpBackend#verifyNoOutstandingExpectation + * @description + * Verifies that all of the requests defined via the `expect` api were made. If any of the + * requests were not made, verifyNoOutstandingExpectation throws an exception. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * ```js + * afterEach($httpBackend.verifyNoOutstandingExpectation); + * ``` + */ + $httpBackend.verifyNoOutstandingExpectation = function (digest) { + if (digest !== false) $rootScope.$digest(); + if (expectations.length) { + throw new Error('Unsatisfied requests: ' + expectations.join(', ')); + } + }; + + + /** + * @ngdoc method + * @name $httpBackend#verifyNoOutstandingRequest + * @description + * Verifies that there are no outstanding requests that need to be flushed. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * ```js + * afterEach($httpBackend.verifyNoOutstandingRequest); + * ``` + */ + $httpBackend.verifyNoOutstandingRequest = function () { + if (responses.length) { + throw new Error('Unflushed requests: ' + responses.length); + } + }; + + + /** + * @ngdoc method + * @name $httpBackend#resetExpectations + * @description + * Resets all request expectations, but preserves all backend definitions. Typically, you would + * call resetExpectations during a multiple-phase test when you want to reuse the same instance of + * $httpBackend mock. + */ + $httpBackend.resetExpectations = function () { + expectations.length = 0; + responses.length = 0; + }; + + return $httpBackend; + + + function createShortMethods(prefix) { + angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function (method) { + $httpBackend[prefix + method] = function (url, headers) { + return $httpBackend[prefix](method, url, undefined, headers); + }; + }); + + angular.forEach(['PUT', 'POST', 'PATCH'], function (method) { + $httpBackend[prefix + method] = function (url, data, headers) { + return $httpBackend[prefix](method, url, data, headers); + }; + }); + } + } + + function MockHttpExpectation(method, url, data, headers) { + + this.data = data; + this.headers = headers; + + this.match = function (m, u, d, h) { + if (method != m) return false; + if (!this.matchUrl(u)) return false; + if (angular.isDefined(d) && !this.matchData(d)) return false; + if (angular.isDefined(h) && !this.matchHeaders(h)) return false; + return true; + }; + + this.matchUrl = function (u) { + if (!url) return true; + if (angular.isFunction(url.test)) return url.test(u); + if (angular.isFunction(url)) return url(u); + return url == u; + }; + + this.matchHeaders = function (h) { + if (angular.isUndefined(headers)) return true; + if (angular.isFunction(headers)) return headers(h); + return angular.equals(headers, h); + }; + + this.matchData = function (d) { + if (angular.isUndefined(data)) return true; + if (data && angular.isFunction(data.test)) return data.test(d); + if (data && angular.isFunction(data)) return data(d); + if (data && !angular.isString(data)) { + return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d)); + } + return data == d; + }; + + this.toString = function () { + return method + ' ' + url; + }; + } + + function createMockXhr() { + return new MockXhr(); + } + + function MockXhr() { + + // hack for testing $http, $httpBackend + MockXhr.$$lastInstance = this; + + this.open = function (method, url, async) { + this.$$method = method; + this.$$url = url; + this.$$async = async; + this.$$reqHeaders = {}; + this.$$respHeaders = {}; + }; + + this.send = function (data) { + this.$$data = data; + }; + + this.setRequestHeader = function (key, value) { + this.$$reqHeaders[key] = value; + }; + + this.getResponseHeader = function (name) { + // the lookup must be case insensitive, + // that's why we try two quick lookups first and full scan last + var header = this.$$respHeaders[name]; + if (header) return header; + + name = angular.lowercase(name); + header = this.$$respHeaders[name]; + if (header) return header; + + header = undefined; + angular.forEach(this.$$respHeaders, function (headerVal, headerName) { + if (!header && angular.lowercase(headerName) == name) header = headerVal; + }); + return header; + }; + + this.getAllResponseHeaders = function () { + var lines = []; + + angular.forEach(this.$$respHeaders, function (value, key) { + lines.push(key + ': ' + value); + }); + return lines.join('\n'); + }; + + this.abort = angular.noop; + } + + + /** + * @ngdoc service + * @name $timeout + * @description + * + * This service is just a simple decorator for {@link ng.$timeout $timeout} service + * that adds a "flush" and "verifyNoPendingTasks" methods. + */ + + angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function ($delegate, $browser) { + + /** + * @ngdoc method + * @name $timeout#flush + * @description + * + * Flushes the queue of pending tasks. + * + * @param {number=} delay maximum timeout amount to flush up until + */ + $delegate.flush = function (delay) { + $browser.defer.flush(delay); + }; + + /** + * @ngdoc method + * @name $timeout#verifyNoPendingTasks + * @description + * + * Verifies that there are no pending tasks that need to be flushed. + */ + $delegate.verifyNoPendingTasks = function () { + if ($browser.deferredFns.length) { + throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + + formatPendingTasksAsString($browser.deferredFns)); + } + }; + + function formatPendingTasksAsString(tasks) { + var result = []; + angular.forEach(tasks, function (task) { + result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); + }); + + return result.join(', '); + } + + return $delegate; + }]; + + angular.mock.$RAFDecorator = ['$delegate', function ($delegate) { + var rafFn = function (fn) { + var index = rafFn.queue.length; + rafFn.queue.push(fn); + return function () { + rafFn.queue.splice(index, 1); + }; + }; + + rafFn.queue = []; + rafFn.supported = $delegate.supported; + + rafFn.flush = function () { + if (rafFn.queue.length === 0) { + throw new Error('No rAF callbacks present'); + } + + var length = rafFn.queue.length; + for (var i = 0; i < length; i++) { + rafFn.queue[i](); + } + + rafFn.queue = rafFn.queue.slice(i); + }; + + return rafFn; + }]; + + /** + * + */ + angular.mock.$RootElementProvider = function () { + this.$get = function () { + return angular.element('<div ng-app></div>'); + }; + }; + + /** + * @ngdoc service + * @name $controller + * @description + * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing + * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}. + * + * + * ## Example + * + * ```js + * + * // Directive definition ... + * + * myMod.directive('myDirective', { + * controller: 'MyDirectiveController', + * bindToController: { + * name: '@' + * } + * }); + * + * + * // Controller definition ... + * + * myMod.controller('MyDirectiveController', ['log', function($log) { + * $log.info(this.name); + * })]; + * + * + * // In a test ... + * + * describe('myDirectiveController', function() { + * it('should write the bound name to the log', inject(function($controller, $log) { + * var ctrl = $controller('MyDirectiveController', { /* no locals */ }, { name: 'Clark Kent' }); + * expect(ctrl.name).toEqual('Clark Kent'); + * expect($log.info.logs).toEqual(['Clark Kent']); + * }); + * }); + * + * ``` + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global + * `window` object (not recommended) + * + * The string can use the `controller as property` syntax, where the controller instance is published + * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this + * to work correctly. + * + * @param {Object} locals Injection locals for Controller. + * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used + * to simulate the `bindToController` feature and simplify certain kinds of tests. + * @return {Object} Instance of given controller. + */ + angular.mock.$ControllerDecorator = ['$delegate', function ($delegate) { + return function (expression, locals, later, ident) { + if (later && typeof later === 'object') { + var create = $delegate(expression, locals, true, ident); + angular.extend(create.instance, later); + return create(); + } + return $delegate(expression, locals, later, ident); + }; + }]; + + + /** + * @ngdoc module + * @name ngMock + * @packageName angular-mocks + * @description + * + * # ngMock + * + * The `ngMock` module provides support to inject and mock Angular services into unit tests. + * In addition, ngMock also extends various core ng services such that they can be + * inspected and controlled in a synchronous manner within test code. + * + * + * <div doc-module-components="ngMock"></div> + * + */ + angular.module('ngMock', ['ng']).provider({ + $browser: angular.mock.$BrowserProvider, + $exceptionHandler: angular.mock.$ExceptionHandlerProvider, + $log: angular.mock.$LogProvider, + $interval: angular.mock.$IntervalProvider, + $httpBackend: angular.mock.$HttpBackendProvider, + $rootElement: angular.mock.$RootElementProvider + }).config(['$provide', function ($provide) { + $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); + $provide.decorator('$$rAF', angular.mock.$RAFDecorator); + $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); + $provide.decorator('$controller', angular.mock.$ControllerDecorator); + }]); + + /** + * @ngdoc module + * @name ngMockE2E + * @module ngMockE2E + * @packageName angular-mocks + * @description + * + * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. + * Currently there is only one mock present in this module - + * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. + */ + angular.module('ngMockE2E', ['ng']).config(['$provide', function ($provide) { + $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); + }]); + + /** + * @ngdoc service + * @name $httpBackend + * @module ngMockE2E + * @description + * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of + * applications that use the {@link ng.$http $http service}. + * + * *Note*: For fake http backend implementation suitable for unit testing please see + * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. + * + * This implementation can be used to respond with static or dynamic responses via the `when` api + * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the + * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch + * templates from a webserver). + * + * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application + * is being developed with the real backend api replaced with a mock, it is often desirable for + * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch + * templates or static files from the webserver). To configure the backend with this behavior + * use the `passThrough` request handler of `when` instead of `respond`. + * + * Additionally, we don't want to manually have to flush mocked out requests like we do during unit + * testing. For this reason the e2e $httpBackend flushes mocked out requests + * automatically, closely simulating the behavior of the XMLHttpRequest object. + * + * To setup the application to run with this http backend, you have to create a module that depends + * on the `ngMockE2E` and your application modules and defines the fake backend: + * + * ```js + * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); + * myAppDev.run(function($httpBackend) { + * phones = [{name: 'phone1'}, {name: 'phone2'}]; + * + * // returns the current list of phones + * $httpBackend.whenGET('/phones').respond(phones); + * + * // adds a new phone to the phones array + * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { + * var phone = angular.fromJson(data); + * phones.push(phone); + * return [200, phone, {}]; + * }); + * $httpBackend.whenGET(/^\/templates\//).passThrough(); + * //... + * }); + * ``` + * + * Afterwards, bootstrap your app with this new module. + */ + + /** + * @ngdoc method + * @name $httpBackend#when + * @module ngMockE2E + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + * + * - respond – + * `{function([status,] data[, headers, statusText]) + * | function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string), response headers + * (Object), and the text for the status (string). + * - passThrough – `{function()}` – Any request matching a backend definition with + * `passThrough` handler will be passed through to the real backend (an XHR request will be made + * to the server.) + * - Both methods return the `requestHandler` object for possible overrides. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenGET + * @module ngMockE2E + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenHEAD + * @module ngMockE2E + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenDELETE + * @module ngMockE2E + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPOST + * @module ngMockE2E + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPUT + * @module ngMockE2E + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenPATCH + * @module ngMockE2E + * @description + * Creates a new backend definition for PATCH requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + + /** + * @ngdoc method + * @name $httpBackend#whenJSONP + * @module ngMockE2E + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp|function(string)} url HTTP url or function that receives a url + * and returns true if the url matches the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. You can save this object for later use and invoke + * `respond` or `passThrough` again in order to change how a matched request is handled. + */ + angular.mock.e2e = {}; + angular.mock.e2e.$httpBackendDecorator = + ['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock]; + + + /** + * @ngdoc type + * @name $rootScope.Scope + * @module ngMock + * @description + * {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These + * methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when + * `ngMock` module is loaded. + * + * In addition to all the regular `Scope` methods, the following helper methods are available: + */ + angular.mock.$RootScopeDecorator = ['$delegate', function ($delegate) { + + var $rootScopePrototype = Object.getPrototypeOf($delegate); + + $rootScopePrototype.$countChildScopes = countChildScopes; + $rootScopePrototype.$countWatchers = countWatchers; + + return $delegate; + + // ------------------------------------------------------------------------------------------ // + + /** + * @ngdoc method + * @name $rootScope.Scope#$countChildScopes + * @module ngMock + * @description + * Counts all the direct and indirect child scopes of the current scope. + * + * The current scope is excluded from the count. The count includes all isolate child scopes. + * + * @returns {number} Total number of child scopes. + */ + function countChildScopes() { + // jshint validthis: true + var count = 0; // exclude the current scope + var pendingChildHeads = [this.$$childHead]; + var currentScope; + + while (pendingChildHeads.length) { + currentScope = pendingChildHeads.shift(); + + while (currentScope) { + count += 1; + pendingChildHeads.push(currentScope.$$childHead); + currentScope = currentScope.$$nextSibling; + } + } + + return count; + } + + + /** + * @ngdoc method + * @name $rootScope.Scope#$countWatchers + * @module ngMock + * @description + * Counts all the watchers of direct and indirect child scopes of the current scope. + * + * The watchers of the current scope are included in the count and so are all the watchers of + * isolate child scopes. + * + * @returns {number} Total number of watchers. + */ + function countWatchers() { + // jshint validthis: true + var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope + var pendingChildHeads = [this.$$childHead]; + var currentScope; + + while (pendingChildHeads.length) { + currentScope = pendingChildHeads.shift(); + + while (currentScope) { + count += currentScope.$$watchers ? currentScope.$$watchers.length : 0; + pendingChildHeads.push(currentScope.$$childHead); + currentScope = currentScope.$$nextSibling; + } + } + + return count; + } + }]; + + + if (window.jasmine || window.mocha) { + + var currentSpec = null, + annotatedFunctions = [], + isSpecRunning = function () { + return !!currentSpec; + }; + + angular.mock.$$annotate = angular.injector.$$annotate; + angular.injector.$$annotate = function (fn) { + if (typeof fn === 'function' && !fn.$inject) { + annotatedFunctions.push(fn); + } + return angular.mock.$$annotate.apply(this, arguments); + }; + + + (window.beforeEach || window.setup)(function () { + annotatedFunctions = []; + currentSpec = this; + }); + + (window.afterEach || window.teardown)(function () { + var injector = currentSpec.$injector; + + annotatedFunctions.forEach(function (fn) { + delete fn.$inject; + }); + + angular.forEach(currentSpec.$modules, function (module) { + if (module && module.$$hashKey) { + module.$$hashKey = undefined; + } + }); + + currentSpec.$injector = null; + currentSpec.$modules = null; + currentSpec = null; + + if (injector) { + injector.get('$rootElement').off(); + } + + // clean up jquery's fragment cache + angular.forEach(angular.element.fragments, function (val, key) { + delete angular.element.fragments[key]; + }); + + MockXhr.$$lastInstance = null; + + angular.forEach(angular.callbacks, function (val, key) { + delete angular.callbacks[key]; + }); + angular.callbacks.counter = 0; + }); + + /** + * @ngdoc function + * @name angular.mock.module + * @description + * + * *NOTE*: This function is also published on window for easy access.<br> + * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * This function registers a module configuration code. It collects the configuration information + * which will be used when the injector is created by {@link angular.mock.inject inject}. + * + * See {@link angular.mock.inject inject} for usage example + * + * @param {...(string|Function|Object)} fns any number of modules which are represented as string + * aliases or as anonymous module initialization functions. The modules are used to + * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an + * object literal is passed they will be registered as values in the module, the key being + * the module name and the value being what is returned. + */ + window.module = angular.mock.module = function () { + var moduleFns = Array.prototype.slice.call(arguments, 0); + return isSpecRunning() ? workFn() : workFn; + ///////////////////// + function workFn() { + if (currentSpec.$injector) { + throw new Error('Injector already created, can not register a module!'); + } else { + var modules = currentSpec.$modules || (currentSpec.$modules = []); + angular.forEach(moduleFns, function (module) { + if (angular.isObject(module) && !angular.isArray(module)) { + modules.push(function ($provide) { + angular.forEach(module, function (value, key) { + $provide.value(key, value); + }); + }); + } else { + modules.push(module); + } + }); + } + } + }; + + /** + * @ngdoc function + * @name angular.mock.inject + * @description + * + * *NOTE*: This function is also published on window for easy access.<br> + * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha + * + * The inject function wraps a function into an injectable function. The inject() creates new + * instance of {@link auto.$injector $injector} per test, which is then used for + * resolving references. + * + * + * ## Resolving References (Underscore Wrapping) + * Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this + * in multiple `it()` clauses. To be able to do this we must assign the reference to a variable + * that is declared in the scope of the `describe()` block. Since we would, most likely, want + * the variable to have the same name of the reference we have a problem, since the parameter + * to the `inject()` function would hide the outer variable. + * + * To help with this, the injected parameters can, optionally, be enclosed with underscores. + * These are ignored by the injector when the reference name is resolved. + * + * For example, the parameter `_myService_` would be resolved as the reference `myService`. + * Since it is available in the function body as _myService_, we can then assign it to a variable + * defined in an outer scope. + * + * ``` + * // Defined out reference variable outside + * var myService; + * + * // Wrap the parameter in underscores + * beforeEach( inject( function(_myService_){ + * myService = _myService_; + * })); + * + * // Use myService in a series of tests. + * it('makes use of myService', function() { + * myService.doStuff(); + * }); + * + * ``` + * + * See also {@link angular.mock.module angular.mock.module} + * + * ## Example + * Example of what a typical jasmine tests looks like with the inject method. + * ```js + * + * angular.module('myApplicationModule', []) + * .value('mode', 'app') + * .value('version', 'v1.0.1'); + * + * + * describe('MyApp', function() { + * + * // You need to load modules that you want to test, + * // it loads only the "ng" module by default. + * beforeEach(module('myApplicationModule')); + * + * + * // inject() is used to inject arguments of all given functions + * it('should provide a version', inject(function(mode, version) { + * expect(version).toEqual('v1.0.1'); + * expect(mode).toEqual('app'); + * })); + * + * + * // The inject and module method can also be used inside of the it or beforeEach + * it('should override a version and test the new version is injected', function() { + * // module() takes functions or strings (module aliases) + * module(function($provide) { + * $provide.value('version', 'overridden'); // override version here + * }); + * + * inject(function(version) { + * expect(version).toEqual('overridden'); + * }); + * }); + * }); + * + * ``` + * + * @param {...Function} fns any number of functions which will be injected using the injector. + */ + + + + var ErrorAddingDeclarationLocationStack = function (e, errorForStack) { + this.message = e.message; + this.name = e.name; + if (e.line) this.line = e.line; + if (e.sourceId) this.sourceId = e.sourceId; + if (e.stack && errorForStack) + this.stack = e.stack + '\n' + errorForStack.stack; + if (e.stackArray) this.stackArray = e.stackArray; + }; + ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString; + + window.inject = angular.mock.inject = function () { + var blockFns = Array.prototype.slice.call(arguments, 0); + var errorForStack = new Error('Declaration Location'); + return isSpecRunning() ? workFn.call(currentSpec) : workFn; + ///////////////////// + function workFn() { + var modules = currentSpec.$modules || []; + var strictDi = !!currentSpec.$injectorStrict; + modules.unshift('ngMock'); + modules.unshift('ng'); + var injector = currentSpec.$injector; + if (!injector) { + if (strictDi) { + // If strictDi is enabled, annotate the providerInjector blocks + angular.forEach(modules, function (moduleFn) { + if (typeof moduleFn === "function") { + angular.injector.$$annotate(moduleFn); + } + }); + } + injector = currentSpec.$injector = angular.injector(modules, strictDi); + currentSpec.$injectorStrict = strictDi; + } + for (var i = 0, ii = blockFns.length; i < ii; i++) { + if (currentSpec.$injectorStrict) { + // If the injector is strict / strictDi, and the spec wants to inject using automatic + // annotation, then annotate the function here. + injector.annotate(blockFns[i]); + } + try { + /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */ + injector.invoke(blockFns[i] || angular.noop, this); + /* jshint +W040 */ + } catch (e) { + if (e.stack && errorForStack) { + throw new ErrorAddingDeclarationLocationStack(e, errorForStack); + } + throw e; + } finally { + errorForStack = null; + } + } + } + }; + + + angular.mock.inject.strictDi = function (value) { + value = arguments.length ? !!value : true; + return isSpecRunning() ? workFn() : workFn; + + function workFn() { + if (value !== currentSpec.$injectorStrict) { + if (currentSpec.$injector) { + throw new Error('Injector already created, can not modify strict annotations'); + } else { + currentSpec.$injectorStrict = value; + } + } + } + }; + } + + +})(window, window.angular); +/** + * @license AngularJS v1.4.8 + * (c) 2010-2015 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + + var $resourceMinErr = angular.$$minErr('$resource'); + +// Helper functions and regex to lookup a dotted path on an object +// stopping at undefined/null. The path must be composed of ASCII +// identifiers (just like $parse) + var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/; + + function isValidDottedPath(path) { + return (path != null && path !== '' && path !== 'hasOwnProperty' && + MEMBER_NAME_REGEX.test('.' + path)); + } + + function lookupDottedPath(obj, path) { + if (!isValidDottedPath(path)) { + throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); + } + var keys = path.split('.'); + for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) { + var key = keys[i]; + obj = (obj !== null) ? obj[key] : undefined; + } + return obj; + } + + /** + * Create a shallow copy of an object and clear other fields from the destination + */ + function shallowClearAndCopy(src, dst) { + dst = dst || {}; + + angular.forEach(dst, function(value, key) { + delete dst[key]; + }); + + for (var key in src) { + if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { + dst[key] = src[key]; + } + } + + return dst; + } + + /** + * @ngdoc module + * @name ngResource + * @description + * + * # ngResource + * + * The `ngResource` module provides interaction support with RESTful services + * via the $resource service. + * + * + * <div doc-module-components="ngResource"></div> + * + * See {@link ngResource.$resource `$resource`} for usage. + */ + + /** + * @ngdoc service + * @name $resource + * @requires $http + * + * @description + * A factory which creates a resource object that lets you interact with + * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. + * + * The returned resource object has action methods which provide high-level behaviors without + * the need to interact with the low level {@link ng.$http $http} service. + * + * Requires the {@link ngResource `ngResource`} module to be installed. + * + * By default, trailing slashes will be stripped from the calculated URLs, + * which can pose problems with server backends that do not expect that + * behavior. This can be disabled by configuring the `$resourceProvider` like + * this: + * + * ```js + app.config(['$resourceProvider', function($resourceProvider) { + // Don't strip trailing slashes from calculated URLs + $resourceProvider.defaults.stripTrailingSlashes = false; + }]); + * ``` + * + * @param {string} url A parameterized URL template with parameters prefixed by `:` as in + * `/user/:username`. If you are using a URL with a port number (e.g. + * `http://example.com:8080/api`), it will be respected. + * + * If you are using a url with a suffix, just add the suffix, like this: + * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` + * or even `$resource('http://example.com/resource/:resource_id.:format')` + * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be + * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you + * can escape it with `/\.`. + * + * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in + * `actions` methods. If any of the parameter value is a function, it will be executed every time + * when a param value needs to be obtained for a request (unless the param was overridden). + * + * Each key value in the parameter object is first bound to url template if present and then any + * excess keys are appended to the url search query after the `?`. + * + * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in + * URL `/path/greet?salutation=Hello`. + * + * If the parameter value is prefixed with `@` then the value for that parameter will be extracted + * from the corresponding property on the `data` object (provided when calling an action method). For + * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam` + * will be `data.someProp`. + * + * @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend + * the default set of resource actions. The declaration should be created in the format of {@link + * ng.$http#usage $http.config}: + * + * {action1: {method:?, params:?, isArray:?, headers:?, ...}, + * action2: {method:?, params:?, isArray:?, headers:?, ...}, + * ...} + * + * Where: + * + * - **`action`** – {string} – The name of action. This name becomes the name of the method on + * your resource object. + * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, + * `DELETE`, `JSONP`, etc). + * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of + * the parameter value is a function, it will be executed every time when a param value needs to + * be obtained for a request (unless the param was overridden). + * - **`url`** – {string} – action specific `url` override. The url templating is supported just + * like for the resource-level urls. + * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, + * see `returns` section. + * - **`transformRequest`** – + * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – + * transform function or an array of such functions. The transform function takes the http + * request body and headers and returns its transformed (typically serialized) version. + * By default, transformRequest will contain one function that checks if the request data is + * an object and serializes to using `angular.toJson`. To prevent this behavior, set + * `transformRequest` to an empty array: `transformRequest: []` + * - **`transformResponse`** – + * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – + * transform function or an array of such functions. The transform function takes the http + * response body and headers and returns its transformed (typically deserialized) version. + * By default, transformResponse will contain one function that checks if the response looks like + * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set + * `transformResponse` to an empty array: `transformResponse: []` + * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the + * GET request, otherwise if a cache instance built with + * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that + * should abort the request when resolved. + * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the + * XHR object. See + * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) + * for more information. + * - **`responseType`** - `{string}` - see + * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). + * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - + * `response` and `responseError`. Both `response` and `responseError` interceptors get called + * with `http response` object. See {@link ng.$http $http interceptors}. + * + * @param {Object} options Hash with custom settings that should extend the + * default `$resourceProvider` behavior. The only supported option is + * + * Where: + * + * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing + * slashes from any calculated URL will be stripped. (Defaults to true.) + * + * @returns {Object} A resource "class" object with methods for the default set of resource actions + * optionally extended with custom `actions`. The default set contains these actions: + * ```js + * { 'get': {method:'GET'}, + * 'save': {method:'POST'}, + * 'query': {method:'GET', isArray:true}, + * 'remove': {method:'DELETE'}, + * 'delete': {method:'DELETE'} }; + * ``` + * + * Calling these methods invoke an {@link ng.$http} with the specified http method, + * destination and parameters. When the data is returned from the server then the object is an + * instance of the resource class. The actions `save`, `remove` and `delete` are available on it + * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, + * read, update, delete) on server-side data like this: + * ```js + * var User = $resource('/user/:userId', {userId:'@id'}); + * var user = User.get({userId:123}, function() { + * user.abc = true; + * user.$save(); + * }); + * ``` + * + * It is important to realize that invoking a $resource object method immediately returns an + * empty reference (object or array depending on `isArray`). Once the data is returned from the + * server the existing reference is populated with the actual data. This is a useful trick since + * usually the resource is assigned to a model which is then rendered by the view. Having an empty + * object results in no rendering, once the data arrives from the server then the object is + * populated with the data and the view automatically re-renders itself showing the new data. This + * means that in most cases one never has to write a callback function for the action methods. + * + * The action methods on the class object or instance object can be invoked with the following + * parameters: + * + * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` + * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` + * - non-GET instance actions: `instance.$action([parameters], [success], [error])` + * + * + * Success callback is called with (value, responseHeaders) arguments, where the value is + * the populated resource instance or collection object. The error callback is called + * with (httpResponse) argument. + * + * Class actions return empty instance (with additional properties below). + * Instance actions return promise of the action. + * + * The Resource instances and collection have these additional properties: + * + * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this + * instance or collection. + * + * On success, the promise is resolved with the same resource instance or collection object, + * updated with data from server. This makes it easy to use in + * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view + * rendering until the resource(s) are loaded. + * + * On failure, the promise is resolved with the {@link ng.$http http response} object, without + * the `resource` property. + * + * If an interceptor object was provided, the promise will instead be resolved with the value + * returned by the interceptor. + * + * - `$resolved`: `true` after first server interaction is completed (either with success or + * rejection), `false` before that. Knowing if the Resource has been resolved is useful in + * data-binding. + * + * @example + * + * # Credit card resource + * + * ```js + // Define CreditCard class + var CreditCard = $resource('/user/:userId/card/:cardId', + {userId:123, cardId:'@id'}, { + charge: {method:'POST', params:{charge:true}} + }); + + // We can retrieve a collection from the server + var cards = CreditCard.query(function() { + // GET: /user/123/card + // server returns: [ {id:456, number:'1234', name:'Smith'} ]; + + var card = cards[0]; + // each item is an instance of CreditCard + expect(card instanceof CreditCard).toEqual(true); + card.name = "J. Smith"; + // non GET methods are mapped onto the instances + card.$save(); + // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} + // server returns: {id:456, number:'1234', name: 'J. Smith'}; + + // our custom method is mapped as well. + card.$charge({amount:9.99}); + // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} + }); + + // we can create an instance as well + var newCard = new CreditCard({number:'0123'}); + newCard.name = "Mike Smith"; + newCard.$save(); + // POST: /user/123/card {number:'0123', name:'Mike Smith'} + // server returns: {id:789, number:'0123', name: 'Mike Smith'}; + expect(newCard.id).toEqual(789); + * ``` + * + * The object returned from this function execution is a resource "class" which has "static" method + * for each action in the definition. + * + * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and + * `headers`. + * When the data is returned from the server then the object is an instance of the resource type and + * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD + * operations (create, read, update, delete) on server-side data. + + ```js + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}, function(user) { + user.abc = true; + user.$save(); + }); + ``` + * + * It's worth noting that the success callback for `get`, `query` and other methods gets passed + * in the response that came from the server as well as $http header getter function, so one + * could rewrite the above example and get access to http headers as: + * + ```js + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}, function(u, getResponseHeaders){ + u.abc = true; + u.$save(function(u, putResponseHeaders) { + //u => saved user object + //putResponseHeaders => $http header getter + }); + }); + ``` + * + * You can also access the raw `$http` promise via the `$promise` property on the object returned + * + ``` + var User = $resource('/user/:userId', {userId:'@id'}); + User.get({userId:123}) + .$promise.then(function(user) { + $scope.user = user; + }); + ``` + + * # Creating a custom 'PUT' request + * In this example we create a custom method on our resource to make a PUT request + * ```js + * var app = angular.module('app', ['ngResource', 'ngRoute']); + * + * // Some APIs expect a PUT request in the format URL/object/ID + * // Here we are creating an 'update' method + * app.factory('Notes', ['$resource', function($resource) { + * return $resource('/notes/:id', null, + * { + * 'update': { method:'PUT' } + * }); + * }]); + * + * // In our controller we get the ID from the URL using ngRoute and $routeParams + * // We pass in $routeParams and our Notes factory along with $scope + * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', + function($scope, $routeParams, Notes) { + * // First get a note object from the factory + * var note = Notes.get({ id:$routeParams.id }); + * $id = note.id; + * + * // Now call update passing in the ID first then the object you are updating + * Notes.update({ id:$id }, note); + * + * // This will PUT /notes/ID with the note object in the request payload + * }]); + * ``` + */ + angular.module('ngResource', ['ng']). + provider('$resource', function() { + var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/; + var provider = this; + + this.defaults = { + // Strip slashes by default + stripTrailingSlashes: true, + + // Default actions configuration + actions: { + 'get': {method: 'GET'}, + 'save': {method: 'POST'}, + 'query': {method: 'GET', isArray: true}, + 'remove': {method: 'DELETE'}, + 'delete': {method: 'DELETE'} + } + }; + + this.$get = ['$http', '$q', function($http, $q) { + + var noop = angular.noop, + forEach = angular.forEach, + extend = angular.extend, + copy = angular.copy, + isFunction = angular.isFunction; + + /** + * We need our custom method because encodeURIComponent is too aggressive and doesn't follow + * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set + * (pchar) allowed in path segments: + * segment = *pchar + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * pct-encoded = "%" HEXDIG HEXDIG + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriSegment(val) { + return encodeUriQuery(val, true). + replace(/%26/gi, '&'). + replace(/%3D/gi, '='). + replace(/%2B/gi, '+'); + } + + + /** + * This method is intended for encoding *key* or *value* parts of query component. We need a + * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't + * have to be encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ + function encodeUriQuery(val, pctEncodeSpaces) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); + } + + function Route(template, defaults) { + this.template = template; + this.defaults = extend({}, provider.defaults, defaults); + this.urlParams = {}; + } + + Route.prototype = { + setUrlParams: function(config, params, actionUrl) { + var self = this, + url = actionUrl || self.template, + val, + encodedVal, + protocolAndDomain = ''; + + var urlParams = self.urlParams = {}; + forEach(url.split(/\W/), function(param) { + if (param === 'hasOwnProperty') { + throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); + } + if (!(new RegExp("^\\d+$").test(param)) && param && + (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { + urlParams[param] = true; + } + }); + url = url.replace(/\\:/g, ':'); + url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) { + protocolAndDomain = match; + return ''; + }); + + params = params || {}; + forEach(self.urlParams, function(_, urlParam) { + val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; + if (angular.isDefined(val) && val !== null) { + encodedVal = encodeUriSegment(val); + url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { + return encodedVal + p1; + }); + } else { + url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, + leadingSlashes, tail) { + if (tail.charAt(0) == '/') { + return tail; + } else { + return leadingSlashes + tail; + } + }); + } + }); + + // strip trailing slashes and set the url (unless this behavior is specifically disabled) + if (self.defaults.stripTrailingSlashes) { + url = url.replace(/\/+$/, '') || '/'; + } + + // then replace collapse `/.` if found in the last URL path segment before the query + // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` + url = url.replace(/\/\.(?=\w+($|\?))/, '.'); + // replace escaped `/\.` with `/.` + config.url = protocolAndDomain + url.replace(/\/\\\./, '/.'); + + + // set params - delegate param encoding to $http + forEach(params, function(value, key) { + if (!self.urlParams[key]) { + config.params = config.params || {}; + config.params[key] = value; + } + }); + } + }; + + + function resourceFactory(url, paramDefaults, actions, options) { + var route = new Route(url, options); + + actions = extend({}, provider.defaults.actions, actions); + + function extractParams(data, actionParams) { + var ids = {}; + actionParams = extend({}, paramDefaults, actionParams); + forEach(actionParams, function(value, key) { + if (isFunction(value)) { value = value(); } + ids[key] = value && value.charAt && value.charAt(0) == '@' ? + lookupDottedPath(data, value.substr(1)) : value; + }); + return ids; + } + + function defaultResponseInterceptor(response) { + return response.resource; + } + + function Resource(value) { + shallowClearAndCopy(value || {}, this); + } + + Resource.prototype.toJSON = function() { + var data = extend({}, this); + delete data.$promise; + delete data.$resolved; + return data; + }; + + forEach(actions, function(action, name) { + var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); + + Resource[name] = function(a1, a2, a3, a4) { + var params = {}, data, success, error; + + /* jshint -W086 */ /* (purposefully fall through case statements) */ + switch (arguments.length) { + case 4: + error = a4; + success = a3; + //fallthrough + case 3: + case 2: + if (isFunction(a2)) { + if (isFunction(a1)) { + success = a1; + error = a2; + break; + } + + success = a2; + error = a3; + //fallthrough + } else { + params = a1; + data = a2; + success = a3; + break; + } + case 1: + if (isFunction(a1)) success = a1; + else if (hasBody) data = a1; + else params = a1; + break; + case 0: break; + default: + throw $resourceMinErr('badargs', + "Expected up to 4 arguments [params, data, success, error], got {0} arguments", + arguments.length); + } + /* jshint +W086 */ /* (purposefully fall through case statements) */ + + var isInstanceCall = this instanceof Resource; + var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); + var httpConfig = {}; + var responseInterceptor = action.interceptor && action.interceptor.response || + defaultResponseInterceptor; + var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || + undefined; + + forEach(action, function(value, key) { + switch (key) { + default: + httpConfig[key] = copy(value); + break; + case 'params': + case 'isArray': + case 'interceptor': + break; + case 'timeout': + httpConfig[key] = value; + break; + } + }); + + if (hasBody) httpConfig.data = data; + route.setUrlParams(httpConfig, + extend({}, extractParams(data, action.params || {}), params), + action.url); + + var promise = $http(httpConfig).then(function(response) { + var data = response.data, + promise = value.$promise; + + if (data) { + // Need to convert action.isArray to boolean in case it is undefined + // jshint -W018 + if (angular.isArray(data) !== (!!action.isArray)) { + throw $resourceMinErr('badcfg', + 'Error in resource configuration for action `{0}`. Expected response to ' + + 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object', + angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url); + } + // jshint +W018 + if (action.isArray) { + value.length = 0; + forEach(data, function(item) { + if (typeof item === "object") { + value.push(new Resource(item)); + } else { + // Valid JSON values may be string literals, and these should not be converted + // into objects. These items will not have access to the Resource prototype + // methods, but unfortunately there + value.push(item); + } + }); + } else { + shallowClearAndCopy(data, value); + value.$promise = promise; + } + } + + value.$resolved = true; + + response.resource = value; + + return response; + }, function(response) { + value.$resolved = true; + + (error || noop)(response); + + return $q.reject(response); + }); + + promise = promise.then( + function(response) { + var value = responseInterceptor(response); + (success || noop)(value, response.headers); + return value; + }, + responseErrorInterceptor); + + if (!isInstanceCall) { + // we are creating instance / collection + // - set the initial promise + // - return the instance / collection + value.$promise = promise; + value.$resolved = false; + + return value; + } + + // instance call + return promise; + }; + + + Resource.prototype['$' + name] = function(params, success, error) { + if (isFunction(params)) { + error = success; success = params; params = {}; + } + var result = Resource[name].call(this, params, this, success, error); + return result.$promise || result; + }; + }); + + Resource.bind = function(additionalParamDefaults) { + return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); + }; + + return Resource; + } + + return resourceFactory; + }]; + }); + + +})(window, window.angular); + +/** + * @license AngularJS v1.4.8 + * (c) 2010-2015 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + + /** + * @ngdoc module + * @name ngRoute + * @description + * + * # ngRoute + * + * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. + * + * ## Example + * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. + * + * + * <div doc-module-components="ngRoute"></div> + */ + /* global -ngRouteModule */ + var ngRouteModule = angular.module('ngRoute', ['ng']). + provider('$route', $RouteProvider), + $routeMinErr = angular.$$minErr('ngRoute'); + + /** + * @ngdoc provider + * @name $routeProvider + * + * @description + * + * Used for configuring routes. + * + * ## Example + * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. + * + * ## Dependencies + * Requires the {@link ngRoute `ngRoute`} module to be installed. + */ + function $RouteProvider() { + function inherit(parent, extra) { + return angular.extend(Object.create(parent), extra); + } + + var routes = {}; + + /** + * @ngdoc method + * @name $routeProvider#when + * + * @param {string} path Route path (matched against `$location.path`). If `$location.path` + * contains redundant trailing slash or is missing one, the route will still match and the + * `$location.path` will be updated to add or drop the trailing slash to exactly match the + * route definition. + * + * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up + * to the next slash are matched and stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain named groups starting with a colon and ending with a star: + * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` + * when the route matches. + * * `path` can contain optional named groups with a question mark: e.g.`:name?`. + * + * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match + * `/color/brown/largecode/code/with/slashes/edit` and extract: * * * `color: brown` * * `largecode: code/with/slashes`. @@ -39043,1550 +41513,4849 @@ * The custom `redirectTo` function is expected to return a string which will be used * to update `$location.path()` and `$location.search()`. * - * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` - * or `$location.hash()` changes. + * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` + * or `$location.hash()` changes. + * + * If the option is set to `false` and url in the browser changes, then + * `$routeUpdate` event is broadcasted on the root scope. + * + * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive + * + * If the option is set to `true`, then the particular route can be matched without being + * case sensitive + * + * @returns {Object} self + * + * @description + * Adds a new route definition to the `$route` service. + */ + this.when = function(path, route) { + //copy original route object to preserve params inherited from proto chain + var routeCopy = angular.copy(route); + if (angular.isUndefined(routeCopy.reloadOnSearch)) { + routeCopy.reloadOnSearch = true; + } + if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) { + routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch; + } + routes[path] = angular.extend( + routeCopy, + path && pathRegExp(path, routeCopy) + ); + + // create redirection for trailing slashes + if (path) { + var redirectPath = (path[path.length - 1] == '/') + ? path.substr(0, path.length - 1) + : path + '/'; + + routes[redirectPath] = angular.extend( + {redirectTo: path}, + pathRegExp(redirectPath, routeCopy) + ); + } + + return this; + }; + + /** + * @ngdoc property + * @name $routeProvider#caseInsensitiveMatch + * @description + * + * A boolean property indicating if routes defined + * using this provider should be matched using a case insensitive + * algorithm. Defaults to `false`. + */ + this.caseInsensitiveMatch = false; + + /** + * @param path {string} path + * @param opts {Object} options + * @return {?Object} * - * If the option is set to `false` and url in the browser changes, then - * `$routeUpdate` event is broadcasted on the root scope. + * @description + * Normalizes the given path, returning a regular expression + * and the original path. * - * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive + * Inspired by pathRexp in visionmedia/express/lib/utils.js. + */ + function pathRegExp(path, opts) { + var insensitive = opts.caseInsensitiveMatch, + ret = { + originalPath: path, + regexp: path + }, + keys = ret.keys = []; + + path = path + .replace(/([().])/g, '\\$1') + .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { + var optional = option === '?' ? option : null; + var star = option === '*' ? option : null; + keys.push({ name: key, optional: !!optional }); + slash = slash || ''; + return '' + + (optional ? '' : slash) + + '(?:' + + (optional ? slash : '') + + (star && '(.+?)' || '([^/]+)') + + (optional || '') + + ')' + + (optional || ''); + }) + .replace(/([\/$\*])/g, '\\$1'); + + ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); + return ret; + } + + /** + * @ngdoc method + * @name $routeProvider#otherwise * - * If the option is set to `true`, then the particular route can be matched without being - * case sensitive + * @description + * Sets route definition that will be used on route change when no other route definition + * is matched. * + * @param {Object|string} params Mapping information to be assigned to `$route.current`. + * If called with a string, the value maps to `redirectTo`. * @returns {Object} self - * - * @description - * Adds a new route definition to the `$route` service. */ - this.when = function(path, route) { - //copy original route object to preserve params inherited from proto chain - var routeCopy = angular.copy(route); - if (angular.isUndefined(routeCopy.reloadOnSearch)) { - routeCopy.reloadOnSearch = true; + this.otherwise = function(params) { + if (typeof params === 'string') { + params = {redirectTo: params}; + } + this.when(null, params); + return this; + }; + + + this.$get = ['$rootScope', + '$location', + '$routeParams', + '$q', + '$injector', + '$templateRequest', + '$sce', + function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { + + /** + * @ngdoc service + * @name $route + * @requires $location + * @requires $routeParams + * + * @property {Object} current Reference to the current route definition. + * The route definition contains: + * + * - `controller`: The controller constructor as define in route definition. + * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for + * controller instantiation. The `locals` contain + * the resolved values of the `resolve` map. Additionally the `locals` also contain: + * + * - `$scope` - The current route scope. + * - `$template` - The current route template HTML. + * + * @property {Object} routes Object with all route configuration Objects as its properties. + * + * @description + * `$route` is used for deep-linking URLs to controllers and views (HTML partials). + * It watches `$location.url()` and tries to map the path to an existing route definition. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. + * + * The `$route` service is typically used in conjunction with the + * {@link ngRoute.directive:ngView `ngView`} directive and the + * {@link ngRoute.$routeParams `$routeParams`} service. + * + * @example + * This example shows how changing the URL hash causes the `$route` to match a route against the + * URL, and the `ngView` pulls in the partial. + * + * <example name="$route-service" module="ngRouteExample" + * deps="angular-route.js" fixBase="true"> + * <file name="index.html"> + * <div ng-controller="MainController"> + * Choose: + * <a href="Book/Moby">Moby</a> | + * <a href="Book/Moby/ch/1">Moby: Ch1</a> | + * <a href="Book/Gatsby">Gatsby</a> | + * <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | + * <a href="Book/Scarlet">Scarlet Letter</a><br/> + * + * <div ng-view></div> + * + * <hr /> + * + * <pre>$location.path() = {{$location.path()}}</pre> + * <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> + * <pre>$route.current.params = {{$route.current.params}}</pre> + * <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> + * <pre>$routeParams = {{$routeParams}}</pre> + * </div> + * </file> + * + * <file name="book.html"> + * controller: {{name}}<br /> + * Book Id: {{params.bookId}}<br /> + * </file> + * + * <file name="chapter.html"> + * controller: {{name}}<br /> + * Book Id: {{params.bookId}}<br /> + * Chapter Id: {{params.chapterId}} + * </file> + * + * <file name="script.js"> + * angular.module('ngRouteExample', ['ngRoute']) + * + * .controller('MainController', function($scope, $route, $routeParams, $location) { + * $scope.$route = $route; + * $scope.$location = $location; + * $scope.$routeParams = $routeParams; + * }) + * + * .controller('BookController', function($scope, $routeParams) { + * $scope.name = "BookController"; + * $scope.params = $routeParams; + * }) + * + * .controller('ChapterController', function($scope, $routeParams) { + * $scope.name = "ChapterController"; + * $scope.params = $routeParams; + * }) + * + * .config(function($routeProvider, $locationProvider) { + * $routeProvider + * .when('/Book/:bookId', { + * templateUrl: 'book.html', + * controller: 'BookController', + * resolve: { + * // I will cause a 1 second delay + * delay: function($q, $timeout) { + * var delay = $q.defer(); + * $timeout(delay.resolve, 1000); + * return delay.promise; + * } + * } + * }) + * .when('/Book/:bookId/ch/:chapterId', { + * templateUrl: 'chapter.html', + * controller: 'ChapterController' + * }); + * + * // configure html5 to get links working on jsfiddle + * $locationProvider.html5Mode(true); + * }); + * + * </file> + * + * <file name="protractor.js" type="protractor"> + * it('should load and compile correct template', function() { + * element(by.linkText('Moby: Ch1')).click(); + * var content = element(by.css('[ng-view]')).getText(); + * expect(content).toMatch(/controller\: ChapterController/); + * expect(content).toMatch(/Book Id\: Moby/); + * expect(content).toMatch(/Chapter Id\: 1/); + * + * element(by.partialLinkText('Scarlet')).click(); + * + * content = element(by.css('[ng-view]')).getText(); + * expect(content).toMatch(/controller\: BookController/); + * expect(content).toMatch(/Book Id\: Scarlet/); + * }); + * </file> + * </example> + */ + + /** + * @ngdoc event + * @name $route#$routeChangeStart + * @eventType broadcast on root scope + * @description + * Broadcasted before a route change. At this point the route services starts + * resolving all of the dependencies needed for the route change to occur. + * Typically this involves fetching the view template as well as any dependencies + * defined in `resolve` route property. Once all of the dependencies are resolved + * `$routeChangeSuccess` is fired. + * + * The route change (and the `$location` change that triggered it) can be prevented + * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} + * for more details about event object. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} next Future route information. + * @param {Route} current Current route information. + */ + + /** + * @ngdoc event + * @name $route#$routeChangeSuccess + * @eventType broadcast on root scope + * @description + * Broadcasted after a route change has happened successfully. + * The `resolve` dependencies are now available in the `current.locals` property. + * + * {@link ngRoute.directive:ngView ngView} listens for the directive + * to instantiate the controller and render the view. + * + * @param {Object} angularEvent Synthetic event object. + * @param {Route} current Current route information. + * @param {Route|Undefined} previous Previous route information, or undefined if current is + * first route entered. + */ + + /** + * @ngdoc event + * @name $route#$routeChangeError + * @eventType broadcast on root scope + * @description + * Broadcasted if any of the resolve promises are rejected. + * + * @param {Object} angularEvent Synthetic event object + * @param {Route} current Current route information. + * @param {Route} previous Previous route information. + * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. + */ + + /** + * @ngdoc event + * @name $route#$routeUpdate + * @eventType broadcast on root scope + * @description + * The `reloadOnSearch` property has been set to false, and we are reusing the same + * instance of the Controller. + * + * @param {Object} angularEvent Synthetic event object + * @param {Route} current Current/previous route information. + */ + + var forceReload = false, + preparedRoute, + preparedRouteIsUpdateOnly, + $route = { + routes: routes, + + /** + * @ngdoc method + * @name $route#reload + * + * @description + * Causes `$route` service to reload the current route even if + * {@link ng.$location $location} hasn't changed. + * + * As a result of that, {@link ngRoute.directive:ngView ngView} + * creates new scope and reinstantiates the controller. + */ + reload: function() { + forceReload = true; + $rootScope.$evalAsync(function() { + // Don't support cancellation of a reload for now... + prepareRoute(); + commitRoute(); + }); + }, + + /** + * @ngdoc method + * @name $route#updateParams + * + * @description + * Causes `$route` service to update the current URL, replacing + * current route parameters with those specified in `newParams`. + * Provided property names that match the route's path segment + * definitions will be interpolated into the location's path, while + * remaining properties will be treated as query params. + * + * @param {!Object<string, string>} newParams mapping of URL parameter names to values + */ + updateParams: function(newParams) { + if (this.current && this.current.$$route) { + newParams = angular.extend({}, this.current.params, newParams); + $location.path(interpolate(this.current.$$route.originalPath, newParams)); + // interpolate modifies newParams, only query params are left + $location.search(newParams); + } else { + throw $routeMinErr('norout', 'Tried updating route when with no current route'); + } + } + }; + + $rootScope.$on('$locationChangeStart', prepareRoute); + $rootScope.$on('$locationChangeSuccess', commitRoute); + + return $route; + + ///////////////////////////////////////////////////// + + /** + * @param on {string} current url + * @param route {Object} route regexp to match the url against + * @return {?Object} + * + * @description + * Check if the route matches the current url. + * + * Inspired by match in + * visionmedia/express/lib/router/router.js. + */ + function switchRouteMatcher(on, route) { + var keys = route.keys, + params = {}; + + if (!route.regexp) return null; + + var m = route.regexp.exec(on); + if (!m) return null; + + for (var i = 1, len = m.length; i < len; ++i) { + var key = keys[i - 1]; + + var val = m[i]; + + if (key && val) { + params[key.name] = val; + } + } + return params; + } + + function prepareRoute($locationEvent) { + var lastRoute = $route.current; + + preparedRoute = parseRoute(); + preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route + && angular.equals(preparedRoute.pathParams, lastRoute.pathParams) + && !preparedRoute.reloadOnSearch && !forceReload; + + if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { + if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { + if ($locationEvent) { + $locationEvent.preventDefault(); + } + } + } + } + + function commitRoute() { + var lastRoute = $route.current; + var nextRoute = preparedRoute; + + if (preparedRouteIsUpdateOnly) { + lastRoute.params = nextRoute.params; + angular.copy(lastRoute.params, $routeParams); + $rootScope.$broadcast('$routeUpdate', lastRoute); + } else if (nextRoute || lastRoute) { + forceReload = false; + $route.current = nextRoute; + if (nextRoute) { + if (nextRoute.redirectTo) { + if (angular.isString(nextRoute.redirectTo)) { + $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) + .replace(); + } else { + $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) + .replace(); + } + } + } + + $q.when(nextRoute). + then(function() { + if (nextRoute) { + var locals = angular.extend({}, nextRoute.resolve), + template, templateUrl; + + angular.forEach(locals, function(value, key) { + locals[key] = angular.isString(value) ? + $injector.get(value) : $injector.invoke(value, null, null, key); + }); + + if (angular.isDefined(template = nextRoute.template)) { + if (angular.isFunction(template)) { + template = template(nextRoute.params); + } + } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { + if (angular.isFunction(templateUrl)) { + templateUrl = templateUrl(nextRoute.params); + } + if (angular.isDefined(templateUrl)) { + nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl); + template = $templateRequest(templateUrl); + } + } + if (angular.isDefined(template)) { + locals['$template'] = template; + } + return $q.all(locals); + } + }). + then(function(locals) { + // after route change + if (nextRoute == $route.current) { + if (nextRoute) { + nextRoute.locals = locals; + angular.copy(nextRoute.params, $routeParams); + } + $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); + } + }, function(error) { + if (nextRoute == $route.current) { + $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); + } + }); + } + } + + + /** + * @returns {Object} the current active route, by matching it against the URL + */ + function parseRoute() { + // Match a route + var params, match; + angular.forEach(routes, function(route, path) { + if (!match && (params = switchRouteMatcher($location.path(), route))) { + match = inherit(route, { + params: angular.extend({}, $location.search(), params), + pathParams: params}); + match.$$route = route; + } + }); + // No route matched; fallback to "otherwise" route + return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); + } + + /** + * @returns {string} interpolation of the redirect path with the parameters + */ + function interpolate(string, params) { + var result = []; + angular.forEach((string || '').split(':'), function(segment, i) { + if (i === 0) { + result.push(segment); + } else { + var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/); + var key = segmentMatch[1]; + result.push(params[key]); + result.push(segmentMatch[2] || ''); + delete params[key]; + } + }); + return result.join(''); + } + }]; + } + + ngRouteModule.provider('$routeParams', $RouteParamsProvider); + + + /** + * @ngdoc service + * @name $routeParams + * @requires $route + * + * @description + * The `$routeParams` service allows you to retrieve the current set of route parameters. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * The route parameters are a combination of {@link ng.$location `$location`}'s + * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. + * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. + * + * In case of parameter name collision, `path` params take precedence over `search` params. + * + * The service guarantees that the identity of the `$routeParams` object will remain unchanged + * (but its properties will likely change) even when a route change occurs. + * + * Note that the `$routeParams` are only updated *after* a route change completes successfully. + * This means that you cannot rely on `$routeParams` being correct in route resolve functions. + * Instead you can use `$route.current.params` to access the new route's parameters. + * + * @example + * ```js + * // Given: + * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby + * // Route: /Chapter/:chapterId/Section/:sectionId + * // + * // Then + * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} + * ``` + */ + function $RouteParamsProvider() { + this.$get = function() { return {}; }; + } + + ngRouteModule.directive('ngView', ngViewFactory); + ngRouteModule.directive('ngView', ngViewFillContentFactory); + + + /** + * @ngdoc directive + * @name ngView + * @restrict ECA + * + * @description + * # Overview + * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by + * including the rendered template of the current route into the main layout (`index.html`) file. + * Every time the current route changes, the included view changes with it according to the + * configuration of the `$route` service. + * + * Requires the {@link ngRoute `ngRoute`} module to be installed. + * + * @animations + * enter - animation is used to bring new content into the browser. + * leave - animation is used to animate existing content away. + * + * The enter and leave animation occur concurrently. + * + * @scope + * @priority 400 + * @param {string=} onload Expression to evaluate whenever the view updates. + * + * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll + * $anchorScroll} to scroll the viewport after the view is updated. + * + * - If the attribute is not set, disable scrolling. + * - If the attribute is set without value, enable scrolling. + * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated + * as an expression yields a truthy value. + * @example + <example name="ngView-directive" module="ngViewExample" + deps="angular-route.js;angular-animate.js" + animations="true" fixBase="true"> + <file name="index.html"> + <div ng-controller="MainCtrl as main"> + Choose: + <a href="Book/Moby">Moby</a> | + <a href="Book/Moby/ch/1">Moby: Ch1</a> | + <a href="Book/Gatsby">Gatsby</a> | + <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | + <a href="Book/Scarlet">Scarlet Letter</a><br/> + + <div class="view-animate-container"> + <div ng-view class="view-animate"></div> + </div> + <hr /> + + <pre>$location.path() = {{main.$location.path()}}</pre> + <pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> + <pre>$route.current.params = {{main.$route.current.params}}</pre> + <pre>$routeParams = {{main.$routeParams}}</pre> + </div> + </file> + + <file name="book.html"> + <div> + controller: {{book.name}}<br /> + Book Id: {{book.params.bookId}}<br /> + </div> + </file> + + <file name="chapter.html"> + <div> + controller: {{chapter.name}}<br /> + Book Id: {{chapter.params.bookId}}<br /> + Chapter Id: {{chapter.params.chapterId}} + </div> + </file> + + <file name="animations.css"> + .view-animate-container { + position:relative; + height:100px!important; + background:white; + border:1px solid black; + height:40px; + overflow:hidden; + } + + .view-animate { + padding:10px; + } + + .view-animate.ng-enter, .view-animate.ng-leave { + transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + + display:block; + width:100%; + border-left:1px solid black; + + position:absolute; + top:0; + left:0; + right:0; + bottom:0; + padding:10px; + } + + .view-animate.ng-enter { + left:100%; + } + .view-animate.ng-enter.ng-enter-active { + left:0; + } + .view-animate.ng-leave.ng-leave-active { + left:-100%; + } + </file> + + <file name="script.js"> + angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) + .config(['$routeProvider', '$locationProvider', + function($routeProvider, $locationProvider) { + $routeProvider + .when('/Book/:bookId', { + templateUrl: 'book.html', + controller: 'BookCtrl', + controllerAs: 'book' + }) + .when('/Book/:bookId/ch/:chapterId', { + templateUrl: 'chapter.html', + controller: 'ChapterCtrl', + controllerAs: 'chapter' + }); + + $locationProvider.html5Mode(true); + }]) + .controller('MainCtrl', ['$route', '$routeParams', '$location', + function($route, $routeParams, $location) { + this.$route = $route; + this.$location = $location; + this.$routeParams = $routeParams; + }]) + .controller('BookCtrl', ['$routeParams', function($routeParams) { + this.name = "BookCtrl"; + this.params = $routeParams; + }]) + .controller('ChapterCtrl', ['$routeParams', function($routeParams) { + this.name = "ChapterCtrl"; + this.params = $routeParams; + }]); + + </file> + + <file name="protractor.js" type="protractor"> + it('should load and compile correct template', function() { + element(by.linkText('Moby: Ch1')).click(); + var content = element(by.css('[ng-view]')).getText(); + expect(content).toMatch(/controller\: ChapterCtrl/); + expect(content).toMatch(/Book Id\: Moby/); + expect(content).toMatch(/Chapter Id\: 1/); + + element(by.partialLinkText('Scarlet')).click(); + + content = element(by.css('[ng-view]')).getText(); + expect(content).toMatch(/controller\: BookCtrl/); + expect(content).toMatch(/Book Id\: Scarlet/); + }); + </file> + </example> + */ + + + /** + * @ngdoc event + * @name ngView#$viewContentLoaded + * @eventType emit on the current ngView scope + * @description + * Emitted every time the ngView content is reloaded. + */ + ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; + function ngViewFactory($route, $anchorScroll, $animate) { + return { + restrict: 'ECA', + terminal: true, + priority: 400, + transclude: 'element', + link: function(scope, $element, attr, ctrl, $transclude) { + var currentScope, + currentElement, + previousLeaveAnimation, + autoScrollExp = attr.autoscroll, + onloadExp = attr.onload || ''; + + scope.$on('$routeChangeSuccess', update); + update(); + + function cleanupLastView() { + if (previousLeaveAnimation) { + $animate.cancel(previousLeaveAnimation); + previousLeaveAnimation = null; + } + + if (currentScope) { + currentScope.$destroy(); + currentScope = null; + } + if (currentElement) { + previousLeaveAnimation = $animate.leave(currentElement); + previousLeaveAnimation.then(function() { + previousLeaveAnimation = null; + }); + currentElement = null; + } + } + + function update() { + var locals = $route.current && $route.current.locals, + template = locals && locals.$template; + + if (angular.isDefined(template)) { + var newScope = scope.$new(); + var current = $route.current; + + // Note: This will also link all children of ng-view that were contained in the original + // html. If that content contains controllers, ... they could pollute/change the scope. + // However, using ng-view on an element with additional content does not make sense... + // Note: We can't remove them in the cloneAttchFn of $transclude as that + // function is called before linking the content, which would apply child + // directives to non existing elements. + var clone = $transclude(newScope, function(clone) { + $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { + if (angular.isDefined(autoScrollExp) + && (!autoScrollExp || scope.$eval(autoScrollExp))) { + $anchorScroll(); + } + }); + cleanupLastView(); + }); + + currentElement = clone; + currentScope = current.scope = newScope; + currentScope.$emit('$viewContentLoaded'); + currentScope.$eval(onloadExp); + } else { + cleanupLastView(); + } + } + } + }; + } + +// This directive is called during the $transclude call of the first `ngView` directive. +// It will replace and compile the content of the element with the loaded template. +// We need this directive so that the element content is already filled when +// the link function of another directive on the same element as ngView +// is called. + ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; + function ngViewFillContentFactory($compile, $controller, $route) { + return { + restrict: 'ECA', + priority: -400, + link: function(scope, $element) { + var current = $route.current, + locals = current.locals; + + $element.html(locals.$template); + + var link = $compile($element.contents()); + + if (current.controller) { + locals.$scope = scope; + var controller = $controller(current.controller, locals); + if (current.controllerAs) { + scope[current.controllerAs] = controller; + } + $element.data('$ngControllerController', controller); + $element.children().data('$ngControllerController', controller); + } + + link(scope); + } + }; + } + + +})(window, window.angular); + +/** + * @license AngularJS v1.4.8 + * (c) 2010-2015 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular, undefined) {'use strict'; + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + var $sanitizeMinErr = angular.$$minErr('$sanitize'); + + /** + * @ngdoc module + * @name ngSanitize + * @description + * + * # ngSanitize + * + * The `ngSanitize` module provides functionality to sanitize HTML. + * + * + * <div doc-module-components="ngSanitize"></div> + * + * See {@link ngSanitize.$sanitize `$sanitize`} for usage. + */ + + /* + * HTML Parser By Misko Hevery (misko@hevery.com) + * based on: HTML Parser By John Resig (ejohn.org) + * Original code by Erik Arvidsson, Mozilla Public License + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + * + * // Use like so: + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + */ + + + /** + * @ngdoc service + * @name $sanitize + * @kind function + * + * @description + * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are + * then serialized back to properly escaped html string. This means that no unsafe input can make + * it into the returned string, however, since our parser is more strict than a typical browser + * parser, it's possible that some obscure input, which would be recognized as valid HTML by a + * browser, won't make it through the sanitizer. The input may also contain SVG markup. + * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and + * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. + * + * @param {string} html HTML input. + * @returns {string} Sanitized HTML. + * + * @example + <example module="sanitizeExample" deps="angular-sanitize.js"> + <file name="index.html"> + <script> + angular.module('sanitizeExample', ['ngSanitize']) + .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) { + $scope.snippet = + '<p style="color:blue">an html\n' + + '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + + 'snippet</p>'; + $scope.deliberatelyTrustDangerousSnippet = function() { + return $sce.trustAsHtml($scope.snippet); + }; + }]); + </script> + <div ng-controller="ExampleController"> + Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> + <table> + <tr> + <td>Directive</td> + <td>How</td> + <td>Source</td> + <td>Rendered</td> + </tr> + <tr id="bind-html-with-sanitize"> + <td>ng-bind-html</td> + <td>Automatically uses $sanitize</td> + <td><pre><div ng-bind-html="snippet"><br/></div></pre></td> + <td><div ng-bind-html="snippet"></div></td> + </tr> + <tr id="bind-html-with-trust"> + <td>ng-bind-html</td> + <td>Bypass $sanitize by explicitly trusting the dangerous value</td> + <td> + <pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()"> + </div></pre> + </td> + <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td> + </tr> + <tr id="bind-default"> + <td>ng-bind</td> + <td>Automatically escapes</td> + <td><pre><div ng-bind="snippet"><br/></div></pre></td> + <td><div ng-bind="snippet"></div></td> + </tr> + </table> + </div> + </file> + <file name="protractor.js" type="protractor"> + it('should sanitize the html snippet by default', function() { + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); + }); + + it('should inline raw snippet if bound to a trusted value', function() { + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). + toBe("<p style=\"color:blue\">an html\n" + + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + + "snippet</p>"); + }); + + it('should escape snippet without any filter', function() { + expect(element(by.css('#bind-default div')).getInnerHtml()). + toBe("<p style=\"color:blue\">an html\n" + + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + + "snippet</p>"); + }); + + it('should update', function() { + element(by.model('snippet')).clear(); + element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>'); + expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). + toBe('new <b>text</b>'); + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( + 'new <b onclick="alert(1)">text</b>'); + expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( + "new <b onclick=\"alert(1)\">text</b>"); + }); + </file> + </example> + */ + function $SanitizeProvider() { + this.$get = ['$$sanitizeUri', function($$sanitizeUri) { + return function(html) { + var buf = []; + htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { + return !/^unsafe/.test($$sanitizeUri(uri, isImage)); + })); + return buf.join(''); + }; + }]; + } + + function sanitizeText(chars) { + var buf = []; + var writer = htmlSanitizeWriter(buf, angular.noop); + writer.chars(chars); + return buf.join(''); + } + + +// Regular Expressions for parsing tags and attributes + var START_TAG_REGEXP = + /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/, + END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/, + ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, + BEGIN_TAG_REGEXP = /^</, + BEGING_END_TAGE_REGEXP = /^<\//, + COMMENT_REGEXP = /<!--(.*?)-->/g, + DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i, + CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g, + SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, + // Match everything outside of normal chars and " (quote character) + NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; + + +// Good source of info about elements and attributes +// http://dev.w3.org/html5/spec/Overview.html#semantics +// http://simon.html5.org/html-elements + +// Safe Void Elements - HTML5 +// http://dev.w3.org/html5/spec/Overview.html#void-elements + var voidElements = makeMap("area,br,col,hr,img,wbr"); + +// Elements that you can, intentionally, leave open (and which close themselves) +// http://dev.w3.org/html5/spec/Overview.html#optional-tags + var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), + optionalEndTagInlineElements = makeMap("rp,rt"), + optionalEndTagElements = angular.extend({}, + optionalEndTagInlineElements, + optionalEndTagBlockElements); + +// Safe Block Elements - HTML5 + var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + + "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + + "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); + +// Inline Elements - HTML5 + var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + + "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + + "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); + +// SVG Elements +// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements +// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted. +// They can potentially allow for arbitrary javascript to be executed. See #11290 + var svgElements = makeMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," + + "hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," + + "radialGradient,rect,stop,svg,switch,text,title,tspan,use"); + +// Special Elements (can contain anything) + var specialElements = makeMap("script,style"); + + var validElements = angular.extend({}, + voidElements, + blockElements, + inlineElements, + optionalEndTagElements, + svgElements); + +//Attributes that have href and hence need to be sanitized + var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href"); + + var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + + 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + + 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + + 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' + + 'valign,value,vspace,width'); + +// SVG attributes (without "id" and "name" attributes) +// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes + var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + + 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' + + 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' + + 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' + + 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' + + 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' + + 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' + + 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' + + 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' + + 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' + + 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' + + 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' + + 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' + + 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' + + 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true); + + var validAttrs = angular.extend({}, + uriAttrs, + svgAttrs, + htmlAttrs); + + function makeMap(str, lowercaseKeys) { + var obj = {}, items = str.split(','), i; + for (i = 0; i < items.length; i++) { + obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true; + } + return obj; + } + + + /** + * @example + * htmlParser(htmlString, { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * }); + * + * @param {string} html string + * @param {object} handler + */ + function htmlParser(html, handler) { + if (typeof html !== 'string') { + if (html === null || typeof html === 'undefined') { + html = ''; + } else { + html = '' + html; + } + } + var index, chars, match, stack = [], last = html, text; + stack.last = function() { return stack[stack.length - 1]; }; + + while (html) { + text = ''; + chars = true; + + // Make sure we're not in a script or style element + if (!stack.last() || !specialElements[stack.last()]) { + + // Comment + if (html.indexOf("<!--") === 0) { + // comments containing -- are not allowed unless they terminate the comment + index = html.indexOf("--", 4); + + if (index >= 0 && html.lastIndexOf("-->", index) === index) { + if (handler.comment) handler.comment(html.substring(4, index)); + html = html.substring(index + 3); + chars = false; + } + // DOCTYPE + } else if (DOCTYPE_REGEXP.test(html)) { + match = html.match(DOCTYPE_REGEXP); + + if (match) { + html = html.replace(match[0], ''); + chars = false; + } + // end tag + } else if (BEGING_END_TAGE_REGEXP.test(html)) { + match = html.match(END_TAG_REGEXP); + + if (match) { + html = html.substring(match[0].length); + match[0].replace(END_TAG_REGEXP, parseEndTag); + chars = false; + } + + // start tag + } else if (BEGIN_TAG_REGEXP.test(html)) { + match = html.match(START_TAG_REGEXP); + + if (match) { + // We only have a valid start-tag if there is a '>'. + if (match[4]) { + html = html.substring(match[0].length); + match[0].replace(START_TAG_REGEXP, parseStartTag); + } + chars = false; + } else { + // no ending tag found --- this piece should be encoded as an entity. + text += '<'; + html = html.substring(1); + } + } + + if (chars) { + index = html.indexOf("<"); + + text += index < 0 ? html : html.substring(0, index); + html = index < 0 ? "" : html.substring(index); + + if (handler.chars) handler.chars(decodeEntities(text)); + } + + } else { + // IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w]. + html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), + function(all, text) { + text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); + + if (handler.chars) handler.chars(decodeEntities(text)); + + return ""; + }); + + parseEndTag("", stack.last()); + } + + if (html == last) { + throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + + "of html: {0}", html); + } + last = html; + } + + // Clean up any remaining tags + parseEndTag(); + + function parseStartTag(tag, tagName, rest, unary) { + tagName = angular.lowercase(tagName); + if (blockElements[tagName]) { + while (stack.last() && inlineElements[stack.last()]) { + parseEndTag("", stack.last()); + } + } + + if (optionalEndTagElements[tagName] && stack.last() == tagName) { + parseEndTag("", tagName); + } + + unary = voidElements[tagName] || !!unary; + + if (!unary) { + stack.push(tagName); + } + + var attrs = {}; + + rest.replace(ATTR_REGEXP, + function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { + var value = doubleQuotedValue + || singleQuotedValue + || unquotedValue + || ''; + + attrs[name] = decodeEntities(value); + }); + if (handler.start) handler.start(tagName, attrs, unary); + } + + function parseEndTag(tag, tagName) { + var pos = 0, i; + tagName = angular.lowercase(tagName); + if (tagName) { + // Find the closest opened tag of the same type + for (pos = stack.length - 1; pos >= 0; pos--) { + if (stack[pos] == tagName) break; + } + } + + if (pos >= 0) { + // Close all the open elements, up the stack + for (i = stack.length - 1; i >= pos; i--) + if (handler.end) handler.end(stack[i]); + + // Remove the open elements from the stack + stack.length = pos; + } + } + } + + var hiddenPre=document.createElement("pre"); + /** + * decodes all entities into regular string + * @param value + * @returns {string} A string with decoded entities. + */ + function decodeEntities(value) { + if (!value) { return ''; } + + hiddenPre.innerHTML = value.replace(/</g,"<"); + // innerText depends on styling as it doesn't display hidden elements. + // Therefore, it's better to use textContent not to cause unnecessary reflows. + return hiddenPre.textContent; + } + + /** + * Escapes all potentially dangerous characters, so that the + * resulting string can be safely inserted into attribute or + * element text. + * @param value + * @returns {string} escaped text + */ + function encodeEntities(value) { + return value. + replace(/&/g, '&'). + replace(SURROGATE_PAIR_REGEXP, function(value) { + var hi = value.charCodeAt(0); + var low = value.charCodeAt(1); + return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; + }). + replace(NON_ALPHANUMERIC_REGEXP, function(value) { + return '&#' + value.charCodeAt(0) + ';'; + }). + replace(/</g, '<'). + replace(/>/g, '>'); + } + + /** + * create an HTML/XML writer which writes to buffer + * @param {Array} buf use buf.jain('') to get out sanitized html string + * @returns {object} in the form of { + * start: function(tag, attrs, unary) {}, + * end: function(tag) {}, + * chars: function(text) {}, + * comment: function(text) {} + * } + */ + function htmlSanitizeWriter(buf, uriValidator) { + var ignore = false; + var out = angular.bind(buf, buf.push); + return { + start: function(tag, attrs, unary) { + tag = angular.lowercase(tag); + if (!ignore && specialElements[tag]) { + ignore = tag; + } + if (!ignore && validElements[tag] === true) { + out('<'); + out(tag); + angular.forEach(attrs, function(value, key) { + var lkey=angular.lowercase(key); + var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); + if (validAttrs[lkey] === true && + (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { + out(' '); + out(key); + out('="'); + out(encodeEntities(value)); + out('"'); + } + }); + out(unary ? '/>' : '>'); + } + }, + end: function(tag) { + tag = angular.lowercase(tag); + if (!ignore && validElements[tag] === true) { + out('</'); + out(tag); + out('>'); + } + if (tag == ignore) { + ignore = false; + } + }, + chars: function(chars) { + if (!ignore) { + out(encodeEntities(chars)); + } } - if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) { - routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch; + }; + } + + +// define ngSanitize module and register $sanitize service + angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); + + /* global sanitizeText: false */ + + /** + * @ngdoc filter + * @name linky + * @kind function + * + * @description + * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and + * plain email address links. + * + * Requires the {@link ngSanitize `ngSanitize`} module to be installed. + * + * @param {string} text Input text. + * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. + * @returns {string} Html-linkified text. + * + * @usage + <span ng-bind-html="linky_expression | linky"></span> + * + * @example + <example module="linkyExample" deps="angular-sanitize.js"> + <file name="index.html"> + <script> + angular.module('linkyExample', ['ngSanitize']) + .controller('ExampleController', ['$scope', function($scope) { + $scope.snippet = + 'Pretty text with some links:\n'+ + 'http://angularjs.org/,\n'+ + 'mailto:us@somewhere.org,\n'+ + 'another@somewhere.org,\n'+ + 'and one more: ftp://127.0.0.1/.'; + $scope.snippetWithTarget = 'http://angularjs.org/'; + }]); + </script> + <div ng-controller="ExampleController"> + Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> + <table> + <tr> + <td>Filter</td> + <td>Source</td> + <td>Rendered</td> + </tr> + <tr id="linky-filter"> + <td>linky filter</td> + <td> + <pre><div ng-bind-html="snippet | linky"><br></div></pre> + </td> + <td> + <div ng-bind-html="snippet | linky"></div> + </td> + </tr> + <tr id="linky-target"> + <td>linky target</td> + <td> + <pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre> + </td> + <td> + <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div> + </td> + </tr> + <tr id="escaped-html"> + <td>no filter</td> + <td><pre><div ng-bind="snippet"><br></div></pre></td> + <td><div ng-bind="snippet"></div></td> + </tr> + </table> + </file> + <file name="protractor.js" type="protractor"> + it('should linkify the snippet with urls', function() { + expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). + toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); + expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); + }); + + it('should not linkify snippet without the linky filter', function() { + expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). + toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + + 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); + expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); + }); + + it('should update', function() { + element(by.model('snippet')).clear(); + element(by.model('snippet')).sendKeys('new http://link.'); + expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). + toBe('new http://link.'); + expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); + expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) + .toBe('new http://link.'); + }); + + it('should work with the target property', function() { + expect(element(by.id('linky-target')). + element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). + toBe('http://angularjs.org/'); + expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); + }); + </file> + </example> + */ + angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { + var LINKY_URL_REGEXP = + /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, + MAILTO_REGEXP = /^mailto:/i; + + return function(text, target) { + if (!text) return text; + var match; + var raw = text; + var html = []; + var url; + var i; + while ((match = raw.match(LINKY_URL_REGEXP))) { + // We can not end in these as they are sometimes found at the end of the sentence + url = match[0]; + // if we did not match ftp/http/www/mailto then assume mailto + if (!match[2] && !match[4]) { + url = (match[3] ? 'http://' : 'mailto:') + url; + } + i = match.index; + addText(raw.substr(0, i)); + addLink(url, match[0].replace(MAILTO_REGEXP, '')); + raw = raw.substring(i + match[0].length); } - routes[path] = angular.extend( - routeCopy, - path && pathRegExp(path, routeCopy) - ); + addText(raw); + return $sanitize(html.join('')); + + function addText(text) { + if (!text) { + return; + } + html.push(sanitizeText(text)); + } + + function addLink(url, text) { + html.push('<a '); + if (angular.isDefined(target)) { + html.push('target="', + target, + '" '); + } + html.push('href="', + url.replace(/"/g, '"'), + '">'); + addText(text); + html.push('</a>'); + } + }; + }]); + + +})(window, window.angular); + +/* +Copyright (c) 2008-2015 Pivotal Labs + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +var getJasmineRequireObj = (function (jasmineGlobal) { + var jasmineRequire; + + if (typeof module !== 'undefined' && module.exports) { + jasmineGlobal = global; + jasmineRequire = exports; + } else { + if (typeof window !== 'undefined' && typeof window.toString === 'function' && window.toString() === '[object GjsGlobal]') { + jasmineGlobal = window; + } + jasmineRequire = jasmineGlobal.jasmineRequire = jasmineGlobal.jasmineRequire || {}; + } + + function getJasmineRequire() { + return jasmineRequire; + } + + getJasmineRequire().core = function(jRequire) { + var j$ = {}; + + jRequire.base(j$, jasmineGlobal); + j$.util = jRequire.util(); + j$.errors = jRequire.errors(); + j$.Any = jRequire.Any(j$); + j$.Anything = jRequire.Anything(j$); + j$.CallTracker = jRequire.CallTracker(); + j$.MockDate = jRequire.MockDate(); + j$.Clock = jRequire.Clock(); + j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); + j$.Env = jRequire.Env(j$); + j$.ExceptionFormatter = jRequire.ExceptionFormatter(); + j$.Expectation = jRequire.Expectation(); + j$.buildExpectationResult = jRequire.buildExpectationResult(); + j$.JsApiReporter = jRequire.JsApiReporter(); + j$.matchersUtil = jRequire.matchersUtil(j$); + j$.ObjectContaining = jRequire.ObjectContaining(j$); + j$.ArrayContaining = jRequire.ArrayContaining(j$); + j$.pp = jRequire.pp(j$); + j$.QueueRunner = jRequire.QueueRunner(j$); + j$.ReportDispatcher = jRequire.ReportDispatcher(); + j$.Spec = jRequire.Spec(j$); + j$.SpyRegistry = jRequire.SpyRegistry(j$); + j$.SpyStrategy = jRequire.SpyStrategy(); + j$.StringMatching = jRequire.StringMatching(j$); + j$.Suite = jRequire.Suite(j$); + j$.Timer = jRequire.Timer(); + j$.TreeProcessor = jRequire.TreeProcessor(); + j$.version = jRequire.version(); + + j$.matchers = jRequire.requireMatchers(jRequire, j$); + + return j$; + }; + + return getJasmineRequire; +})(this); + +getJasmineRequireObj().requireMatchers = function(jRequire, j$) { + var availableMatchers = [ + 'toBe', + 'toBeCloseTo', + 'toBeDefined', + 'toBeFalsy', + 'toBeGreaterThan', + 'toBeLessThan', + 'toBeNaN', + 'toBeNull', + 'toBeTruthy', + 'toBeUndefined', + 'toContain', + 'toEqual', + 'toHaveBeenCalled', + 'toHaveBeenCalledWith', + 'toMatch', + 'toThrow', + 'toThrowError' + ], + matchers = {}; + + for (var i = 0; i < availableMatchers.length; i++) { + var name = availableMatchers[i]; + matchers[name] = jRequire[name](j$); + } + + return matchers; +}; + +getJasmineRequireObj().base = function(j$, jasmineGlobal) { + j$.unimplementedMethod_ = function() { + throw new Error('unimplemented method'); + }; + + j$.MAX_PRETTY_PRINT_DEPTH = 40; + j$.MAX_PRETTY_PRINT_ARRAY_LENGTH = 100; + j$.DEFAULT_TIMEOUT_INTERVAL = 5000; + + j$.getGlobal = function() { + return jasmineGlobal; + }; + + j$.getEnv = function(options) { + var env = j$.currentEnv_ = j$.currentEnv_ || new j$.Env(options); + //jasmine. singletons in here (setTimeout blah blah). + return env; + }; + + j$.isArray_ = function(value) { + return j$.isA_('Array', value); + }; + + j$.isString_ = function(value) { + return j$.isA_('String', value); + }; + + j$.isNumber_ = function(value) { + return j$.isA_('Number', value); + }; + + j$.isA_ = function(typeName, value) { + return Object.prototype.toString.apply(value) === '[object ' + typeName + ']'; + }; + + j$.isDomNode = function(obj) { + return obj.nodeType > 0; + }; + + j$.fnNameFor = function(func) { + return func.name || func.toString().match(/^\s*function\s*(\w*)\s*\(/)[1]; + }; + + j$.any = function(clazz) { + return new j$.Any(clazz); + }; + + j$.anything = function() { + return new j$.Anything(); + }; + + j$.objectContaining = function(sample) { + return new j$.ObjectContaining(sample); + }; + + j$.stringMatching = function(expected) { + return new j$.StringMatching(expected); + }; + + j$.arrayContaining = function(sample) { + return new j$.ArrayContaining(sample); + }; + + j$.createSpy = function(name, originalFn) { + + var spyStrategy = new j$.SpyStrategy({ + name: name, + fn: originalFn, + getSpy: function() { return spy; } + }), + callTracker = new j$.CallTracker(), + spy = function() { + var callData = { + object: this, + args: Array.prototype.slice.apply(arguments) + }; + + callTracker.track(callData); + var returnValue = spyStrategy.exec.apply(this, arguments); + callData.returnValue = returnValue; + + return returnValue; + }; + + for (var prop in originalFn) { + if (prop === 'and' || prop === 'calls') { + throw new Error('Jasmine spies would overwrite the \'and\' and \'calls\' properties on the object being spied upon'); + } + + spy[prop] = originalFn[prop]; + } + + spy.and = spyStrategy; + spy.calls = callTracker; + + return spy; + }; + + j$.isSpy = function(putativeSpy) { + if (!putativeSpy) { + return false; + } + return putativeSpy.and instanceof j$.SpyStrategy && + putativeSpy.calls instanceof j$.CallTracker; + }; + + j$.createSpyObj = function(baseName, methodNames) { + if (j$.isArray_(baseName) && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + if (!j$.isArray_(methodNames) || methodNames.length === 0) { + throw 'createSpyObj requires a non-empty array of method names to create spies for'; + } + var obj = {}; + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + } + return obj; + }; +}; + +getJasmineRequireObj().util = function() { + + var util = {}; + + util.inherit = function(childClass, parentClass) { + var Subclass = function() { + }; + Subclass.prototype = parentClass.prototype; + childClass.prototype = new Subclass(); + }; + + util.htmlEscape = function(str) { + if (!str) { + return str; + } + return str.replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>'); + }; + + util.argsToArray = function(args) { + var arrayOfArgs = []; + for (var i = 0; i < args.length; i++) { + arrayOfArgs.push(args[i]); + } + return arrayOfArgs; + }; + + util.isUndefined = function(obj) { + return obj === void 0; + }; + + util.arrayContains = function(array, search) { + var i = array.length; + while (i--) { + if (array[i] === search) { + return true; + } + } + return false; + }; + + util.clone = function(obj) { + if (Object.prototype.toString.apply(obj) === '[object Array]') { + return obj.slice(); + } + + var cloned = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + cloned[prop] = obj[prop]; + } + } + + return cloned; + }; + + return util; +}; + +getJasmineRequireObj().Spec = function(j$) { + function Spec(attrs) { + this.expectationFactory = attrs.expectationFactory; + this.resultCallback = attrs.resultCallback || function() {}; + this.id = attrs.id; + this.description = attrs.description || ''; + this.queueableFn = attrs.queueableFn; + this.beforeAndAfterFns = attrs.beforeAndAfterFns || function() { return {befores: [], afters: []}; }; + this.userContext = attrs.userContext || function() { return {}; }; + this.onStart = attrs.onStart || function() {}; + this.getSpecName = attrs.getSpecName || function() { return ''; }; + this.expectationResultFactory = attrs.expectationResultFactory || function() { }; + this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; + this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + if (!this.queueableFn.fn) { + this.pend(); + } + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [], + passedExpectations: [], + pendingReason: '' + }; + } + + Spec.prototype.addExpectationResult = function(passed, data, isError) { + var expectationResult = this.expectationResultFactory(data); + if (passed) { + this.result.passedExpectations.push(expectationResult); + } else { + this.result.failedExpectations.push(expectationResult); + + if (this.throwOnExpectationFailure && !isError) { + throw new j$.errors.ExpectationFailed(); + } + } + }; + + Spec.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Spec.prototype.execute = function(onComplete, enabled) { + var self = this; + + this.onStart(this); + + if (!this.isExecutable() || this.markedPending || enabled === false) { + complete(enabled); + return; + } + + var fns = this.beforeAndAfterFns(); + var allFns = fns.befores.concat(this.queueableFn).concat(fns.afters); + + this.queueRunnerFactory({ + queueableFns: allFns, + onException: function() { self.onException.apply(self, arguments); }, + onComplete: complete, + userContext: this.userContext() + }); + + function complete(enabledAgain) { + self.result.status = self.status(enabledAgain); + self.resultCallback(self.result); + + if (onComplete) { + onComplete(); + } + } + }; + + Spec.prototype.onException = function onException(e) { + if (Spec.isPendingSpecException(e)) { + this.pend(extractCustomPendingMessage(e)); + return; + } + + if (e instanceof j$.errors.ExpectationFailed) { + return; + } + + this.addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: e + }, true); + }; + + Spec.prototype.disable = function() { + this.disabled = true; + }; + + Spec.prototype.pend = function(message) { + this.markedPending = true; + if (message) { + this.result.pendingReason = message; + } + }; + + Spec.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Spec.prototype.status = function(enabled) { + if (this.disabled || enabled === false) { + return 'disabled'; + } + + if (this.markedPending) { + return 'pending'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'passed'; + } + }; + + Spec.prototype.isExecutable = function() { + return !this.disabled; + }; + + Spec.prototype.getFullName = function() { + return this.getSpecName(this); + }; + + var extractCustomPendingMessage = function(e) { + var fullMessage = e.toString(), + boilerplateStart = fullMessage.indexOf(Spec.pendingSpecExceptionMessage), + boilerplateEnd = boilerplateStart + Spec.pendingSpecExceptionMessage.length; + + return fullMessage.substr(boilerplateEnd); + }; + + Spec.pendingSpecExceptionMessage = '=> marked Pending'; + + Spec.isPendingSpecException = function(e) { + return !!(e && e.toString && e.toString().indexOf(Spec.pendingSpecExceptionMessage) !== -1); + }; + + return Spec; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Spec = jasmineRequire.Spec; +} + +getJasmineRequireObj().Env = function(j$) { + function Env(options) { + options = options || {}; + + var self = this; + var global = options.global || j$.getGlobal(); + + var totalSpecsDefined = 0; + + var catchExceptions = true; + + var realSetTimeout = j$.getGlobal().setTimeout; + var realClearTimeout = j$.getGlobal().clearTimeout; + this.clock = new j$.Clock(global, function () { return new j$.DelayedFunctionScheduler(); }, new j$.MockDate(global)); + + var runnableLookupTable = {}; + var runnableResources = {}; + + var currentSpec = null; + var currentlyExecutingSuites = []; + var currentDeclarationSuite = null; + var throwOnExpectationFailure = false; + + var currentSuite = function() { + return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; + }; + + var currentRunnable = function() { + return currentSpec || currentSuite(); + }; + + var reporter = new j$.ReportDispatcher([ + 'jasmineStarted', + 'jasmineDone', + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone' + ]); + + this.specFilter = function() { + return true; + }; + + this.addCustomEqualityTester = function(tester) { + if(!currentRunnable()) { + throw new Error('Custom Equalities must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customEqualityTesters.push(tester); + }; + + this.addMatchers = function(matchersToAdd) { + if(!currentRunnable()) { + throw new Error('Matchers must be added in a before function or a spec'); + } + var customMatchers = runnableResources[currentRunnable().id].customMatchers; + for (var matcherName in matchersToAdd) { + customMatchers[matcherName] = matchersToAdd[matcherName]; + } + }; + + j$.Expectation.addCoreMatchers(j$.matchers); + + var nextSpecId = 0; + var getNextSpecId = function() { + return 'spec' + nextSpecId++; + }; + + var nextSuiteId = 0; + var getNextSuiteId = function() { + return 'suite' + nextSuiteId++; + }; + + var expectationFactory = function(actual, spec) { + return j$.Expectation.Factory({ + util: j$.matchersUtil, + customEqualityTesters: runnableResources[spec.id].customEqualityTesters, + customMatchers: runnableResources[spec.id].customMatchers, + actual: actual, + addExpectationResult: addExpectationResult + }); + + function addExpectationResult(passed, result) { + return spec.addExpectationResult(passed, result); + } + }; + + var defaultResourcesForRunnable = function(id, parentRunnableId) { + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + + if(runnableResources[parentRunnableId]){ + resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); + resources.customMatchers = j$.util.clone(runnableResources[parentRunnableId].customMatchers); + } + + runnableResources[id] = resources; + }; + + var clearResourcesForRunnable = function(id) { + spyRegistry.clearSpies(); + delete runnableResources[id]; + }; + + var beforeAndAfterFns = function(suite) { + return function() { + var befores = [], + afters = []; + + while(suite) { + befores = befores.concat(suite.beforeFns); + afters = afters.concat(suite.afterFns); + + suite = suite.parentSuite; + } + + return { + befores: befores.reverse(), + afters: afters + }; + }; + }; + + var getSpecName = function(spec, suite) { + return suite.getFullName() + ' ' + spec.description; + }; + + // TODO: we may just be able to pass in the fn instead of wrapping here + var buildExpectationResult = j$.buildExpectationResult, + exceptionFormatter = new j$.ExceptionFormatter(), + expectationResultFactory = function(attrs) { + attrs.messageFormatter = exceptionFormatter.message; + attrs.stackFormatter = exceptionFormatter.stack; + + return buildExpectationResult(attrs); + }; + + // TODO: fix this naming, and here's where the value comes in + this.catchExceptions = function(value) { + catchExceptions = !!value; + return catchExceptions; + }; + + this.catchingExceptions = function() { + return catchExceptions; + }; + + var maximumSpecCallbackDepth = 20; + var currentSpecCallbackDepth = 0; + + function clearStack(fn) { + currentSpecCallbackDepth++; + if (currentSpecCallbackDepth >= maximumSpecCallbackDepth) { + currentSpecCallbackDepth = 0; + realSetTimeout(fn, 0); + } else { + fn(); + } + } + + var catchException = function(e) { + return j$.Spec.isPendingSpecException(e) || catchExceptions; + }; + + this.throwOnExpectationFailure = function(value) { + throwOnExpectationFailure = !!value; + }; + + this.throwingExpectationFailures = function() { + return throwOnExpectationFailure; + }; + + var queueRunnerFactory = function(options) { + options.catchException = catchException; + options.clearStack = options.clearStack || clearStack; + options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; + options.fail = self.fail; + + new j$.QueueRunner(options).execute(); + }; + + var topSuite = new j$.Suite({ + env: this, + id: getNextSuiteId(), + description: 'Jasmine__TopLevel__Suite', + queueRunner: queueRunnerFactory + }); + runnableLookupTable[topSuite.id] = topSuite; + defaultResourcesForRunnable(topSuite.id); + currentDeclarationSuite = topSuite; + + this.topSuite = function() { + return topSuite; + }; + + this.execute = function(runnablesToRun) { + if(!runnablesToRun) { + if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } + } + var processor = new j$.TreeProcessor({ + tree: topSuite, + runnableIds: runnablesToRun, + queueRunnerFactory: queueRunnerFactory, + nodeStart: function(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + }, + nodeComplete: function(suite, result) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + } + currentlyExecutingSuites.pop(); + reporter.suiteDone(result); + } + }); + + if(!processor.processTree().valid) { + throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times'); + } + + reporter.jasmineStarted({ + totalSpecsDefined: totalSpecsDefined + }); + + processor.execute(reporter.jasmineDone); + }; + + this.addReporter = function(reporterToAdd) { + reporter.addReporter(reporterToAdd); + }; + + var spyRegistry = new j$.SpyRegistry({currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } + return runnableResources[currentRunnable().id].spies; + }}); + + this.spyOn = function() { + return spyRegistry.spyOn.apply(spyRegistry, arguments); + }; + + var suiteFactory = function(description) { + var suite = new j$.Suite({ + env: self, + id: getNextSuiteId(), + description: description, + parentSuite: currentDeclarationSuite, + expectationFactory: expectationFactory, + expectationResultFactory: expectationResultFactory, + throwOnExpectationFailure: throwOnExpectationFailure + }); + + runnableLookupTable[suite.id] = suite; + return suite; + }; + + this.describe = function(description, specDefinitions) { + var suite = suiteFactory(description); + addSpecsToSuite(suite, specDefinitions); + return suite; + }; + + this.xdescribe = function(description, specDefinitions) { + var suite = this.describe(description, specDefinitions); + suite.disable(); + return suite; + }; + + var focusedRunnables = []; + + this.fdescribe = function(description, specDefinitions) { + var suite = suiteFactory(description); + suite.isFocused = true; + + focusedRunnables.push(suite.id); + unfocusAncestor(); + addSpecsToSuite(suite, specDefinitions); + + return suite; + }; + + function addSpecsToSuite(suite, specDefinitions) { + var parentSuite = currentDeclarationSuite; + parentSuite.addChild(suite); + currentDeclarationSuite = suite; + + var declarationError = null; + try { + specDefinitions.call(suite); + } catch (e) { + declarationError = e; + } + + if (declarationError) { + self.it('encountered a declaration exception', function() { + throw declarationError; + }); + } + + currentDeclarationSuite = parentSuite; + } + + function findFocusedAncestor(suite) { + while (suite) { + if (suite.isFocused) { + return suite.id; + } + suite = suite.parentSuite; + } + + return null; + } + + function unfocusAncestor() { + var focusedAncestor = findFocusedAncestor(currentDeclarationSuite); + if (focusedAncestor) { + for (var i = 0; i < focusedRunnables.length; i++) { + if (focusedRunnables[i] === focusedAncestor) { + focusedRunnables.splice(i, 1); + break; + } + } + } + } + + var specFactory = function(description, fn, suite, timeout) { + totalSpecsDefined++; + var spec = new j$.Spec({ + id: getNextSpecId(), + beforeAndAfterFns: beforeAndAfterFns(suite), + expectationFactory: expectationFactory, + resultCallback: specResultCallback, + getSpecName: function(spec) { + return getSpecName(spec, suite); + }, + onStart: specStarted, + description: description, + expectationResultFactory: expectationResultFactory, + queueRunnerFactory: queueRunnerFactory, + userContext: function() { return suite.clonedSharedUserContext(); }, + queueableFn: { + fn: fn, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }, + throwOnExpectationFailure: throwOnExpectationFailure + }); + + runnableLookupTable[spec.id] = spec; + + if (!self.specFilter(spec)) { + spec.disable(); + } + + return spec; + + function specResultCallback(result) { + clearResourcesForRunnable(spec.id); + currentSpec = null; + reporter.specDone(result); + } + + function specStarted(spec) { + currentSpec = spec; + defaultResourcesForRunnable(spec.id, suite.id); + reporter.specStarted(spec.result); + } + }; + + this.it = function(description, fn, timeout) { + var spec = specFactory(description, fn, currentDeclarationSuite, timeout); + currentDeclarationSuite.addChild(spec); + return spec; + }; + + this.xit = function() { + var spec = this.it.apply(this, arguments); + spec.pend(); + return spec; + }; + + this.fit = function(){ + var spec = this.it.apply(this, arguments); + + focusedRunnables.push(spec.id); + unfocusAncestor(); + return spec; + }; + + this.expect = function(actual) { + if (!currentRunnable()) { + throw new Error('\'expect\' was used when there was no current spec, this could be because an asynchronous test timed out'); + } + + return currentRunnable().expect(actual); + }; + + this.beforeEach = function(beforeEachFunction, timeout) { + currentDeclarationSuite.beforeEach({ + fn: beforeEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.beforeAll = function(beforeAllFunction, timeout) { + currentDeclarationSuite.beforeAll({ + fn: beforeAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterEach = function(afterEachFunction, timeout) { + currentDeclarationSuite.afterEach({ + fn: afterEachFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.afterAll = function(afterAllFunction, timeout) { + currentDeclarationSuite.afterAll({ + fn: afterAllFunction, + timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } + }); + }; + + this.pending = function(message) { + var fullMessage = j$.Spec.pendingSpecExceptionMessage; + if(message) { + fullMessage += message; + } + throw fullMessage; + }; + + this.fail = function(error) { + var message = 'Failed'; + if (error) { + message += ': '; + message += error.message || error; + } + + currentRunnable().addExpectationResult(false, { + matcherName: '', + passed: false, + expected: '', + actual: '', + message: message, + error: error && error.message ? error : null + }); + }; + } + + return Env; +}; + +getJasmineRequireObj().JsApiReporter = function() { + + var noopTimer = { + start: function(){}, + elapsed: function(){ return 0; } + }; + + function JsApiReporter(options) { + var timer = options.timer || noopTimer, + status = 'loaded'; + + this.started = false; + this.finished = false; + + this.jasmineStarted = function() { + this.started = true; + status = 'started'; + timer.start(); + }; + + var executionTime; + + this.jasmineDone = function() { + this.finished = true; + executionTime = timer.elapsed(); + status = 'done'; + }; + + this.status = function() { + return status; + }; + + var suites = [], + suites_hash = {}; + + this.suiteStarted = function(result) { + suites_hash[result.id] = result; + }; + + this.suiteDone = function(result) { + storeSuite(result); + }; + + this.suiteResults = function(index, length) { + return suites.slice(index, index + length); + }; + + function storeSuite(result) { + suites.push(result); + suites_hash[result.id] = result; + } + + this.suites = function() { + return suites_hash; + }; + + var specs = []; + + this.specDone = function(result) { + specs.push(result); + }; + + this.specResults = function(index, length) { + return specs.slice(index, index + length); + }; + + this.specs = function() { + return specs; + }; + + this.executionTime = function() { + return executionTime; + }; + + } + + return JsApiReporter; +}; + +getJasmineRequireObj().CallTracker = function() { + + function CallTracker() { + var calls = []; + + this.track = function(context) { + calls.push(context); + }; + + this.any = function() { + return !!calls.length; + }; + + this.count = function() { + return calls.length; + }; + + this.argsFor = function(index) { + var call = calls[index]; + return call ? call.args : []; + }; + + this.all = function() { + return calls; + }; + + this.allArgs = function() { + var callArgs = []; + for(var i = 0; i < calls.length; i++){ + callArgs.push(calls[i].args); + } + + return callArgs; + }; + + this.first = function() { + return calls[0]; + }; + + this.mostRecent = function() { + return calls[calls.length - 1]; + }; + + this.reset = function() { + calls = []; + }; + } + + return CallTracker; +}; + +getJasmineRequireObj().Clock = function() { + function Clock(global, delayedFunctionSchedulerFactory, mockDate) { + var self = this, + realTimingFunctions = { + setTimeout: global.setTimeout, + clearTimeout: global.clearTimeout, + setInterval: global.setInterval, + clearInterval: global.clearInterval + }, + fakeTimingFunctions = { + setTimeout: setTimeout, + clearTimeout: clearTimeout, + setInterval: setInterval, + clearInterval: clearInterval + }, + installed = false, + delayedFunctionScheduler, + timer; + + + self.install = function() { + if(!originalTimingFunctionsIntact()) { + throw new Error('Jasmine Clock was unable to install over custom global timer functions. Is the clock already installed?'); + } + replace(global, fakeTimingFunctions); + timer = fakeTimingFunctions; + delayedFunctionScheduler = delayedFunctionSchedulerFactory(); + installed = true; + + return self; + }; + + self.uninstall = function() { + delayedFunctionScheduler = null; + mockDate.uninstall(); + replace(global, realTimingFunctions); + + timer = realTimingFunctions; + installed = false; + }; + + self.withMock = function(closure) { + this.install(); + try { + closure(); + } finally { + this.uninstall(); + } + }; + + self.mockDate = function(initialDate) { + mockDate.install(initialDate); + }; + + self.setTimeout = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); + } + return timer.setTimeout(fn, delay); + } + return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); + }; + + self.setInterval = function(fn, delay, params) { + if (legacyIE()) { + if (arguments.length > 2) { + throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); + } + return timer.setInterval(fn, delay); + } + return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); + }; + + self.clearTimeout = function(id) { + return Function.prototype.call.apply(timer.clearTimeout, [global, id]); + }; + + self.clearInterval = function(id) { + return Function.prototype.call.apply(timer.clearInterval, [global, id]); + }; + + self.tick = function(millis) { + if (installed) { + mockDate.tick(millis); + delayedFunctionScheduler.tick(millis); + } else { + throw new Error('Mock clock is not installed, use jasmine.clock().install()'); + } + }; + + return self; + + function originalTimingFunctionsIntact() { + return global.setTimeout === realTimingFunctions.setTimeout && + global.clearTimeout === realTimingFunctions.clearTimeout && + global.setInterval === realTimingFunctions.setInterval && + global.clearInterval === realTimingFunctions.clearInterval; + } + + function legacyIE() { + //if these methods are polyfilled, apply will be present + return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; + } + + function replace(dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } + } + + function setTimeout(fn, delay) { + return delayedFunctionScheduler.scheduleFunction(fn, delay, argSlice(arguments, 2)); + } + + function clearTimeout(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function setInterval(fn, interval) { + return delayedFunctionScheduler.scheduleFunction(fn, interval, argSlice(arguments, 2), true); + } + + function clearInterval(id) { + return delayedFunctionScheduler.removeFunctionWithId(id); + } + + function argSlice(argsObj, n) { + return Array.prototype.slice.call(argsObj, n); + } + } + + return Clock; +}; + +getJasmineRequireObj().DelayedFunctionScheduler = function() { + function DelayedFunctionScheduler() { + var self = this; + var scheduledLookup = []; + var scheduledFunctions = {}; + var currentTime = 0; + var delayedFnCount = 0; + + self.tick = function(millis) { + millis = millis || 0; + var endTime = currentTime + millis; + + runScheduledFunctions(endTime); + currentTime = endTime; + }; + + self.scheduleFunction = function(funcToCall, millis, params, recurring, timeoutKey, runAtMillis) { + var f; + if (typeof(funcToCall) === 'string') { + /* jshint evil: true */ + f = function() { return eval(funcToCall); }; + /* jshint evil: false */ + } else { + f = funcToCall; + } + + millis = millis || 0; + timeoutKey = timeoutKey || ++delayedFnCount; + runAtMillis = runAtMillis || (currentTime + millis); + + var funcToSchedule = { + runAtMillis: runAtMillis, + funcToCall: f, + recurring: recurring, + params: params, + timeoutKey: timeoutKey, + millis: millis + }; + + if (runAtMillis in scheduledFunctions) { + scheduledFunctions[runAtMillis].push(funcToSchedule); + } else { + scheduledFunctions[runAtMillis] = [funcToSchedule]; + scheduledLookup.push(runAtMillis); + scheduledLookup.sort(function (a, b) { + return a - b; + }); + } + + return timeoutKey; + }; + + self.removeFunctionWithId = function(timeoutKey) { + for (var runAtMillis in scheduledFunctions) { + var funcs = scheduledFunctions[runAtMillis]; + var i = indexOfFirstToPass(funcs, function (func) { + return func.timeoutKey === timeoutKey; + }); + + if (i > -1) { + if (funcs.length === 1) { + delete scheduledFunctions[runAtMillis]; + deleteFromLookup(runAtMillis); + } else { + funcs.splice(i, 1); + } + + // intervals get rescheduled when executed, so there's never more + // than a single scheduled function with a given timeoutKey + break; + } + } + }; + + return self; + + function indexOfFirstToPass(array, testFn) { + var index = -1; + + for (var i = 0; i < array.length; ++i) { + if (testFn(array[i])) { + index = i; + break; + } + } + + return index; + } + + function deleteFromLookup(key) { + var value = Number(key); + var i = indexOfFirstToPass(scheduledLookup, function (millis) { + return millis === value; + }); + + if (i > -1) { + scheduledLookup.splice(i, 1); + } + } + + function reschedule(scheduledFn) { + self.scheduleFunction(scheduledFn.funcToCall, + scheduledFn.millis, + scheduledFn.params, + true, + scheduledFn.timeoutKey, + scheduledFn.runAtMillis + scheduledFn.millis); + } + + function forEachFunction(funcsToRun, callback) { + for (var i = 0; i < funcsToRun.length; ++i) { + callback(funcsToRun[i]); + } + } + + function runScheduledFunctions(endTime) { + if (scheduledLookup.length === 0 || scheduledLookup[0] > endTime) { + return; + } + + do { + currentTime = scheduledLookup.shift(); + + var funcsToRun = scheduledFunctions[currentTime]; + delete scheduledFunctions[currentTime]; + + forEachFunction(funcsToRun, function(funcToRun) { + if (funcToRun.recurring) { + reschedule(funcToRun); + } + }); + + forEachFunction(funcsToRun, function(funcToRun) { + funcToRun.funcToCall.apply(null, funcToRun.params || []); + }); + } while (scheduledLookup.length > 0 && + // checking first if we're out of time prevents setTimeout(0) + // scheduled in a funcToRun from forcing an extra iteration + currentTime !== endTime && + scheduledLookup[0] <= endTime); + } + } + + return DelayedFunctionScheduler; +}; + +getJasmineRequireObj().ExceptionFormatter = function() { + function ExceptionFormatter() { + this.message = function(error) { + var message = ''; + + if (error.name && error.message) { + message += error.name + ': ' + error.message; + } else { + message += error.toString() + ' thrown'; + } + + if (error.fileName || error.sourceURL) { + message += ' in ' + (error.fileName || error.sourceURL); + } + + if (error.line || error.lineNumber) { + message += ' (line ' + (error.line || error.lineNumber) + ')'; + } + + return message; + }; + + this.stack = function(error) { + return error ? error.stack : null; + }; + } + + return ExceptionFormatter; +}; + +getJasmineRequireObj().Expectation = function() { + + function Expectation(options) { + this.util = options.util || { buildFailureMessage: function() {} }; + this.customEqualityTesters = options.customEqualityTesters || []; + this.actual = options.actual; + this.addExpectationResult = options.addExpectationResult || function(){}; + this.isNot = options.isNot; + + var customMatchers = options.customMatchers || {}; + for (var matcherName in customMatchers) { + this[matcherName] = Expectation.prototype.wrapCompare(matcherName, customMatchers[matcherName]); + } + } + + Expectation.prototype.wrapCompare = function(name, matcherFactory) { + return function() { + var args = Array.prototype.slice.call(arguments, 0), + expected = args.slice(0), + message = ''; + + args.unshift(this.actual); + + var matcher = matcherFactory(this.util, this.customEqualityTesters), + matcherCompare = matcher.compare; + + function defaultNegativeCompare() { + var result = matcher.compare.apply(null, args); + result.pass = !result.pass; + return result; + } + + if (this.isNot) { + matcherCompare = matcher.negativeCompare || defaultNegativeCompare; + } + + var result = matcherCompare.apply(null, args); + + if (!result.pass) { + if (!result.message) { + args.unshift(this.isNot); + args.unshift(name); + message = this.util.buildFailureMessage.apply(null, args); + } else { + if (Object.prototype.toString.apply(result.message) === '[object Function]') { + message = result.message(); + } else { + message = result.message; + } + } + } + + if (expected.length == 1) { + expected = expected[0]; + } + + // TODO: how many of these params are needed? + this.addExpectationResult( + result.pass, + { + matcherName: name, + passed: result.pass, + message: message, + actual: this.actual, + expected: expected // TODO: this may need to be arrayified/sliced + } + ); + }; + }; + + Expectation.addCoreMatchers = function(matchers) { + var prototype = Expectation.prototype; + for (var matcherName in matchers) { + var matcher = matchers[matcherName]; + prototype[matcherName] = prototype.wrapCompare(matcherName, matcher); + } + }; + + Expectation.Factory = function(options) { + options = options || {}; + + var expect = new Expectation(options); + + // TODO: this would be nice as its own Object - NegativeExpectation + // TODO: copy instead of mutate options + options.isNot = true; + expect.not = new Expectation(options); + + return expect; + }; + + return Expectation; +}; + +//TODO: expectation result may make more sense as a presentation of an expectation. +getJasmineRequireObj().buildExpectationResult = function() { + function buildExpectationResult(options) { + var messageFormatter = options.messageFormatter || function() {}, + stackFormatter = options.stackFormatter || function() {}; + + var result = { + matcherName: options.matcherName, + message: message(), + stack: stack(), + passed: options.passed + }; + + if(!result.passed) { + result.expected = options.expected; + result.actual = options.actual; + } + + return result; + + function message() { + if (options.passed) { + return 'Passed.'; + } else if (options.message) { + return options.message; + } else if (options.error) { + return messageFormatter(options.error); + } + return ''; + } + + function stack() { + if (options.passed) { + return ''; + } + + var error = options.error; + if (!error) { + try { + throw new Error(message()); + } catch (e) { + error = e; + } + } + return stackFormatter(error); + } + } + + return buildExpectationResult; +}; + +getJasmineRequireObj().MockDate = function() { + function MockDate(global) { + var self = this; + var currentTime = 0; + + if (!global || !global.Date) { + self.install = function() {}; + self.tick = function() {}; + self.uninstall = function() {}; + return self; + } + + var GlobalDate = global.Date; + + self.install = function(mockDate) { + if (mockDate instanceof GlobalDate) { + currentTime = mockDate.getTime(); + } else { + currentTime = new GlobalDate().getTime(); + } + + global.Date = FakeDate; + }; + + self.tick = function(millis) { + millis = millis || 0; + currentTime = currentTime + millis; + }; + + self.uninstall = function() { + currentTime = 0; + global.Date = GlobalDate; + }; + + createDateProperties(); + + return self; + + function FakeDate() { + switch(arguments.length) { + case 0: + return new GlobalDate(currentTime); + case 1: + return new GlobalDate(arguments[0]); + case 2: + return new GlobalDate(arguments[0], arguments[1]); + case 3: + return new GlobalDate(arguments[0], arguments[1], arguments[2]); + case 4: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3]); + case 5: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4]); + case 6: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5]); + default: + return new GlobalDate(arguments[0], arguments[1], arguments[2], arguments[3], + arguments[4], arguments[5], arguments[6]); + } + } + + function createDateProperties() { + FakeDate.prototype = GlobalDate.prototype; + + FakeDate.now = function() { + if (GlobalDate.now) { + return currentTime; + } else { + throw new Error('Browser does not support Date.now()'); + } + }; + + FakeDate.toSource = GlobalDate.toSource; + FakeDate.toString = GlobalDate.toString; + FakeDate.parse = GlobalDate.parse; + FakeDate.UTC = GlobalDate.UTC; + } + } + + return MockDate; +}; + +getJasmineRequireObj().pp = function(j$) { + + function PrettyPrinter() { + this.ppNestLevel_ = 0; + this.seen = []; + } + + PrettyPrinter.prototype.format = function(value) { + this.ppNestLevel_++; + try { + if (j$.util.isUndefined(value)) { + this.emitScalar('undefined'); + } else if (value === null) { + this.emitScalar('null'); + } else if (value === 0 && 1/value === -Infinity) { + this.emitScalar('-0'); + } else if (value === j$.getGlobal()) { + this.emitScalar('<global>'); + } else if (value.jasmineToString) { + this.emitScalar(value.jasmineToString()); + } else if (typeof value === 'string') { + this.emitString(value); + } else if (j$.isSpy(value)) { + this.emitScalar('spy on ' + value.and.identity()); + } else if (value instanceof RegExp) { + this.emitScalar(value.toString()); + } else if (typeof value === 'function') { + this.emitScalar('Function'); + } else if (typeof value.nodeType === 'number') { + this.emitScalar('HTMLNode'); + } else if (value instanceof Date) { + this.emitScalar('Date(' + value + ')'); + } else if (j$.util.arrayContains(this.seen, value)) { + this.emitScalar('<circular reference: ' + (j$.isArray_(value) ? 'Array' : 'Object') + '>'); + } else if (j$.isArray_(value) || j$.isA_('Object', value)) { + this.seen.push(value); + if (j$.isArray_(value)) { + this.emitArray(value); + } else { + this.emitObject(value); + } + this.seen.pop(); + } else { + this.emitScalar(value.toString()); + } + } finally { + this.ppNestLevel_--; + } + }; + + PrettyPrinter.prototype.iterateObject = function(obj, fn) { + for (var property in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, property)) { continue; } + fn(property, obj.__lookupGetter__ ? (!j$.util.isUndefined(obj.__lookupGetter__(property)) && + obj.__lookupGetter__(property) !== null) : false); + } + }; + + PrettyPrinter.prototype.emitArray = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; + + function StringPrettyPrinter() { + PrettyPrinter.call(this); + + this.string = ''; + } + + j$.util.inherit(StringPrettyPrinter, PrettyPrinter); + + StringPrettyPrinter.prototype.emitScalar = function(value) { + this.append(value); + }; + + StringPrettyPrinter.prototype.emitString = function(value) { + this.append('\'' + value + '\''); + }; + + StringPrettyPrinter.prototype.emitArray = function(array) { + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + this.append('Array'); + return; + } + var length = Math.min(array.length, j$.MAX_PRETTY_PRINT_ARRAY_LENGTH); + this.append('[ '); + for (var i = 0; i < length; i++) { + if (i > 0) { + this.append(', '); + } + this.format(array[i]); + } + if(array.length > length){ + this.append(', ...'); + } + + var self = this; + var first = array.length === 0; + this.iterateObject(array, function(property, isGetter) { + if (property.match(/^\d+$/)) { + return; + } + + if (first) { + first = false; + } else { + self.append(', '); + } + + self.formatProperty(array, property, isGetter); + }); + + this.append(' ]'); + }; + + StringPrettyPrinter.prototype.emitObject = function(obj) { + var constructorName = obj.constructor ? j$.fnNameFor(obj.constructor) : 'null'; + this.append(constructorName); + + if (this.ppNestLevel_ > j$.MAX_PRETTY_PRINT_DEPTH) { + return; + } + + var self = this; + this.append('({ '); + var first = true; + + this.iterateObject(obj, function(property, isGetter) { + if (first) { + first = false; + } else { + self.append(', '); + } + + self.formatProperty(obj, property, isGetter); + }); + + this.append(' })'); + }; + + StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { + this.append(property); + this.append(': '); + if (isGetter) { + this.append('<getter>'); + } else { + this.format(obj[property]); + } + }; + + StringPrettyPrinter.prototype.append = function(value) { + this.string += value; + }; + + return function(value) { + var stringPrettyPrinter = new StringPrettyPrinter(); + stringPrettyPrinter.format(value); + return stringPrettyPrinter.string; + }; +}; + +getJasmineRequireObj().QueueRunner = function(j$) { + + function once(fn) { + var called = false; + return function() { + if (!called) { + called = true; + fn(); + } + }; + } + + function QueueRunner(attrs) { + this.queueableFns = attrs.queueableFns || []; + this.onComplete = attrs.onComplete || function() {}; + this.clearStack = attrs.clearStack || function(fn) {fn();}; + this.onException = attrs.onException || function() {}; + this.catchException = attrs.catchException || function() { return true; }; + this.userContext = attrs.userContext || {}; + this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; + this.fail = attrs.fail || function() {}; + } + + QueueRunner.prototype.execute = function() { + this.run(this.queueableFns, 0); + }; + + QueueRunner.prototype.run = function(queueableFns, recursiveIndex) { + var length = queueableFns.length, + self = this, + iterativeIndex; + + + for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { + var queueableFn = queueableFns[iterativeIndex]; + if (queueableFn.fn.length > 0) { + attemptAsync(queueableFn); + return; + } else { + attemptSync(queueableFn); + } + } + + var runnerDone = iterativeIndex >= length; + + if (runnerDone) { + this.clearStack(this.onComplete); + } + + function attemptSync(queueableFn) { + try { + queueableFn.fn.call(self.userContext); + } catch (e) { + handleException(e, queueableFn); + } + } + + function attemptAsync(queueableFn) { + var clearTimeout = function () { + Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); + }, + next = once(function () { + clearTimeout(timeoutId); + self.run(queueableFns, iterativeIndex + 1); + }), + timeoutId; + + next.fail = function() { + self.fail.apply(null, arguments); + next(); + }; + + if (queueableFn.timeout) { + timeoutId = Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [function() { + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error, queueableFn); + next(); + }, queueableFn.timeout()]]); + } + + try { + queueableFn.fn.call(self.userContext, next); + } catch (e) { + handleException(e, queueableFn); + next(); + } + } + + function onException(e, queueableFn) { + self.onException(e); + } + + function handleException(e, queueableFn) { + onException(e, queueableFn); + if (!self.catchException(e)) { + //TODO: set a var when we catch an exception and + //use a finally block to close the loop in a nice way.. + throw e; + } + } + }; + + return QueueRunner; +}; + +getJasmineRequireObj().ReportDispatcher = function() { + function ReportDispatcher(methods) { + + var dispatchedMethods = methods || []; + + for (var i = 0; i < dispatchedMethods.length; i++) { + var method = dispatchedMethods[i]; + this[method] = (function(m) { + return function() { + dispatch(m, arguments); + }; + }(method)); + } + + var reporters = []; + + this.addReporter = function(reporter) { + reporters.push(reporter); + }; + + return this; + + function dispatch(method, args) { + for (var i = 0; i < reporters.length; i++) { + var reporter = reporters[i]; + if (reporter[method]) { + reporter[method].apply(reporter, args); + } + } + } + } + + return ReportDispatcher; +}; + + +getJasmineRequireObj().SpyRegistry = function(j$) { + + function SpyRegistry(options) { + options = options || {}; + var currentSpies = options.currentSpies || function() { return []; }; + + this.spyOn = function(obj, methodName) { + if (j$.util.isUndefined(obj)) { + throw new Error('spyOn could not find an object to spy upon for ' + methodName + '()'); + } + + if (j$.util.isUndefined(methodName)) { + throw new Error('No method name supplied'); + } + + if (j$.util.isUndefined(obj[methodName])) { + throw new Error(methodName + '() method does not exist'); + } + + if (obj[methodName] && j$.isSpy(obj[methodName])) { + //TODO?: should this return the current spy? Downside: may cause user confusion about spy state + throw new Error(methodName + ' has already been spied upon'); + } + + var spy = j$.createSpy(methodName, obj[methodName]); + + currentSpies().push({ + spy: spy, + baseObj: obj, + methodName: methodName, + originalValue: obj[methodName] + }); + + obj[methodName] = spy; + + return spy; + }; - // create redirection for trailing slashes - if (path) { - var redirectPath = (path[path.length - 1] == '/') - ? path.substr(0, path.length - 1) - : path + '/'; + this.clearSpies = function() { + var spies = currentSpies(); + for (var i = 0; i < spies.length; i++) { + var spyEntry = spies[i]; + spyEntry.baseObj[spyEntry.methodName] = spyEntry.originalValue; + } + }; + } - routes[redirectPath] = angular.extend( - {redirectTo: path}, - pathRegExp(redirectPath, routeCopy) - ); - } + return SpyRegistry; +}; - return this; - }; +getJasmineRequireObj().SpyStrategy = function() { - /** - * @ngdoc property - * @name $routeProvider#caseInsensitiveMatch - * @description - * - * A boolean property indicating if routes defined - * using this provider should be matched using a case insensitive - * algorithm. Defaults to `false`. - */ - this.caseInsensitiveMatch = false; + function SpyStrategy(options) { + options = options || {}; - /** - * @param path {string} path - * @param opts {Object} options - * @return {?Object} - * - * @description - * Normalizes the given path, returning a regular expression - * and the original path. - * - * Inspired by pathRexp in visionmedia/express/lib/utils.js. - */ - function pathRegExp(path, opts) { - var insensitive = opts.caseInsensitiveMatch, - ret = { - originalPath: path, - regexp: path - }, - keys = ret.keys = []; + var identity = options.name || 'unknown', + originalFn = options.fn || function() {}, + getSpy = options.getSpy || function() {}, + plan = function() {}; - path = path - .replace(/([().])/g, '\\$1') - .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { - var optional = option === '?' ? option : null; - var star = option === '*' ? option : null; - keys.push({ name: key, optional: !!optional }); - slash = slash || ''; - return '' - + (optional ? '' : slash) - + '(?:' - + (optional ? slash : '') - + (star && '(.+?)' || '([^/]+)') - + (optional || '') - + ')' - + (optional || ''); - }) - .replace(/([\/$\*])/g, '\\$1'); + this.identity = function() { + return identity; + }; - ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); - return ret; - } + this.exec = function() { + return plan.apply(this, arguments); + }; - /** - * @ngdoc method - * @name $routeProvider#otherwise - * - * @description - * Sets route definition that will be used on route change when no other route definition - * is matched. - * - * @param {Object|string} params Mapping information to be assigned to `$route.current`. - * If called with a string, the value maps to `redirectTo`. - * @returns {Object} self - */ - this.otherwise = function(params) { - if (typeof params === 'string') { - params = {redirectTo: params}; - } - this.when(null, params); - return this; - }; + this.callThrough = function() { + plan = originalFn; + return getSpy(); + }; + this.returnValue = function(value) { + plan = function() { + return value; + }; + return getSpy(); + }; - this.$get = ['$rootScope', - '$location', - '$routeParams', - '$q', - '$injector', - '$templateRequest', - '$sce', - function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { + this.returnValues = function() { + var values = Array.prototype.slice.call(arguments); + plan = function () { + return values.shift(); + }; + return getSpy(); + }; - /** - * @ngdoc service - * @name $route - * @requires $location - * @requires $routeParams - * - * @property {Object} current Reference to the current route definition. - * The route definition contains: - * - * - `controller`: The controller constructor as define in route definition. - * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for - * controller instantiation. The `locals` contain - * the resolved values of the `resolve` map. Additionally the `locals` also contain: - * - * - `$scope` - The current route scope. - * - `$template` - The current route template HTML. - * - * @property {Object} routes Object with all route configuration Objects as its properties. - * - * @description - * `$route` is used for deep-linking URLs to controllers and views (HTML partials). - * It watches `$location.url()` and tries to map the path to an existing route definition. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. - * - * The `$route` service is typically used in conjunction with the - * {@link ngRoute.directive:ngView `ngView`} directive and the - * {@link ngRoute.$routeParams `$routeParams`} service. - * - * @example - * This example shows how changing the URL hash causes the `$route` to match a route against the - * URL, and the `ngView` pulls in the partial. - * - * <example name="$route-service" module="ngRouteExample" - * deps="angular-route.js" fixBase="true"> - * <file name="index.html"> - * <div ng-controller="MainController"> - * Choose: - * <a href="Book/Moby">Moby</a> | - * <a href="Book/Moby/ch/1">Moby: Ch1</a> | - * <a href="Book/Gatsby">Gatsby</a> | - * <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | - * <a href="Book/Scarlet">Scarlet Letter</a><br/> - * - * <div ng-view></div> - * - * <hr /> - * - * <pre>$location.path() = {{$location.path()}}</pre> - * <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> - * <pre>$route.current.params = {{$route.current.params}}</pre> - * <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> - * <pre>$routeParams = {{$routeParams}}</pre> - * </div> - * </file> - * - * <file name="book.html"> - * controller: {{name}}<br /> - * Book Id: {{params.bookId}}<br /> - * </file> - * - * <file name="chapter.html"> - * controller: {{name}}<br /> - * Book Id: {{params.bookId}}<br /> - * Chapter Id: {{params.chapterId}} - * </file> - * - * <file name="script.js"> - * angular.module('ngRouteExample', ['ngRoute']) - * - * .controller('MainController', function($scope, $route, $routeParams, $location) { - * $scope.$route = $route; - * $scope.$location = $location; - * $scope.$routeParams = $routeParams; - * }) - * - * .controller('BookController', function($scope, $routeParams) { - * $scope.name = "BookController"; - * $scope.params = $routeParams; - * }) - * - * .controller('ChapterController', function($scope, $routeParams) { - * $scope.name = "ChapterController"; - * $scope.params = $routeParams; - * }) - * - * .config(function($routeProvider, $locationProvider) { - * $routeProvider - * .when('/Book/:bookId', { - * templateUrl: 'book.html', - * controller: 'BookController', - * resolve: { - * // I will cause a 1 second delay - * delay: function($q, $timeout) { - * var delay = $q.defer(); - * $timeout(delay.resolve, 1000); - * return delay.promise; - * } - * } - * }) - * .when('/Book/:bookId/ch/:chapterId', { - * templateUrl: 'chapter.html', - * controller: 'ChapterController' - * }); - * - * // configure html5 to get links working on jsfiddle - * $locationProvider.html5Mode(true); - * }); - * - * </file> - * - * <file name="protractor.js" type="protractor"> - * it('should load and compile correct template', function() { - * element(by.linkText('Moby: Ch1')).click(); - * var content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller\: ChapterController/); - * expect(content).toMatch(/Book Id\: Moby/); - * expect(content).toMatch(/Chapter Id\: 1/); - * - * element(by.partialLinkText('Scarlet')).click(); - * - * content = element(by.css('[ng-view]')).getText(); - * expect(content).toMatch(/controller\: BookController/); - * expect(content).toMatch(/Book Id\: Scarlet/); - * }); - * </file> - * </example> - */ + this.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + plan = function() { + throw error; + }; + return getSpy(); + }; + + this.callFake = function(fn) { + plan = fn; + return getSpy(); + }; + + this.stub = function(fn) { + plan = function() {}; + return getSpy(); + }; + } + + return SpyStrategy; +}; + +getJasmineRequireObj().Suite = function(j$) { + function Suite(attrs) { + this.env = attrs.env; + this.id = attrs.id; + this.parentSuite = attrs.parentSuite; + this.description = attrs.description; + this.expectationFactory = attrs.expectationFactory; + this.expectationResultFactory = attrs.expectationResultFactory; + this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure; + + this.beforeFns = []; + this.afterFns = []; + this.beforeAllFns = []; + this.afterAllFns = []; + this.disabled = false; + + this.children = []; + + this.result = { + id: this.id, + description: this.description, + fullName: this.getFullName(), + failedExpectations: [] + }; + } + + Suite.prototype.expect = function(actual) { + return this.expectationFactory(actual, this); + }; + + Suite.prototype.getFullName = function() { + var fullName = this.description; + for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) { + if (parentSuite.parentSuite) { + fullName = parentSuite.description + ' ' + fullName; + } + } + return fullName; + }; + + Suite.prototype.disable = function() { + this.disabled = true; + }; + + Suite.prototype.beforeEach = function(fn) { + this.beforeFns.unshift(fn); + }; + + Suite.prototype.beforeAll = function(fn) { + this.beforeAllFns.push(fn); + }; + + Suite.prototype.afterEach = function(fn) { + this.afterFns.unshift(fn); + }; + + Suite.prototype.afterAll = function(fn) { + this.afterAllFns.push(fn); + }; + + Suite.prototype.addChild = function(child) { + this.children.push(child); + }; + + Suite.prototype.status = function() { + if (this.disabled) { + return 'disabled'; + } + + if (this.result.failedExpectations.length > 0) { + return 'failed'; + } else { + return 'finished'; + } + }; + + Suite.prototype.isExecutable = function() { + return !this.disabled; + }; + + Suite.prototype.canBeReentered = function() { + return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; + }; + + Suite.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Suite.prototype.sharedUserContext = function() { + if (!this.sharedContext) { + this.sharedContext = this.parentSuite ? clone(this.parentSuite.sharedUserContext()) : {}; + } + + return this.sharedContext; + }; + + Suite.prototype.clonedSharedUserContext = function() { + return clone(this.sharedUserContext()); + }; + + Suite.prototype.onException = function() { + if (arguments[0] instanceof j$.errors.ExpectationFailed) { + return; + } + + if(isAfterAll(this.children)) { + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + child.onException.apply(child, arguments); + } + } + }; + + Suite.prototype.addExpectationResult = function () { + if(isAfterAll(this.children) && isFailure(arguments)){ + var data = arguments[1]; + this.result.failedExpectations.push(this.expectationResultFactory(data)); + if(this.throwOnExpectationFailure) { + throw new j$.errors.ExpectationFailed(); + } + } else { + for (var i = 0; i < this.children.length; i++) { + var child = this.children[i]; + try { + child.addExpectationResult.apply(child, arguments); + } catch(e) { + // keep going + } + } + } + }; + + function isAfterAll(children) { + return children && children[0].result.status; + } + + function isFailure(args) { + return !args[0]; + } + + function clone(obj) { + var clonedObj = {}; + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + clonedObj[prop] = obj[prop]; + } + } + + return clonedObj; + } + + return Suite; +}; + +if (typeof window == void 0 && typeof exports == 'object') { + exports.Suite = jasmineRequire.Suite; +} + +getJasmineRequireObj().Timer = function() { + var defaultNow = (function(Date) { + return function() { return new Date().getTime(); }; + })(Date); + + function Timer(options) { + options = options || {}; + + var now = options.now || defaultNow, + startTime; + + this.start = function() { + startTime = now(); + }; + + this.elapsed = function() { + return now() - startTime; + }; + } + + return Timer; +}; + +getJasmineRequireObj().TreeProcessor = function() { + function TreeProcessor(attrs) { + var tree = attrs.tree, + runnableIds = attrs.runnableIds, + queueRunnerFactory = attrs.queueRunnerFactory, + nodeStart = attrs.nodeStart || function() {}, + nodeComplete = attrs.nodeComplete || function() {}, + stats = { valid: true }, + processed = false, + defaultMin = Infinity, + defaultMax = 1 - Infinity; + + this.processTree = function() { + processNode(tree, false); + processed = true; + return stats; + }; + + this.execute = function(done) { + if (!processed) { + this.processTree(); + } + + if (!stats.valid) { + throw 'invalid order'; + } + + var childFns = wrapChildren(tree, 0); + + queueRunnerFactory({ + queueableFns: childFns, + userContext: tree.sharedUserContext(), + onException: function() { + tree.onException.apply(tree, arguments); + }, + onComplete: done + }); + }; + + function runnableIndex(id) { + for (var i = 0; i < runnableIds.length; i++) { + if (runnableIds[i] === id) { + return i; + } + } + } + + function processNode(node, parentEnabled) { + var executableIndex = runnableIndex(node.id); + + if (executableIndex !== undefined) { + parentEnabled = true; + } + + parentEnabled = parentEnabled && node.isExecutable(); + + if (!node.children) { + stats[node.id] = { + executable: parentEnabled && node.isExecutable(), + segments: [{ + index: 0, + owner: node, + nodes: [node], + min: startingMin(executableIndex), + max: startingMax(executableIndex) + }] + }; + } else { + var hasExecutableChild = false; + + for (var i = 0; i < node.children.length; i++) { + var child = node.children[i]; + + processNode(child, parentEnabled); + + if (!stats.valid) { + return; + } + + var childStats = stats[child.id]; + + hasExecutableChild = hasExecutableChild || childStats.executable; + } + + stats[node.id] = { + executable: hasExecutableChild + }; + + segmentChildren(node, stats[node.id], executableIndex); + + if (!node.canBeReentered() && stats[node.id].segments.length > 1) { + stats = { valid: false }; + } + } + } - /** - * @ngdoc event - * @name $route#$routeChangeStart - * @eventType broadcast on root scope - * @description - * Broadcasted before a route change. At this point the route services starts - * resolving all of the dependencies needed for the route change to occur. - * Typically this involves fetching the view template as well as any dependencies - * defined in `resolve` route property. Once all of the dependencies are resolved - * `$routeChangeSuccess` is fired. - * - * The route change (and the `$location` change that triggered it) can be prevented - * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} - * for more details about event object. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} next Future route information. - * @param {Route} current Current route information. - */ + function startingMin(executableIndex) { + return executableIndex === undefined ? defaultMin : executableIndex; + } - /** - * @ngdoc event - * @name $route#$routeChangeSuccess - * @eventType broadcast on root scope - * @description - * Broadcasted after a route change has happened successfully. - * The `resolve` dependencies are now available in the `current.locals` property. - * - * {@link ngRoute.directive:ngView ngView} listens for the directive - * to instantiate the controller and render the view. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} current Current route information. - * @param {Route|Undefined} previous Previous route information, or undefined if current is - * first route entered. - */ + function startingMax(executableIndex) { + return executableIndex === undefined ? defaultMax : executableIndex; + } - /** - * @ngdoc event - * @name $route#$routeChangeError - * @eventType broadcast on root scope - * @description - * Broadcasted if any of the resolve promises are rejected. - * - * @param {Object} angularEvent Synthetic event object - * @param {Route} current Current route information. - * @param {Route} previous Previous route information. - * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. - */ + function segmentChildren(node, nodeStats, executableIndex) { + var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, + result = [currentSegment], + lastMax = defaultMax, + orderedChildSegments = orderChildSegments(node.children); - /** - * @ngdoc event - * @name $route#$routeUpdate - * @eventType broadcast on root scope - * @description - * The `reloadOnSearch` property has been set to false, and we are reusing the same - * instance of the Controller. - * - * @param {Object} angularEvent Synthetic event object - * @param {Route} current Current/previous route information. - */ + function isSegmentBoundary(minIndex) { + return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1; + } - var forceReload = false, - preparedRoute, - preparedRouteIsUpdateOnly, - $route = { - routes: routes, + for (var i = 0; i < orderedChildSegments.length; i++) { + var childSegment = orderedChildSegments[i], + maxIndex = childSegment.max, + minIndex = childSegment.min; - /** - * @ngdoc method - * @name $route#reload - * - * @description - * Causes `$route` service to reload the current route even if - * {@link ng.$location $location} hasn't changed. - * - * As a result of that, {@link ngRoute.directive:ngView ngView} - * creates new scope and reinstantiates the controller. - */ - reload: function() { - forceReload = true; - $rootScope.$evalAsync(function() { - // Don't support cancellation of a reload for now... - prepareRoute(); - commitRoute(); - }); - }, + if (isSegmentBoundary(minIndex)) { + currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMax}; + result.push(currentSegment); + } - /** - * @ngdoc method - * @name $route#updateParams - * - * @description - * Causes `$route` service to update the current URL, replacing - * current route parameters with those specified in `newParams`. - * Provided property names that match the route's path segment - * definitions will be interpolated into the location's path, while - * remaining properties will be treated as query params. - * - * @param {!Object<string, string>} newParams mapping of URL parameter names to values - */ - updateParams: function(newParams) { - if (this.current && this.current.$$route) { - newParams = angular.extend({}, this.current.params, newParams); - $location.path(interpolate(this.current.$$route.originalPath, newParams)); - // interpolate modifies newParams, only query params are left - $location.search(newParams); - } else { - throw $routeMinErr('norout', 'Tried updating route when with no current route'); - } - } - }; + currentSegment.nodes.push(childSegment); + currentSegment.min = Math.min(currentSegment.min, minIndex); + currentSegment.max = Math.max(currentSegment.max, maxIndex); + lastMax = maxIndex; + } - $rootScope.$on('$locationChangeStart', prepareRoute); - $rootScope.$on('$locationChangeSuccess', commitRoute); + nodeStats.segments = result; + } - return $route; + function orderChildSegments(children) { + var specifiedOrder = [], + unspecifiedOrder = []; - ///////////////////////////////////////////////////// + for (var i = 0; i < children.length; i++) { + var child = children[i], + segments = stats[child.id].segments; - /** - * @param on {string} current url - * @param route {Object} route regexp to match the url against - * @return {?Object} - * - * @description - * Check if the route matches the current url. - * - * Inspired by match in - * visionmedia/express/lib/router/router.js. - */ - function switchRouteMatcher(on, route) { - var keys = route.keys, - params = {}; + for (var j = 0; j < segments.length; j++) { + var seg = segments[j]; - if (!route.regexp) return null; + if (seg.min === defaultMin) { + unspecifiedOrder.push(seg); + } else { + specifiedOrder.push(seg); + } + } + } - var m = route.regexp.exec(on); - if (!m) return null; + specifiedOrder.sort(function(a, b) { + return a.min - b.min; + }); - for (var i = 1, len = m.length; i < len; ++i) { - var key = keys[i - 1]; + return specifiedOrder.concat(unspecifiedOrder); + } - var val = m[i]; + function executeNode(node, segmentNumber) { + if (node.children) { + return { + fn: function(done) { + nodeStart(node); - if (key && val) { - params[key.name] = val; - } - } - return params; - } + queueRunnerFactory({ + onComplete: function() { + nodeComplete(node, node.getResult()); + done(); + }, + queueableFns: wrapChildren(node, segmentNumber), + userContext: node.sharedUserContext(), + onException: function() { + node.onException.apply(node, arguments); + } + }); + } + }; + } else { + return { + fn: function(done) { node.execute(done, stats[node.id].executable); } + }; + } + } - function prepareRoute($locationEvent) { - var lastRoute = $route.current; + function wrapChildren(node, segmentNumber) { + var result = [], + segmentChildren = stats[node.id].segments[segmentNumber].nodes; - preparedRoute = parseRoute(); - preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route - && angular.equals(preparedRoute.pathParams, lastRoute.pathParams) - && !preparedRoute.reloadOnSearch && !forceReload; + for (var i = 0; i < segmentChildren.length; i++) { + result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); + } - if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { - if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { - if ($locationEvent) { - $locationEvent.preventDefault(); - } - } - } - } + if (!stats[node.id].executable) { + return result; + } - function commitRoute() { - var lastRoute = $route.current; - var nextRoute = preparedRoute; + return node.beforeAllFns.concat(result).concat(node.afterAllFns); + } + } - if (preparedRouteIsUpdateOnly) { - lastRoute.params = nextRoute.params; - angular.copy(lastRoute.params, $routeParams); - $rootScope.$broadcast('$routeUpdate', lastRoute); - } else if (nextRoute || lastRoute) { - forceReload = false; - $route.current = nextRoute; - if (nextRoute) { - if (nextRoute.redirectTo) { - if (angular.isString(nextRoute.redirectTo)) { - $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) - .replace(); - } else { - $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) - .replace(); - } - } - } + return TreeProcessor; +}; - $q.when(nextRoute). - then(function() { - if (nextRoute) { - var locals = angular.extend({}, nextRoute.resolve), - template, templateUrl; +getJasmineRequireObj().Any = function(j$) { - angular.forEach(locals, function(value, key) { - locals[key] = angular.isString(value) ? - $injector.get(value) : $injector.invoke(value, null, null, key); - }); + function Any(expectedObject) { + this.expectedObject = expectedObject; + } - if (angular.isDefined(template = nextRoute.template)) { - if (angular.isFunction(template)) { - template = template(nextRoute.params); - } - } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { - if (angular.isFunction(templateUrl)) { - templateUrl = templateUrl(nextRoute.params); - } - if (angular.isDefined(templateUrl)) { - nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl); - template = $templateRequest(templateUrl); - } - } - if (angular.isDefined(template)) { - locals['$template'] = template; - } - return $q.all(locals); - } - }). - then(function(locals) { - // after route change - if (nextRoute == $route.current) { - if (nextRoute) { - nextRoute.locals = locals; - angular.copy(nextRoute.params, $routeParams); - } - $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); - } - }, function(error) { - if (nextRoute == $route.current) { - $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); - } - }); - } - } + Any.prototype.asymmetricMatch = function(other) { + if (this.expectedObject == String) { + return typeof other == 'string' || other instanceof String; + } + if (this.expectedObject == Number) { + return typeof other == 'number' || other instanceof Number; + } - /** - * @returns {Object} the current active route, by matching it against the URL - */ - function parseRoute() { - // Match a route - var params, match; - angular.forEach(routes, function(route, path) { - if (!match && (params = switchRouteMatcher($location.path(), route))) { - match = inherit(route, { - params: angular.extend({}, $location.search(), params), - pathParams: params}); - match.$$route = route; - } - }); - // No route matched; fallback to "otherwise" route - return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); - } + if (this.expectedObject == Function) { + return typeof other == 'function' || other instanceof Function; + } + + if (this.expectedObject == Object) { + return typeof other == 'object'; + } + + if (this.expectedObject == Boolean) { + return typeof other == 'boolean'; + } + + return other instanceof this.expectedObject; + }; + + Any.prototype.jasmineToString = function() { + return '<jasmine.any(' + j$.fnNameFor(this.expectedObject) + ')>'; + }; + + return Any; +}; + +getJasmineRequireObj().Anything = function(j$) { + + function Anything() {} + + Anything.prototype.asymmetricMatch = function(other) { + return !j$.util.isUndefined(other) && other !== null; + }; - /** - * @returns {string} interpolation of the redirect path with the parameters - */ - function interpolate(string, params) { - var result = []; - angular.forEach((string || '').split(':'), function(segment, i) { - if (i === 0) { - result.push(segment); - } else { - var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/); - var key = segmentMatch[1]; - result.push(params[key]); - result.push(segmentMatch[2] || ''); - delete params[key]; - } - }); - return result.join(''); - } - }]; - } + Anything.prototype.jasmineToString = function() { + return '<jasmine.anything>'; + }; - ngRouteModule.provider('$routeParams', $RouteParamsProvider); + return Anything; +}; +getJasmineRequireObj().ArrayContaining = function(j$) { + function ArrayContaining(sample) { + this.sample = sample; + } - /** - * @ngdoc service - * @name $routeParams - * @requires $route - * - * @description - * The `$routeParams` service allows you to retrieve the current set of route parameters. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * The route parameters are a combination of {@link ng.$location `$location`}'s - * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. - * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. - * - * In case of parameter name collision, `path` params take precedence over `search` params. - * - * The service guarantees that the identity of the `$routeParams` object will remain unchanged - * (but its properties will likely change) even when a route change occurs. - * - * Note that the `$routeParams` are only updated *after* a route change completes successfully. - * This means that you cannot rely on `$routeParams` being correct in route resolve functions. - * Instead you can use `$route.current.params` to access the new route's parameters. - * - * @example - * ```js - * // Given: - * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby - * // Route: /Chapter/:chapterId/Section/:sectionId - * // - * // Then - * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} - * ``` - */ - function $RouteParamsProvider() { - this.$get = function() { return {}; }; - } + ArrayContaining.prototype.asymmetricMatch = function(other) { + var className = Object.prototype.toString.call(this.sample); + if (className !== '[object Array]') { throw new Error('You must provide an array to arrayContaining, not \'' + this.sample + '\'.'); } - ngRouteModule.directive('ngView', ngViewFactory); - ngRouteModule.directive('ngView', ngViewFillContentFactory); + for (var i = 0; i < this.sample.length; i++) { + var item = this.sample[i]; + if (!j$.matchersUtil.contains(other, item)) { + return false; + } + } + return true; + }; - /** - * @ngdoc directive - * @name ngView - * @restrict ECA - * - * @description - * # Overview - * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by - * including the rendered template of the current route into the main layout (`index.html`) file. - * Every time the current route changes, the included view changes with it according to the - * configuration of the `$route` service. - * - * Requires the {@link ngRoute `ngRoute`} module to be installed. - * - * @animations - * enter - animation is used to bring new content into the browser. - * leave - animation is used to animate existing content away. - * - * The enter and leave animation occur concurrently. - * - * @scope - * @priority 400 - * @param {string=} onload Expression to evaluate whenever the view updates. - * - * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll - * $anchorScroll} to scroll the viewport after the view is updated. - * - * - If the attribute is not set, disable scrolling. - * - If the attribute is set without value, enable scrolling. - * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated - * as an expression yields a truthy value. - * @example - <example name="ngView-directive" module="ngViewExample" - deps="angular-route.js;angular-animate.js" - animations="true" fixBase="true"> - <file name="index.html"> - <div ng-controller="MainCtrl as main"> - Choose: - <a href="Book/Moby">Moby</a> | - <a href="Book/Moby/ch/1">Moby: Ch1</a> | - <a href="Book/Gatsby">Gatsby</a> | - <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | - <a href="Book/Scarlet">Scarlet Letter</a><br/> + ArrayContaining.prototype.jasmineToString = function () { + return '<jasmine.arrayContaining(' + jasmine.pp(this.sample) +')>'; + }; - <div class="view-animate-container"> - <div ng-view class="view-animate"></div> - </div> - <hr /> + return ArrayContaining; +}; - <pre>$location.path() = {{main.$location.path()}}</pre> - <pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> - <pre>$route.current.params = {{main.$route.current.params}}</pre> - <pre>$routeParams = {{main.$routeParams}}</pre> - </div> - </file> +getJasmineRequireObj().ObjectContaining = function(j$) { - <file name="book.html"> - <div> - controller: {{book.name}}<br /> - Book Id: {{book.params.bookId}}<br /> - </div> - </file> + function ObjectContaining(sample) { + this.sample = sample; + } - <file name="chapter.html"> - <div> - controller: {{chapter.name}}<br /> - Book Id: {{chapter.params.bookId}}<br /> - Chapter Id: {{chapter.params.chapterId}} - </div> - </file> + function getPrototype(obj) { + if (Object.getPrototypeOf) { + return Object.getPrototypeOf(obj); + } - <file name="animations.css"> - .view-animate-container { - position:relative; - height:100px!important; - background:white; - border:1px solid black; - height:40px; - overflow:hidden; - } + if (obj.constructor.prototype == obj) { + return null; + } - .view-animate { - padding:10px; - } + return obj.constructor.prototype; + } - .view-animate.ng-enter, .view-animate.ng-leave { - transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; + function hasProperty(obj, property) { + if (!obj) { + return false; + } - display:block; - width:100%; - border-left:1px solid black; + if (Object.prototype.hasOwnProperty.call(obj, property)) { + return true; + } - position:absolute; - top:0; - left:0; - right:0; - bottom:0; - padding:10px; - } + return hasProperty(getPrototype(obj), property); + } - .view-animate.ng-enter { - left:100%; - } - .view-animate.ng-enter.ng-enter-active { - left:0; - } - .view-animate.ng-leave.ng-leave-active { - left:-100%; - } - </file> + ObjectContaining.prototype.asymmetricMatch = function(other) { + if (typeof(this.sample) !== 'object') { throw new Error('You must provide an object to objectContaining, not \''+this.sample+'\'.'); } - <file name="script.js"> - angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) - .config(['$routeProvider', '$locationProvider', - function($routeProvider, $locationProvider) { - $routeProvider - .when('/Book/:bookId', { - templateUrl: 'book.html', - controller: 'BookCtrl', - controllerAs: 'book' - }) - .when('/Book/:bookId/ch/:chapterId', { - templateUrl: 'chapter.html', - controller: 'ChapterCtrl', - controllerAs: 'chapter' - }); + for (var property in this.sample) { + if (!hasProperty(other, property) || + !j$.matchersUtil.equals(this.sample[property], other[property])) { + return false; + } + } - $locationProvider.html5Mode(true); - }]) - .controller('MainCtrl', ['$route', '$routeParams', '$location', - function($route, $routeParams, $location) { - this.$route = $route; - this.$location = $location; - this.$routeParams = $routeParams; - }]) - .controller('BookCtrl', ['$routeParams', function($routeParams) { - this.name = "BookCtrl"; - this.params = $routeParams; - }]) - .controller('ChapterCtrl', ['$routeParams', function($routeParams) { - this.name = "ChapterCtrl"; - this.params = $routeParams; - }]); + return true; + }; - </file> + ObjectContaining.prototype.jasmineToString = function() { + return '<jasmine.objectContaining(' + j$.pp(this.sample) + ')>'; + }; - <file name="protractor.js" type="protractor"> - it('should load and compile correct template', function() { - element(by.linkText('Moby: Ch1')).click(); - var content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller\: ChapterCtrl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); + return ObjectContaining; +}; - element(by.partialLinkText('Scarlet')).click(); +getJasmineRequireObj().StringMatching = function(j$) { - content = element(by.css('[ng-view]')).getText(); - expect(content).toMatch(/controller\: BookCtrl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - </file> - </example> - */ + function StringMatching(expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } + this.regexp = new RegExp(expected); + } - /** - * @ngdoc event - * @name ngView#$viewContentLoaded - * @eventType emit on the current ngView scope - * @description - * Emitted every time the ngView content is reloaded. - */ - ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; - function ngViewFactory($route, $anchorScroll, $animate) { - return { - restrict: 'ECA', - terminal: true, - priority: 400, - transclude: 'element', - link: function(scope, $element, attr, ctrl, $transclude) { - var currentScope, - currentElement, - previousLeaveAnimation, - autoScrollExp = attr.autoscroll, - onloadExp = attr.onload || ''; + StringMatching.prototype.asymmetricMatch = function(other) { + return this.regexp.test(other); + }; + + StringMatching.prototype.jasmineToString = function() { + return '<jasmine.stringMatching(' + this.regexp + ')>'; + }; + + return StringMatching; +}; + +getJasmineRequireObj().errors = function() { + function ExpectationFailed() {} + + ExpectationFailed.prototype = new Error(); + ExpectationFailed.prototype.constructor = ExpectationFailed; + + return { + ExpectationFailed: ExpectationFailed + }; +}; +getJasmineRequireObj().matchersUtil = function(j$) { + // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? + + return { + equals: function(a, b, customTesters) { + customTesters = customTesters || []; + + return eq(a, b, [], [], customTesters); + }, + + contains: function(haystack, needle, customTesters) { + customTesters = customTesters || []; + + if ((Object.prototype.toString.apply(haystack) === '[object Array]') || + (!!haystack && !haystack.indexOf)) + { + for (var i = 0; i < haystack.length; i++) { + if (eq(haystack[i], needle, [], [], customTesters)) { + return true; + } + } + return false; + } + + return !!haystack && haystack.indexOf(needle) >= 0; + }, + + buildFailureMessage: function() { + var args = Array.prototype.slice.call(arguments, 0), + matcherName = args[0], + isNot = args[1], + actual = args[2], + expected = args.slice(3), + englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); }); + + var message = 'Expected ' + + j$.pp(actual) + + (isNot ? ' not ' : ' ') + + englishyPredicate; + + if (expected.length > 0) { + for (var i = 0; i < expected.length; i++) { + if (i > 0) { + message += ','; + } + message += ' ' + j$.pp(expected[i]); + } + } + + return message + '.'; + } + }; + + function isAsymmetric(obj) { + return obj && j$.isA_('Function', obj.asymmetricMatch); + } + + function asymmetricMatch(a, b) { + var asymmetricA = isAsymmetric(a), + asymmetricB = isAsymmetric(b); + + if (asymmetricA && asymmetricB) { + return undefined; + } + + if (asymmetricA) { + return a.asymmetricMatch(b); + } + + if (asymmetricB) { + return b.asymmetricMatch(a); + } + } + + // Equality function lovingly adapted from isEqual in + // [Underscore](http://underscorejs.org) + function eq(a, b, aStack, bStack, customTesters) { + var result = true; + + var asymmetricResult = asymmetricMatch(a, b); + if (!j$.util.isUndefined(asymmetricResult)) { + return asymmetricResult; + } + + for (var i = 0; i < customTesters.length; i++) { + var customTesterResult = customTesters[i](a, b); + if (!j$.util.isUndefined(customTesterResult)) { + return customTesterResult; + } + } + + if (a instanceof Error && b instanceof Error) { + return a.message == b.message; + } + + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) { return a !== 0 || 1 / a == 1 / b; } + // A strict comparison is necessary because `null == undefined`. + if (a === null || b === null) { return a === b; } + var className = Object.prototype.toString.call(a); + if (className != Object.prototype.toString.call(b)) { return false; } + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a === 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') { return false; } + + var aIsDomNode = j$.isDomNode(a); + var bIsDomNode = j$.isDomNode(b); + if (aIsDomNode && bIsDomNode) { + // At first try to use DOM3 method isEqualNode + if (a.isEqualNode) { + return a.isEqualNode(b); + } + // IE8 doesn't support isEqualNode, try to use outerHTML && innerText + var aIsElement = a instanceof Element; + var bIsElement = b instanceof Element; + if (aIsElement && bIsElement) { + return a.outerHTML == b.outerHTML; + } + if (aIsElement || bIsElement) { + return false; + } + return a.innerText == b.innerText && a.textContent == b.textContent; + } + if (aIsDomNode || bIsDomNode) { + return false; + } + + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) { return bStack[length] == b; } + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0; + // Recursively compare objects and arrays. + // Compare array lengths to determine if a deep comparison is necessary. + if (className == '[object Array]' && a.length !== b.length) { + result = false; + } + + if (result) { + // Objects with different constructors are not equivalent, but `Object`s + // or `Array`s from different frames are. + if (className !== '[object Array]') { + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction(aCtor) && aCtor instanceof aCtor && + isFunction(bCtor) && bCtor instanceof bCtor)) { + return false; + } + } + // Deep compare objects. + for (var key in a) { + if (has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = has(b, key) && eq(a[key], b[key], aStack, bStack, customTesters))) { break; } + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (has(b, key) && !(size--)) { break; } + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + + return result; + + function has(obj, key) { + return Object.prototype.hasOwnProperty.call(obj, key); + } + + function isFunction(obj) { + return typeof obj === 'function'; + } + } +}; + +getJasmineRequireObj().toBe = function() { + function toBe() { + return { + compare: function(actual, expected) { + return { + pass: actual === expected + }; + } + }; + } - scope.$on('$routeChangeSuccess', update); - update(); + return toBe; +}; - function cleanupLastView() { - if (previousLeaveAnimation) { - $animate.cancel(previousLeaveAnimation); - previousLeaveAnimation = null; - } +getJasmineRequireObj().toBeCloseTo = function() { - if (currentScope) { - currentScope.$destroy(); - currentScope = null; - } - if (currentElement) { - previousLeaveAnimation = $animate.leave(currentElement); - previousLeaveAnimation.then(function() { - previousLeaveAnimation = null; - }); - currentElement = null; - } - } + function toBeCloseTo() { + return { + compare: function(actual, expected, precision) { + if (precision !== 0) { + precision = precision || 2; + } - function update() { - var locals = $route.current && $route.current.locals, - template = locals && locals.$template; + return { + pass: Math.abs(expected - actual) < (Math.pow(10, -precision) / 2) + }; + } + }; + } + + return toBeCloseTo; +}; + +getJasmineRequireObj().toBeDefined = function() { + function toBeDefined() { + return { + compare: function(actual) { + return { + pass: (void 0 !== actual) + }; + } + }; + } + + return toBeDefined; +}; + +getJasmineRequireObj().toBeFalsy = function() { + function toBeFalsy() { + return { + compare: function(actual) { + return { + pass: !!!actual + }; + } + }; + } - if (angular.isDefined(template)) { - var newScope = scope.$new(); - var current = $route.current; + return toBeFalsy; +}; - // Note: This will also link all children of ng-view that were contained in the original - // html. If that content contains controllers, ... they could pollute/change the scope. - // However, using ng-view on an element with additional content does not make sense... - // Note: We can't remove them in the cloneAttchFn of $transclude as that - // function is called before linking the content, which would apply child - // directives to non existing elements. - var clone = $transclude(newScope, function(clone) { - $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { - if (angular.isDefined(autoScrollExp) - && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } - }); - cleanupLastView(); - }); +getJasmineRequireObj().toBeGreaterThan = function() { - currentElement = clone; - currentScope = current.scope = newScope; - currentScope.$emit('$viewContentLoaded'); - currentScope.$eval(onloadExp); - } else { - cleanupLastView(); - } - } - } - }; - } + function toBeGreaterThan() { + return { + compare: function(actual, expected) { + return { + pass: actual > expected + }; + } + }; + } -// This directive is called during the $transclude call of the first `ngView` directive. -// It will replace and compile the content of the element with the loaded template. -// We need this directive so that the element content is already filled when -// the link function of another directive on the same element as ngView -// is called. - ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; - function ngViewFillContentFactory($compile, $controller, $route) { - return { - restrict: 'ECA', - priority: -400, - link: function(scope, $element) { - var current = $route.current, - locals = current.locals; + return toBeGreaterThan; +}; - $element.html(locals.$template); - var link = $compile($element.contents()); +getJasmineRequireObj().toBeLessThan = function() { + function toBeLessThan() { + return { - if (current.controller) { - locals.$scope = scope; - var controller = $controller(current.controller, locals); - if (current.controllerAs) { - scope[current.controllerAs] = controller; - } - $element.data('$ngControllerController', controller); - $element.children().data('$ngControllerController', controller); - } + compare: function(actual, expected) { + return { + pass: actual < expected + }; + } + }; + } + + return toBeLessThan; +}; +getJasmineRequireObj().toBeNaN = function(j$) { + + function toBeNaN() { + return { + compare: function(actual) { + var result = { + pass: (actual !== actual) + }; - link(scope); - } - }; - } + if (result.pass) { + result.message = 'Expected actual not to be NaN.'; + } else { + result.message = function() { return 'Expected ' + j$.pp(actual) + ' to be NaN.'; }; + } + return result; + } + }; + } -})(window, window.angular); + return toBeNaN; +}; -/** - * @license AngularJS v1.4.8 - * (c) 2010-2015 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) {'use strict'; +getJasmineRequireObj().toBeNull = function() { - /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * Any commits to this file should be reviewed with security in mind. * - * Changes to this file can potentially create security vulnerabilities. * - * An approval from 2 Core members with history of modifying * - * this file is required. * - * * - * Does the change somehow allow for arbitrary javascript to be executed? * - * Or allows for someone to change the prototype of built-in objects? * - * Or gives undesired access to variables likes document or window? * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + function toBeNull() { + return { + compare: function(actual) { + return { + pass: actual === null + }; + } + }; + } - var $sanitizeMinErr = angular.$$minErr('$sanitize'); + return toBeNull; +}; - /** - * @ngdoc module - * @name ngSanitize - * @description - * - * # ngSanitize - * - * The `ngSanitize` module provides functionality to sanitize HTML. - * - * - * <div doc-module-components="ngSanitize"></div> - * - * See {@link ngSanitize.$sanitize `$sanitize`} for usage. - */ +getJasmineRequireObj().toBeTruthy = function() { - /* - * HTML Parser By Misko Hevery (misko@hevery.com) - * based on: HTML Parser By John Resig (ejohn.org) - * Original code by Erik Arvidsson, Mozilla Public License - * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js - * - * // Use like so: - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - */ + function toBeTruthy() { + return { + compare: function(actual) { + return { + pass: !!actual + }; + } + }; + } + return toBeTruthy; +}; - /** - * @ngdoc service - * @name $sanitize - * @kind function - * - * @description - * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are - * then serialized back to properly escaped html string. This means that no unsafe input can make - * it into the returned string, however, since our parser is more strict than a typical browser - * parser, it's possible that some obscure input, which would be recognized as valid HTML by a - * browser, won't make it through the sanitizer. The input may also contain SVG markup. - * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and - * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. - * - * @param {string} html HTML input. - * @returns {string} Sanitized HTML. - * - * @example - <example module="sanitizeExample" deps="angular-sanitize.js"> - <file name="index.html"> - <script> - angular.module('sanitizeExample', ['ngSanitize']) - .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) { - $scope.snippet = - '<p style="color:blue">an html\n' + - '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + - 'snippet</p>'; - $scope.deliberatelyTrustDangerousSnippet = function() { - return $sce.trustAsHtml($scope.snippet); - }; - }]); - </script> - <div ng-controller="ExampleController"> - Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> - <table> - <tr> - <td>Directive</td> - <td>How</td> - <td>Source</td> - <td>Rendered</td> - </tr> - <tr id="bind-html-with-sanitize"> - <td>ng-bind-html</td> - <td>Automatically uses $sanitize</td> - <td><pre><div ng-bind-html="snippet"><br/></div></pre></td> - <td><div ng-bind-html="snippet"></div></td> - </tr> - <tr id="bind-html-with-trust"> - <td>ng-bind-html</td> - <td>Bypass $sanitize by explicitly trusting the dangerous value</td> - <td> - <pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()"> - </div></pre> - </td> - <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td> - </tr> - <tr id="bind-default"> - <td>ng-bind</td> - <td>Automatically escapes</td> - <td><pre><div ng-bind="snippet"><br/></div></pre></td> - <td><div ng-bind="snippet"></div></td> - </tr> - </table> - </div> - </file> - <file name="protractor.js" type="protractor"> - it('should sanitize the html snippet by default', function() { - expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). - toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); - }); +getJasmineRequireObj().toBeUndefined = function() { - it('should inline raw snippet if bound to a trusted value', function() { - expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). - toBe("<p style=\"color:blue\">an html\n" + - "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + - "snippet</p>"); - }); + function toBeUndefined() { + return { + compare: function(actual) { + return { + pass: void 0 === actual + }; + } + }; + } - it('should escape snippet without any filter', function() { - expect(element(by.css('#bind-default div')).getInnerHtml()). - toBe("<p style=\"color:blue\">an html\n" + - "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + - "snippet</p>"); - }); + return toBeUndefined; +}; - it('should update', function() { - element(by.model('snippet')).clear(); - element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>'); - expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). - toBe('new <b>text</b>'); - expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( - 'new <b onclick="alert(1)">text</b>'); - expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( - "new <b onclick=\"alert(1)\">text</b>"); - }); - </file> - </example> - */ - function $SanitizeProvider() { - this.$get = ['$$sanitizeUri', function($$sanitizeUri) { - return function(html) { - var buf = []; - htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { - return !/^unsafe/.test($$sanitizeUri(uri, isImage)); - })); - return buf.join(''); - }; - }]; - } +getJasmineRequireObj().toContain = function() { + function toContain(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + + return { + compare: function(actual, expected) { + + return { + pass: util.contains(actual, expected, customEqualityTesters) + }; + } + }; + } - function sanitizeText(chars) { - var buf = []; - var writer = htmlSanitizeWriter(buf, angular.noop); - writer.chars(chars); - return buf.join(''); - } + return toContain; +}; +getJasmineRequireObj().toEqual = function() { -// Regular Expressions for parsing tags and attributes - var START_TAG_REGEXP = - /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/, - END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/, - ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, - BEGIN_TAG_REGEXP = /^</, - BEGING_END_TAGE_REGEXP = /^<\//, - COMMENT_REGEXP = /<!--(.*?)-->/g, - DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i, - CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g, - SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, - // Match everything outside of normal chars and " (quote character) - NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; + function toEqual(util, customEqualityTesters) { + customEqualityTesters = customEqualityTesters || []; + return { + compare: function(actual, expected) { + var result = { + pass: false + }; -// Good source of info about elements and attributes -// http://dev.w3.org/html5/spec/Overview.html#semantics -// http://simon.html5.org/html-elements + result.pass = util.equals(actual, expected, customEqualityTesters); -// Safe Void Elements - HTML5 -// http://dev.w3.org/html5/spec/Overview.html#void-elements - var voidElements = makeMap("area,br,col,hr,img,wbr"); + return result; + } + }; + } -// Elements that you can, intentionally, leave open (and which close themselves) -// http://dev.w3.org/html5/spec/Overview.html#optional-tags - var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), - optionalEndTagInlineElements = makeMap("rp,rt"), - optionalEndTagElements = angular.extend({}, - optionalEndTagInlineElements, - optionalEndTagBlockElements); + return toEqual; +}; -// Safe Block Elements - HTML5 - var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + - "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + - "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); +getJasmineRequireObj().toHaveBeenCalled = function(j$) { -// Inline Elements - HTML5 - var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + - "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + - "samp,small,span,strike,strong,sub,sup,time,tt,u,var")); + function toHaveBeenCalled() { + return { + compare: function(actual) { + var result = {}; -// SVG Elements -// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements -// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted. -// They can potentially allow for arbitrary javascript to be executed. See #11290 - var svgElements = makeMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," + - "hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," + - "radialGradient,rect,stop,svg,switch,text,title,tspan,use"); + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } -// Special Elements (can contain anything) - var specialElements = makeMap("script,style"); + if (arguments.length > 1) { + throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith'); + } - var validElements = angular.extend({}, - voidElements, - blockElements, - inlineElements, - optionalEndTagElements, - svgElements); + result.pass = actual.calls.any(); -//Attributes that have href and hence need to be sanitized - var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href"); + result.message = result.pass ? + 'Expected spy ' + actual.and.identity() + ' not to have been called.' : + 'Expected spy ' + actual.and.identity() + ' to have been called.'; - var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + - 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + - 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + - 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' + - 'valign,value,vspace,width'); + return result; + } + }; + } -// SVG attributes (without "id" and "name" attributes) -// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes - var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + - 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' + - 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' + - 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' + - 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' + - 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' + - 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' + - 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' + - 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' + - 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' + - 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' + - 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' + - 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' + - 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' + - 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true); + return toHaveBeenCalled; +}; - var validAttrs = angular.extend({}, - uriAttrs, - svgAttrs, - htmlAttrs); +getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { - function makeMap(str, lowercaseKeys) { - var obj = {}, items = str.split(','), i; - for (i = 0; i < items.length; i++) { - obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true; - } - return obj; - } + function toHaveBeenCalledWith(util, customEqualityTesters) { + return { + compare: function() { + var args = Array.prototype.slice.call(arguments, 0), + actual = args[0], + expectedArgs = args.slice(1), + result = { pass: false }; + if (!j$.isSpy(actual)) { + throw new Error('Expected a spy, but got ' + j$.pp(actual) + '.'); + } - /** - * @example - * htmlParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - * @param {string} html string - * @param {object} handler - */ - function htmlParser(html, handler) { - if (typeof html !== 'string') { - if (html === null || typeof html === 'undefined') { - html = ''; - } else { - html = '' + html; - } - } - var index, chars, match, stack = [], last = html, text; - stack.last = function() { return stack[stack.length - 1]; }; + if (!actual.calls.any()) { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; + return result; + } - while (html) { - text = ''; - chars = true; + if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { + result.pass = true; + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; + } else { + result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; + } - // Make sure we're not in a script or style element - if (!stack.last() || !specialElements[stack.last()]) { + return result; + } + }; + } - // Comment - if (html.indexOf("<!--") === 0) { - // comments containing -- are not allowed unless they terminate the comment - index = html.indexOf("--", 4); + return toHaveBeenCalledWith; +}; - if (index >= 0 && html.lastIndexOf("-->", index) === index) { - if (handler.comment) handler.comment(html.substring(4, index)); - html = html.substring(index + 3); - chars = false; - } - // DOCTYPE - } else if (DOCTYPE_REGEXP.test(html)) { - match = html.match(DOCTYPE_REGEXP); +getJasmineRequireObj().toMatch = function(j$) { - if (match) { - html = html.replace(match[0], ''); - chars = false; - } - // end tag - } else if (BEGING_END_TAGE_REGEXP.test(html)) { - match = html.match(END_TAG_REGEXP); + function toMatch() { + return { + compare: function(actual, expected) { + if (!j$.isString_(expected) && !j$.isA_('RegExp', expected)) { + throw new Error('Expected is not a String or a RegExp'); + } - if (match) { - html = html.substring(match[0].length); - match[0].replace(END_TAG_REGEXP, parseEndTag); - chars = false; - } + var regexp = new RegExp(expected); - // start tag - } else if (BEGIN_TAG_REGEXP.test(html)) { - match = html.match(START_TAG_REGEXP); + return { + pass: regexp.test(actual) + }; + } + }; + } - if (match) { - // We only have a valid start-tag if there is a '>'. - if (match[4]) { - html = html.substring(match[0].length); - match[0].replace(START_TAG_REGEXP, parseStartTag); - } - chars = false; - } else { - // no ending tag found --- this piece should be encoded as an entity. - text += '<'; - html = html.substring(1); - } - } + return toMatch; +}; - if (chars) { - index = html.indexOf("<"); +getJasmineRequireObj().toThrow = function(j$) { - text += index < 0 ? html : html.substring(0, index); - html = index < 0 ? "" : html.substring(index); + function toThrow(util) { + return { + compare: function(actual, expected) { + var result = { pass: false }, + threw = false, + thrown; - if (handler.chars) handler.chars(decodeEntities(text)); - } + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } - } else { - // IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w]. - html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), - function(all, text) { - text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } - if (handler.chars) handler.chars(decodeEntities(text)); + if (!threw) { + result.message = 'Expected function to throw an exception.'; + return result; + } - return ""; - }); + if (arguments.length == 1) { + result.pass = true; + result.message = function() { return 'Expected function not to throw, but it threw ' + j$.pp(thrown) + '.'; }; - parseEndTag("", stack.last()); - } + return result; + } - if (html == last) { - throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + - "of html: {0}", html); - } - last = html; - } + if (util.equals(thrown, expected)) { + result.pass = true; + result.message = function() { return 'Expected function not to throw ' + j$.pp(expected) + '.'; }; + } else { + result.message = function() { return 'Expected function to throw ' + j$.pp(expected) + ', but it threw ' + j$.pp(thrown) + '.'; }; + } - // Clean up any remaining tags - parseEndTag(); + return result; + } + }; + } + + return toThrow; +}; + +getJasmineRequireObj().toThrowError = function(j$) { + function toThrowError (util) { + return { + compare: function(actual) { + var threw = false, + pass = {pass: true}, + fail = {pass: false}, + thrown; + + if (typeof actual != 'function') { + throw new Error('Actual is not a Function'); + } - function parseStartTag(tag, tagName, rest, unary) { - tagName = angular.lowercase(tagName); - if (blockElements[tagName]) { - while (stack.last() && inlineElements[stack.last()]) { - parseEndTag("", stack.last()); - } - } + var errorMatcher = getMatcher.apply(null, arguments); - if (optionalEndTagElements[tagName] && stack.last() == tagName) { - parseEndTag("", tagName); - } + try { + actual(); + } catch (e) { + threw = true; + thrown = e; + } - unary = voidElements[tagName] || !!unary; + if (!threw) { + fail.message = 'Expected function to throw an Error.'; + return fail; + } - if (!unary) { - stack.push(tagName); - } + if (!(thrown instanceof Error)) { + fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; + return fail; + } - var attrs = {}; + if (errorMatcher.hasNoSpecifics()) { + pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; + return pass; + } - rest.replace(ATTR_REGEXP, - function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { - var value = doubleQuotedValue - || singleQuotedValue - || unquotedValue - || ''; + if (errorMatcher.matches(thrown)) { + pass.message = function() { + return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; + }; + return pass; + } else { + fail.message = function() { + return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + + ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; + }; + return fail; + } + } + }; - attrs[name] = decodeEntities(value); - }); - if (handler.start) handler.start(tagName, attrs, unary); - } + function getMatcher() { + var expected = null, + errorType = null; + + if (arguments.length == 2) { + expected = arguments[1]; + if (isAnErrorType(expected)) { + errorType = expected; + expected = null; + } + } else if (arguments.length > 2) { + errorType = arguments[1]; + expected = arguments[2]; + if (!isAnErrorType(errorType)) { + throw new Error('Expected error type is not an Error.'); + } + } + + if (expected && !isStringOrRegExp(expected)) { + if (errorType) { + throw new Error('Expected error message is not a string or RegExp.'); + } else { + throw new Error('Expected is not an Error, string, or RegExp.'); + } + } + + function messageMatch(message) { + if (typeof expected == 'string') { + return expected == message; + } else { + return expected.test(message); + } + } + + return { + errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', + thrownDescription: function(thrown) { + var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', + thrownMessage = ''; + + if (expected) { + thrownMessage = ' with message ' + j$.pp(thrown.message); + } + + return thrownName + thrownMessage; + }, + messageDescription: function() { + if (expected === null) { + return ''; + } else if (expected instanceof RegExp) { + return ' with a message matching ' + j$.pp(expected); + } else { + return ' with message ' + j$.pp(expected); + } + }, + hasNoSpecifics: function() { + return expected === null && errorType === null; + }, + matches: function(error) { + return (errorType === null || error instanceof errorType) && + (expected === null || messageMatch(error.message)); + } + }; + } + + function isStringOrRegExp(potential) { + return potential instanceof RegExp || (typeof potential == 'string'); + } + + function isAnErrorType(type) { + if (typeof type !== 'function') { + return false; + } + + var Surrogate = function() {}; + Surrogate.prototype = type.prototype; + return (new Surrogate()) instanceof Error; + } + } + + return toThrowError; +}; - function parseEndTag(tag, tagName) { - var pos = 0, i; - tagName = angular.lowercase(tagName); - if (tagName) { - // Find the closest opened tag of the same type - for (pos = stack.length - 1; pos >= 0; pos--) { - if (stack[pos] == tagName) break; - } - } +getJasmineRequireObj().interface = function(jasmine, env) { + var jasmineInterface = { + describe: function(description, specDefinitions) { + return env.describe(description, specDefinitions); + }, - if (pos >= 0) { - // Close all the open elements, up the stack - for (i = stack.length - 1; i >= pos; i--) - if (handler.end) handler.end(stack[i]); + xdescribe: function(description, specDefinitions) { + return env.xdescribe(description, specDefinitions); + }, - // Remove the open elements from the stack - stack.length = pos; - } - } - } + fdescribe: function(description, specDefinitions) { + return env.fdescribe(description, specDefinitions); + }, - var hiddenPre=document.createElement("pre"); - /** - * decodes all entities into regular string - * @param value - * @returns {string} A string with decoded entities. - */ - function decodeEntities(value) { - if (!value) { return ''; } + it: function() { + return env.it.apply(env, arguments); + }, - hiddenPre.innerHTML = value.replace(/</g,"<"); - // innerText depends on styling as it doesn't display hidden elements. - // Therefore, it's better to use textContent not to cause unnecessary reflows. - return hiddenPre.textContent; - } + xit: function() { + return env.xit.apply(env, arguments); + }, - /** - * Escapes all potentially dangerous characters, so that the - * resulting string can be safely inserted into attribute or - * element text. - * @param value - * @returns {string} escaped text - */ - function encodeEntities(value) { - return value. - replace(/&/g, '&'). - replace(SURROGATE_PAIR_REGEXP, function(value) { - var hi = value.charCodeAt(0); - var low = value.charCodeAt(1); - return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';'; - }). - replace(NON_ALPHANUMERIC_REGEXP, function(value) { - return '&#' + value.charCodeAt(0) + ';'; - }). - replace(/</g, '<'). - replace(/>/g, '>'); - } + fit: function() { + return env.fit.apply(env, arguments); + }, - /** - * create an HTML/XML writer which writes to buffer - * @param {Array} buf use buf.jain('') to get out sanitized html string - * @returns {object} in the form of { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * } - */ - function htmlSanitizeWriter(buf, uriValidator) { - var ignore = false; - var out = angular.bind(buf, buf.push); - return { - start: function(tag, attrs, unary) { - tag = angular.lowercase(tag); - if (!ignore && specialElements[tag]) { - ignore = tag; - } - if (!ignore && validElements[tag] === true) { - out('<'); - out(tag); - angular.forEach(attrs, function(value, key) { - var lkey=angular.lowercase(key); - var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); - if (validAttrs[lkey] === true && - (uriAttrs[lkey] !== true || uriValidator(value, isImage))) { - out(' '); - out(key); - out('="'); - out(encodeEntities(value)); - out('"'); - } - }); - out(unary ? '/>' : '>'); - } - }, - end: function(tag) { - tag = angular.lowercase(tag); - if (!ignore && validElements[tag] === true) { - out('</'); - out(tag); - out('>'); - } - if (tag == ignore) { - ignore = false; - } - }, - chars: function(chars) { - if (!ignore) { - out(encodeEntities(chars)); - } - } - }; - } + beforeEach: function() { + return env.beforeEach.apply(env, arguments); + }, + afterEach: function() { + return env.afterEach.apply(env, arguments); + }, -// define ngSanitize module and register $sanitize service - angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); + beforeAll: function() { + return env.beforeAll.apply(env, arguments); + }, - /* global sanitizeText: false */ + afterAll: function() { + return env.afterAll.apply(env, arguments); + }, - /** - * @ngdoc filter - * @name linky - * @kind function - * - * @description - * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and - * plain email address links. - * - * Requires the {@link ngSanitize `ngSanitize`} module to be installed. - * - * @param {string} text Input text. - * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. - * @returns {string} Html-linkified text. - * - * @usage - <span ng-bind-html="linky_expression | linky"></span> - * - * @example - <example module="linkyExample" deps="angular-sanitize.js"> - <file name="index.html"> - <script> - angular.module('linkyExample', ['ngSanitize']) - .controller('ExampleController', ['$scope', function($scope) { - $scope.snippet = - 'Pretty text with some links:\n'+ - 'http://angularjs.org/,\n'+ - 'mailto:us@somewhere.org,\n'+ - 'another@somewhere.org,\n'+ - 'and one more: ftp://127.0.0.1/.'; - $scope.snippetWithTarget = 'http://angularjs.org/'; - }]); - </script> - <div ng-controller="ExampleController"> - Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> - <table> - <tr> - <td>Filter</td> - <td>Source</td> - <td>Rendered</td> - </tr> - <tr id="linky-filter"> - <td>linky filter</td> - <td> - <pre><div ng-bind-html="snippet | linky"><br></div></pre> - </td> - <td> - <div ng-bind-html="snippet | linky"></div> - </td> - </tr> - <tr id="linky-target"> - <td>linky target</td> - <td> - <pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre> - </td> - <td> - <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div> - </td> - </tr> - <tr id="escaped-html"> - <td>no filter</td> - <td><pre><div ng-bind="snippet"><br></div></pre></td> - <td><div ng-bind="snippet"></div></td> - </tr> - </table> - </file> - <file name="protractor.js" type="protractor"> - it('should linkify the snippet with urls', function() { - expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). - toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' + - 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); - expect(element.all(by.css('#linky-filter a')).count()).toEqual(4); - }); + expect: function(actual) { + return env.expect(actual); + }, - it('should not linkify snippet without the linky filter', function() { - expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()). - toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' + - 'another@somewhere.org, and one more: ftp://127.0.0.1/.'); - expect(element.all(by.css('#escaped-html a')).count()).toEqual(0); - }); + pending: function() { + return env.pending.apply(env, arguments); + }, - it('should update', function() { - element(by.model('snippet')).clear(); - element(by.model('snippet')).sendKeys('new http://link.'); - expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()). - toBe('new http://link.'); - expect(element.all(by.css('#linky-filter a')).count()).toEqual(1); - expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()) - .toBe('new http://link.'); - }); + fail: function() { + return env.fail.apply(env, arguments); + }, - it('should work with the target property', function() { - expect(element(by.id('linky-target')). - element(by.binding("snippetWithTarget | linky:'_blank'")).getText()). - toBe('http://angularjs.org/'); - expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank'); - }); - </file> - </example> - */ - angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { - var LINKY_URL_REGEXP = - /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, - MAILTO_REGEXP = /^mailto:/i; + spyOn: function(obj, methodName) { + return env.spyOn(obj, methodName); + }, - return function(text, target) { - if (!text) return text; - var match; - var raw = text; - var html = []; - var url; - var i; - while ((match = raw.match(LINKY_URL_REGEXP))) { - // We can not end in these as they are sometimes found at the end of the sentence - url = match[0]; - // if we did not match ftp/http/www/mailto then assume mailto - if (!match[2] && !match[4]) { - url = (match[3] ? 'http://' : 'mailto:') + url; - } - i = match.index; - addText(raw.substr(0, i)); - addLink(url, match[0].replace(MAILTO_REGEXP, '')); - raw = raw.substring(i + match[0].length); - } - addText(raw); - return $sanitize(html.join('')); + jsApiReporter: new jasmine.JsApiReporter({ + timer: new jasmine.Timer() + }), - function addText(text) { - if (!text) { - return; - } - html.push(sanitizeText(text)); - } + jasmine: jasmine + }; - function addLink(url, text) { - html.push('<a '); - if (angular.isDefined(target)) { - html.push('target="', - target, - '" '); - } - html.push('href="', - url.replace(/"/g, '"'), - '">'); - addText(text); - html.push('</a>'); - } - }; - }]); + jasmine.addCustomEqualityTester = function(tester) { + env.addCustomEqualityTester(tester); + }; + jasmine.addMatchers = function(matchers) { + return env.addMatchers(matchers); + }; -})(window, window.angular); + jasmine.clock = function() { + return env.clock; + }; + + return jasmineInterface; +}; + +getJasmineRequireObj().version = function() { + return '2.3.4'; +}; (function() { 'use strict'; diff --git a/src/reStart-app/core/Page.ctrl.js b/src/reStart-app/core/Page.ctrl.js index 3a9ae43..d8e723a 100644 --- a/src/reStart-app/core/Page.ctrl.js +++ b/src/reStart-app/core/Page.ctrl.js @@ -133,5 +133,10 @@ $log.error(msg); } + PageCtrl.enterMobile = _enterMobile;//test code + PageCtrl.exitMobile = _exitMobile;//test code + PageCtrl.loadingOn = _loadingOn;//test code + PageCtrl.loadingOff = _loadingOff;//test code + return PageCtrl;//test code } }()); \ No newline at end of file diff --git a/src/reStart-app/modules/header/navControl.dir.js b/src/reStart-app/modules/header/navControl.dir.js index 5c49423..d6d92bf 100644 --- a/src/reStart-app/modules/header/navControl.dir.js +++ b/src/reStart-app/modules/header/navControl.dir.js @@ -1,4 +1,4 @@ -(function() { +(function () { 'use strict'; angular @@ -89,7 +89,6 @@ * Toggle nav open/closed */ function toggleNav() { - console.log("yo"); if (!_navOpen) { _openNav(); } else { diff --git a/src/reStart-app/pages/error404/Error404.ctrl.js b/src/reStart-app/pages/error404/Error404.ctrl.js index fee54c4..4ecf8df 100644 --- a/src/reStart-app/pages/error404/Error404.ctrl.js +++ b/src/reStart-app/pages/error404/Error404.ctrl.js @@ -1,4 +1,4 @@ -(function() { +(function () { 'use strict'; angular @@ -29,7 +29,7 @@ } return { //test code - init: _init //test code - } //test code + init: _init //test code + }; //test code } }()); \ No newline at end of file diff --git a/src/reStart-app/pages/home/Home.ctrl.js b/src/reStart-app/pages/home/Home.ctrl.js index 4cb1b93..da2f599 100644 --- a/src/reStart-app/pages/home/Home.ctrl.js +++ b/src/reStart-app/pages/home/Home.ctrl.js @@ -1,4 +1,4 @@ -(function() { +(function () { 'use strict'; angular @@ -89,15 +89,14 @@ } function getView() { //test code - return home.viewformat; //test code + return home.viewformat; //test code } //test code - return { //test code - enterMobile: _enterMobile, //test code - exitMobile: _exitMobile, //test code - getJsonSucess: _getJsonSuccess, //test code - activate: _activate, //test code - getView: getView //test code - } //test code + home.enterMobile = _enterMobile; //test code + home.exitMobile = _exitMobile; //test code + home.getJsonSucess = _getJsonSuccess; //test code + home.activate = _activate; //test code + home.getView = getView; //test code + return home; //test code } }()); \ No newline at end of file diff --git a/src/reStart-app/reStart-app.js b/src/reStart-app/reStart-app.js index 10070d4..f7c419f 100644 --- a/src/reStart-app/reStart-app.js +++ b/src/reStart-app/reStart-app.js @@ -1,517 +1,522 @@ -// application module setter -(function() { - 'use strict'; - - angular - .module('reStart', ['ngRoute', 'ngResource', 'ngSanitize', 'mediaCheck', 'resize']); +// application module setter +(function() { + 'use strict'; + + angular + .module('reStart', ['ngRoute', 'ngResource', 'ngSanitize', 'mediaCheck', 'resize']); }()); -(function() { - 'use strict'; - - angular - .module('reStart') - .controller('PageCtrl', PageCtrl); - - PageCtrl.$inject = ['Page', '$scope', 'MQ', 'mediaCheck', '$log']; - - function PageCtrl(Page, $scope, MQ, mediaCheck, $log) { - var page = this; - - // private variables - var _handlingRouteChangeError = false; - // Set up functionality to run on enter/exit of media query - var _mc = mediaCheck.init({ - scope: $scope, - media: { - mq: MQ.SMALL, - enter: _enterMobile, - exit: _exitMobile - }, - debounce: 200 - }); - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // associate page <title> - page.pageTitle = Page; - - $scope.$on('$routeChangeStart', _routeChangeStart); - $scope.$on('$routeChangeSuccess', _routeChangeSuccess); - $scope.$on('$routeChangeError', _routeChangeError); - } - - /** - * Enter mobile media query - * $broadcast 'enter-mobile' event - * - * @private - */ - function _enterMobile() { - $scope.$broadcast('enter-mobile'); - } - - /** - * Exit mobile media query - * $broadcast 'exit-mobile' event - * - * @private - */ - function _exitMobile() { - $scope.$broadcast('exit-mobile'); - } - - /** - * Turn on loading state - * - * @private - */ - function _loadingOn() { - $scope.$broadcast('loading-on'); - } - - /** - * Turn off loading state - * - * @private - */ - function _loadingOff() { - $scope.$broadcast('loading-off'); - } - - /** - * Route change start handler - * If next route has resolve, turn on loading - * - * @param $event {object} - * @param next {object} - * @param current {object} - * @private - */ - function _routeChangeStart($event, next, current) { - if (next.$$route && next.$$route.resolve) { // eslint-disable-line angular/no-private-call - _loadingOn(); - } - } - - /** - * Route change success handler - * Match current media query and run appropriate function - * If current route has been resolved, turn off loading - * - * @param $event {object} - * @param current {object} - * @param previous {object} - * @private - */ - function _routeChangeSuccess($event, current, previous) { - _mc.matchCurrent(MQ.SMALL); - - if (current.$$route && current.$$route.resolve) { // eslint-disable-line angular/no-private-call - _loadingOff(); - } - } - - /** - * Route change error handler - * Handle route resolve failures - * - * @param $event {object} - * @param current {object} - * @param previous {object} - * @param rejection {object} - * @private - */ - function _routeChangeError($event, current, previous, rejection) { - var destination = (current && (current.title || current.name || current.loadedTemplateUrl)) || 'unknown target'; - var msg = 'Error routing to ' + destination + '. ' + (rejection.msg || ''); - - if (_handlingRouteChangeError) { - return; - } - - _handlingRouteChangeError = true; - _loadingOff(); - - $log.error(msg); - } - } +(function() { + 'use strict'; + + angular + .module('reStart') + .controller('PageCtrl', PageCtrl); + + PageCtrl.$inject = ['Page', '$scope', 'MQ', 'mediaCheck', '$log']; + + function PageCtrl(Page, $scope, MQ, mediaCheck, $log) { + var page = this; + + // private variables + var _handlingRouteChangeError = false; + // Set up functionality to run on enter/exit of media query + var _mc = mediaCheck.init({ + scope: $scope, + media: { + mq: MQ.SMALL, + enter: _enterMobile, + exit: _exitMobile + }, + debounce: 200 + }); + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // associate page <title> + page.pageTitle = Page; + + $scope.$on('$routeChangeStart', _routeChangeStart); + $scope.$on('$routeChangeSuccess', _routeChangeSuccess); + $scope.$on('$routeChangeError', _routeChangeError); + } + + /** + * Enter mobile media query + * $broadcast 'enter-mobile' event + * + * @private + */ + function _enterMobile() { + $scope.$broadcast('enter-mobile'); + } + + /** + * Exit mobile media query + * $broadcast 'exit-mobile' event + * + * @private + */ + function _exitMobile() { + $scope.$broadcast('exit-mobile'); + } + + /** + * Turn on loading state + * + * @private + */ + function _loadingOn() { + $scope.$broadcast('loading-on'); + } + + /** + * Turn off loading state + * + * @private + */ + function _loadingOff() { + $scope.$broadcast('loading-off'); + } + + /** + * Route change start handler + * If next route has resolve, turn on loading + * + * @param $event {object} + * @param next {object} + * @param current {object} + * @private + */ + function _routeChangeStart($event, next, current) { + if (next.$$route && next.$$route.resolve) { // eslint-disable-line angular/no-private-call + _loadingOn(); + } + } + + /** + * Route change success handler + * Match current media query and run appropriate function + * If current route has been resolved, turn off loading + * + * @param $event {object} + * @param current {object} + * @param previous {object} + * @private + */ + function _routeChangeSuccess($event, current, previous) { + _mc.matchCurrent(MQ.SMALL); + + if (current.$$route && current.$$route.resolve) { // eslint-disable-line angular/no-private-call + _loadingOff(); + } + } + + /** + * Route change error handler + * Handle route resolve failures + * + * @param $event {object} + * @param current {object} + * @param previous {object} + * @param rejection {object} + * @private + */ + function _routeChangeError($event, current, previous, rejection) { + var destination = (current && (current.title || current.name || current.loadedTemplateUrl)) || 'unknown target'; + var msg = 'Error routing to ' + destination + '. ' + (rejection.msg || ''); + + if (_handlingRouteChangeError) { + return; + } + + _handlingRouteChangeError = true; + _loadingOff(); + + $log.error(msg); + } + PageCtrl.enterMobile = _enterMobile;//test code + PageCtrl.exitMobile = _exitMobile;//test code + PageCtrl.loadingOn = _loadingOn;//test code + PageCtrl.loadingOff = _loadingOff;//test code + return PageCtrl;//test code + } }()); -(function() { - 'use strict'; - - angular - .module('reStart') - .factory('Page', Page); - - function Page() { - // private vars - var siteTitle = 'reStart Angular'; - var pageTitle = 'Home'; - - // callable members - return { - getTitle: getTitle, - setTitle: setTitle - }; - - /** - * Title function - * Sets site title and page title - * - * @returns {string} site title + page title - */ - function getTitle() { - return siteTitle + ' | ' + pageTitle; - } - - /** - * Set page title - * - * @param newTitle {string} - */ - function setTitle(newTitle) { - pageTitle = newTitle; - } - } +(function() { + 'use strict'; + + angular + .module('reStart') + .factory('Page', Page); + + function Page() { + // private vars + var siteTitle = 'reStart Angular'; + var pageTitle = 'Home'; + + // callable members + return { + getTitle: getTitle, + setTitle: setTitle + }; + + /** + * Title function + * Sets site title and page title + * + * @returns {string} site title + page title + */ + function getTitle() { + return siteTitle + ' | ' + pageTitle; + } + + /** + * Set page title + * + * @param newTitle {string} + */ + function setTitle(newTitle) { + pageTitle = newTitle; + } + } }()); -// "global" object to share between controllers -(function() { - 'use strict'; - - angular - .module('reStart') - .factory('Utils', Utils); - - function Utils() { - var greeting = 'Hello'; - - // callable members - return { - greeting: greeting, - alertGreeting: alertGreeting - }; - - /** - * Alert greeting - * - * @param name {string} - */ - function alertGreeting(name) { - alert(greeting + ', ' + name + '!'); - } - } +// "global" object to share between controllers +(function() { + 'use strict'; + + angular + .module('reStart') + .factory('Utils', Utils); + + function Utils() { + var greeting = 'Hello'; + + // callable members + return { + greeting: greeting, + alertGreeting: alertGreeting + }; + + /** + * Alert greeting + * + * @param name {string} + */ + function alertGreeting(name) { + alert(greeting + ', ' + name + '!'); + } + } }()); -// application config -(function() { - 'use strict'; - - angular - .module('reStart') - .config(appConfig); - - appConfig.$inject = ['$routeProvider', '$locationProvider']; - - function appConfig($routeProvider, $locationProvider) { - $routeProvider - .when('/', { - templateUrl: 'reStart-app/pages/home/Home.view.html', - controller: 'HomeCtrl', - controllerAs: 'home' - }) - .when('/subpage', { - templateUrl: 'reStart-app/pages/sub/Sub.view.html', - controller: 'SubCtrl', - controllerAs: 'sub', - resolve: { - resolveLocalData: resolveLocalData - } - }) - .otherwise({ - templateUrl: 'reStart-app/pages/error404/Error404.view.html', - controller: 'Error404Ctrl', - controllerAs: 'e404' - }); - - $locationProvider - .html5Mode({ - enabled: true - }) - .hashPrefix('!'); - } - - resolveLocalData.$inject = ['JSONData']; - /** - * Get local data for route resolve - * - * @param JSONData {factory} - * @returns {promise} data - */ - function resolveLocalData(JSONData) { - return JSONData.getLocalData(); - } +// application config +(function() { + 'use strict'; + + angular + .module('reStart') + .config(appConfig); + + appConfig.$inject = ['$routeProvider', '$locationProvider']; + + function appConfig($routeProvider, $locationProvider) { + $routeProvider + .when('/', { + templateUrl: 'reStart-app/pages/home/Home.view.html', + controller: 'HomeCtrl', + controllerAs: 'home' + }) + .when('/subpage', { + templateUrl: 'reStart-app/pages/sub/Sub.view.html', + controller: 'SubCtrl', + controllerAs: 'sub', + resolve: { + resolveLocalData: resolveLocalData + } + }) + .otherwise({ + templateUrl: 'reStart-app/pages/error404/Error404.view.html', + controller: 'Error404Ctrl', + controllerAs: 'e404' + }); + + $locationProvider + .html5Mode({ + enabled: true + }) + .hashPrefix('!'); + } + + resolveLocalData.$inject = ['JSONData']; + /** + * Get local data for route resolve + * + * @param JSONData {factory} + * @returns {promise} data + */ + function resolveLocalData(JSONData) { + return JSONData.getLocalData(); + } }()); -// fetch JSON data to share between controllers -(function() { - 'use strict'; - - angular - .module('reStart') - .factory('JSONData', JSONData); - - JSONData.$inject = ['$http', 'Res']; - - function JSONData($http, Res) { - // callable members - return { - getLocalData: getLocalData - }; - - /** - * GET local JSON data file and return results - * - * @returns {promise} - */ - function getLocalData() { - return $http - .get('/data/data.json') - .then(Res.success, Res.error); - } - } +// fetch JSON data to share between controllers +(function() { + 'use strict'; + + angular + .module('reStart') + .factory('JSONData', JSONData); + + JSONData.$inject = ['$http', 'Res']; + + function JSONData($http, Res) { + // callable members + return { + getLocalData: getLocalData + }; + + /** + * GET local JSON data file and return results + * + * @returns {promise} + */ + function getLocalData() { + return $http + .get('/data/data.json') + .then(Res.success, Res.error); + } + } }()); -(function() { - 'use strict'; - - angular - .module('reStart') - .factory('Res', Res); - - function Res() { - // callable members - return { - success: success, - error: error - }; - - /** - * Promise response function - * Checks typeof data returned and succeeds if JS object, throws error if not - * Useful for APIs (ie, with nginx) where server error HTML page may be returned in error - * - * @param response {*} data from $http - * @returns {*} object, array - */ - function success(response) { - if (angular.isObject(response.data)) { - return response.data; - } else { - throw new Error('retrieved data is not typeof object.'); - } - } - - /** - * Promise response function - error - * Throws an error with error data - * - * @param error {object} - */ - function error(error) { - throw new Error('Error retrieving data', error); - } - } +(function() { + 'use strict'; + + angular + .module('reStart') + .factory('Res', Res); + + function Res() { + // callable members + return { + success: success, + error: error + }; + + /** + * Promise response function + * Checks typeof data returned and succeeds if JS object, throws error if not + * Useful for APIs (ie, with nginx) where server error HTML page may be returned in error + * + * @param response {*} data from $http + * @returns {*} object, array + */ + function success(response) { + if (angular.isObject(response.data)) { + return response.data; + } else { + throw new Error('retrieved data is not typeof object.'); + } + } + + /** + * Promise response function - error + * Throws an error with error data + * + * @param error {object} + */ + function error(error) { + throw new Error('Error retrieving data', error); + } + } }()); -(function() { - 'use strict'; - - // media query constants - var MQ = { - SMALL: '(max-width: 767px)', - LARGE: '(min-width: 768px)' - }; - - angular - .module('reStart') - .constant('MQ', MQ); +(function() { + 'use strict'; + + angular + .module('reStart') + .directive('loading', loading); + + loading.$inject = ['$window', 'resize']; + + function loading($window, resize) { + // return directive + return { + restrict: 'EA', + replace: true, + templateUrl: 'reStart-app/core/ui/loading.tpl.html', + transclude: true, + controller: loadingCtrl, + controllerAs: 'loading', + bindToController: true, + link: loadingLink + }; + + /** + * loading LINK + * Disables page scrolling when loading overlay is open + * + * @param $scope + * @param $element + * @param $attrs + * @param loading {controller} + */ + function loadingLink($scope, $element, $attrs, loading) { + // private variables + var _$body = angular.element('body'); + var _winHeight = $window.innerHeight + 'px'; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // initialize debounced resize + var _rs = resize.init({ + scope: $scope, + resizedFn: _resized, + debounce: 200 + }); + + // $watch active state + $scope.$watch('loading.active', _$watchActive); + } + + /** + * Window resized + * If loading, reapply body height + * to prevent scrollbar + * + * @private + */ + function _resized() { + _winHeight = $window.innerHeight + 'px'; + + if (loading.active) { + _$body.css({ + height: _winHeight, + overflowY: 'hidden' + }); + } + } + + /** + * $watch loading.active + * + * @param newVal {boolean} + * @param oldVal {undefined|boolean} + * @private + */ + function _$watchActive(newVal, oldVal) { + if (newVal) { + _open(); + } else { + _close(); + } + } + + /** + * Open loading + * Disable scroll + * + * @private + */ + function _open() { + _$body.css({ + height: _winHeight, + overflowY: 'hidden' + }); + } + + /** + * Close loading + * Enable scroll + * + * @private + */ + function _close() { + _$body.css({ + height: 'auto', + overflowY: 'auto' + }); + } + } + } + + loadingCtrl.$inject = ['$scope']; + /** + * loading CONTROLLER + * Update the loading status based + * on routeChange state + */ + function loadingCtrl($scope) { + var loading = this; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // turn on loading for initial page load + _loadingActive(); + + $scope.$on('loading-on', _loadingActive); + $scope.$on('loading-off', _loadingInactive); + } + + /** + * Set loading to active + * + * @private + */ + function _loadingActive() { + loading.active = true; + } + + /** + * Set loading to inactive + * + * @private + */ + function _loadingInactive() { + loading.active = false; + } + } + }()); -(function() { - 'use strict'; - - angular - .module('reStart') - .directive('loading', loading); - - loading.$inject = ['$window', 'resize']; - - function loading($window, resize) { - // return directive - return { - restrict: 'EA', - replace: true, - templateUrl: 'reStart-app/core/ui/loading.tpl.html', - transclude: true, - controller: loadingCtrl, - controllerAs: 'loading', - bindToController: true, - link: loadingLink - }; - - /** - * loading LINK - * Disables page scrolling when loading overlay is open - * - * @param $scope - * @param $element - * @param $attrs - * @param loading {controller} - */ - function loadingLink($scope, $element, $attrs, loading) { - // private variables - var _$body = angular.element('body'); - var _winHeight = $window.innerHeight + 'px'; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // initialize debounced resize - var _rs = resize.init({ - scope: $scope, - resizedFn: _resized, - debounce: 200 - }); - - // $watch active state - $scope.$watch('loading.active', _$watchActive); - } - - /** - * Window resized - * If loading, reapply body height - * to prevent scrollbar - * - * @private - */ - function _resized() { - _winHeight = $window.innerHeight + 'px'; - - if (loading.active) { - _$body.css({ - height: _winHeight, - overflowY: 'hidden' - }); - } - } - - /** - * $watch loading.active - * - * @param newVal {boolean} - * @param oldVal {undefined|boolean} - * @private - */ - function _$watchActive(newVal, oldVal) { - if (newVal) { - _open(); - } else { - _close(); - } - } - - /** - * Open loading - * Disable scroll - * - * @private - */ - function _open() { - _$body.css({ - height: _winHeight, - overflowY: 'hidden' - }); - } - - /** - * Close loading - * Enable scroll - * - * @private - */ - function _close() { - _$body.css({ - height: 'auto', - overflowY: 'auto' - }); - } - } - } - - loadingCtrl.$inject = ['$scope']; - /** - * loading CONTROLLER - * Update the loading status based - * on routeChange state - */ - function loadingCtrl($scope) { - var loading = this; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // turn on loading for initial page load - _loadingActive(); - - $scope.$on('loading-on', _loadingActive); - $scope.$on('loading-off', _loadingInactive); - } - - /** - * Set loading to active - * - * @private - */ - function _loadingActive() { - loading.active = true; - } - - /** - * Set loading to inactive - * - * @private - */ - function _loadingInactive() { - loading.active = false; - } - } - +(function() { + 'use strict'; + + // media query constants + var MQ = { + SMALL: '(max-width: 767px)', + LARGE: '(min-width: 768px)' + }; + + angular + .module('reStart') + .constant('MQ', MQ); }()); -(function() { - 'use strict'; - - angular - .module('reStart') - .filter('trustAsHTML', trustAsHTML); - - trustAsHTML.$inject = ['$sce']; - - function trustAsHTML($sce) { - return function(text) { - return $sce.trustAsHtml(text); - }; - } +(function() { + 'use strict'; + + angular + .module('reStart') + .filter('trustAsHTML', trustAsHTML); + + trustAsHTML.$inject = ['$sce']; + + function trustAsHTML($sce) { + return function(text) { + return $sce.trustAsHtml(text); + }; + } }()); (function() { 'use strict'; @@ -586,174 +591,178 @@ } }()); -(function() { - 'use strict'; - - angular - .module('reStart') - .directive('navControl', navControl); - - navControl.$inject = ['$window', 'resize']; - - function navControl($window, resize) { - // return directive - return { - restrict: 'EA', - link: navControlLink - }; - - /** - * navControl LINK function - * - * @param $scope - */ - function navControlLink($scope) { - // private variables - var _$body = angular.element('body'); - var _layoutCanvas = _$body.find('.layout-canvas'); - var _navOpen; - - // data model - $scope.nav = {}; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // initialize debounced resize - var _rs = resize.init({ - scope: $scope, - resizedFn: _resized, - debounce: 100 - }); - - $scope.$on('$locationChangeStart', _$locationChangeStart); - $scope.$on('enter-mobile', _enterMobile); - $scope.$on('exit-mobile', _exitMobile); - } - - /** - * Resized window (debounced) - * - * @private - */ - function _resized() { - _layoutCanvas.css({ - minHeight: $window.innerHeight + 'px' - }); - } - - /** - * Open mobile navigation - * - * @private - */ - function _openNav() { - _$body - .removeClass('nav-closed') - .addClass('nav-open'); - - _navOpen = true; - } - - /** - * Close mobile navigation - * - * @private - */ - function _closeNav() { - _$body - .removeClass('nav-open') - .addClass('nav-closed'); - - _navOpen = false; - } - - /** - * Toggle nav open/closed - */ - function toggleNav() { - if (!_navOpen) { - _openNav(); - } else { - _closeNav(); - } - } - - /** - * When changing location, close the nav if it's open - */ - function _$locationChangeStart() { - if (_navOpen) { - _closeNav(); - } - } - - /** - * Function to execute when entering mobile media query - * Close nav and set up menu toggling functionality - * - * @private - */ - function _enterMobile(mq) { - _closeNav(); - - // bind function to toggle mobile navigation open/closed - $scope.nav.toggleNav = toggleNav; - } - - /** - * Function to execute when exiting mobile media query - * Disable menu toggling and remove body classes - * - * @private - */ - function _exitMobile(mq) { - // unbind function to toggle mobile navigation open/closed - $scope.nav.toggleNav = null; - - _$body.removeClass('nav-closed nav-open'); - } - } - } - +(function () { + 'use strict'; + + angular + .module('reStart') + .directive('navControl', navControl); + + navControl.$inject = ['$window', 'resize']; + + function navControl($window, resize) { + // return directive + return { + restrict: 'EA', + link: navControlLink + }; + + /** + * navControl LINK function + * + * @param $scope + */ + function navControlLink($scope) { + // private variables + var _$body = angular.element('body'); + var _layoutCanvas = _$body.find('.layout-canvas'); + var _navOpen; + + // data model + $scope.nav = {}; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // initialize debounced resize + var _rs = resize.init({ + scope: $scope, + resizedFn: _resized, + debounce: 100 + }); + + $scope.$on('$locationChangeStart', _$locationChangeStart); + $scope.$on('enter-mobile', _enterMobile); + $scope.$on('exit-mobile', _exitMobile); + } + + /** + * Resized window (debounced) + * + * @private + */ + function _resized() { + _layoutCanvas.css({ + minHeight: $window.innerHeight + 'px' + }); + } + + /** + * Open mobile navigation + * + * @private + */ + function _openNav() { + _$body + .removeClass('nav-closed') + .addClass('nav-open'); + + _navOpen = true; + } + + /** + * Close mobile navigation + * + * @private + */ + function _closeNav() { + _$body + .removeClass('nav-open') + .addClass('nav-closed'); + + _navOpen = false; + } + + /** + * Toggle nav open/closed + */ + function toggleNav() { + if (!_navOpen) { + _openNav(); + } else { + _closeNav(); + } + } + + /** + * When changing location, close the nav if it's open + */ + function _$locationChangeStart() { + if (_navOpen) { + _closeNav(); + } + } + + /** + * Function to execute when entering mobile media query + * Close nav and set up menu toggling functionality + * + * @private + */ + function _enterMobile(mq) { + _closeNav(); + + // bind function to toggle mobile navigation open/closed + $scope.nav.toggleNav = toggleNav; + } + + /** + * Function to execute when exiting mobile media query + * Disable menu toggling and remove body classes + * + * @private + */ + function _exitMobile(mq) { + // unbind function to toggle mobile navigation open/closed + $scope.nav.toggleNav = null; + + _$body.removeClass('nav-closed nav-open'); + } + } + } + }()); -(function() { - 'use strict'; - - angular - .module('reStart') - .controller('Error404Ctrl', Error404Ctrl); - - Error404Ctrl.$inject = ['$scope', 'Page']; - - function Error404Ctrl($scope, Page) { - var e404 = this; - - // bindable members - e404.title = '404 - Page Not Found'; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // set page <title> - Page.setTitle(e404.title); - - // no data to load, but loading state might be on - $scope.$emit('loading-off'); - } - } +(function () { + 'use strict'; + + angular + .module('reStart') + .controller('Error404Ctrl', Error404Ctrl); + + Error404Ctrl.$inject = ['$scope', 'Page']; + + function Error404Ctrl($scope, Page) { + var e404 = this; + + // bindable members + e404.title = '404 - Page Not Found'; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // set page <title> + Page.setTitle(e404.title); + + // no data to load, but loading state might be on + $scope.$emit('loading-off'); + } + + return { //test code + init: _init //test code + }; //test code + } }()); -(function() { +(function () { 'use strict'; angular @@ -842,37 +851,18 @@ function _exitMobile() { home.viewformat = 'large'; } - } -}()); -(function() { - 'use strict'; - angular - .module('reStart') - .controller('SubCtrl', SubCtrl); + function getView() { //test code + return home.viewformat; //test code + } //test code - SubCtrl.$inject = ['Utils', 'Page', 'resolveLocalData']; + home.enterMobile = _enterMobile; //test code + home.exitMobile = _exitMobile; //test code + home.getJsonSucess = _getJsonSuccess; //test code + home.activate = _activate; //test code + home.getView = getView; //test code - function SubCtrl(Utils, Page, resolveLocalData) { - // controllerAs ViewModel - var sub = this; - - // bindable members - sub.title = 'Subpage'; - sub.global = Utils; - sub.json = resolveLocalData; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // set page <title> - Page.setTitle(sub.title); - } + return home; } }()); /** @@ -958,4 +948,35 @@ } }()); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5tb2R1bGUuanMiLCJjb3JlL1BhZ2UuY3RybC5qcyIsImNvcmUvUGFnZS5mYWN0b3J5LmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LmpzIiwiY29yZS9hcHAtc2V0dXAvYXBwLmNvbmZpZy5qcyIsImNvcmUvZ2V0LWRhdGEvSlNPTkRhdGEuZmFjdG9yeS5qcyIsImNvcmUvZ2V0LWRhdGEvUmVzLmZhY3RvcnkuanMiLCJjb3JlL3VpL01RLmNvbnN0YW50LmpzIiwiY29yZS91aS9sb2FkaW5nLmRpci5qcyIsImNvcmUvdWkvdHJ1c3RBc0hUTUwuZmlsdGVyLmpzIiwibW9kdWxlcy9oZWFkZXIvSGVhZGVyLmN0cmwuanMiLCJtb2R1bGVzL2hlYWRlci9uYXZDb250cm9sLmRpci5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuanMiLCJwYWdlcy9ob21lL0hvbWUuY3RybC5qcyIsInBhZ2VzL3N1Yi9TdWIuY3RybC5qcyIsInBhZ2VzL3N1Yi9zYW1wbGUuZGlyLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDTkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDckNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzFCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2hEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzNCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDeENBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDWkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMvSkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDZEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUN4RUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUN2SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM5QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM5QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJyZVN0YXJ0LWFwcC5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8vIGFwcGxpY2F0aW9uIG1vZHVsZSBzZXR0ZXJcbihmdW5jdGlvbigpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG5cdGFuZ3VsYXJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JywgWyduZ1JvdXRlJywgJ25nUmVzb3VyY2UnLCAnbmdTYW5pdGl6ZScsICdtZWRpYUNoZWNrJywgJ3Jlc2l6ZSddKTtcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5jb250cm9sbGVyKCdQYWdlQ3RybCcsIFBhZ2VDdHJsKTtcblxuXHRQYWdlQ3RybC4kaW5qZWN0ID0gWydQYWdlJywgJyRzY29wZScsICdNUScsICdtZWRpYUNoZWNrJywgJyRsb2cnXTtcblxuXHRmdW5jdGlvbiBQYWdlQ3RybChQYWdlLCAkc2NvcGUsIE1RLCBtZWRpYUNoZWNrLCAkbG9nKSB7XG5cdFx0dmFyIHBhZ2UgPSB0aGlzO1xuXG5cdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcblx0XHR2YXIgX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvciA9IGZhbHNlO1xuXHRcdC8vIFNldCB1cCBmdW5jdGlvbmFsaXR5IHRvIHJ1biBvbiBlbnRlci9leGl0IG9mIG1lZGlhIHF1ZXJ5XG5cdFx0dmFyIF9tYyA9IG1lZGlhQ2hlY2suaW5pdCh7XG5cdFx0XHRzY29wZTogJHNjb3BlLFxuXHRcdFx0bWVkaWE6IHtcblx0XHRcdFx0bXE6IE1RLlNNQUxMLFxuXHRcdFx0XHRlbnRlcjogX2VudGVyTW9iaWxlLFxuXHRcdFx0XHRleGl0OiBfZXhpdE1vYmlsZVxuXHRcdFx0fSxcblx0XHRcdGRlYm91bmNlOiAyMDBcblx0XHR9KTtcblxuXHRcdF9pbml0KCk7XG5cblx0XHQvKipcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcblx0XHRcdC8vIGFzc29jaWF0ZSBwYWdlIDx0aXRsZT5cblx0XHRcdHBhZ2UucGFnZVRpdGxlID0gUGFnZTtcblxuXHRcdFx0JHNjb3BlLiRvbignJHJvdXRlQ2hhbmdlU3RhcnQnLCBfcm91dGVDaGFuZ2VTdGFydCk7XG5cdFx0XHQkc2NvcGUuJG9uKCckcm91dGVDaGFuZ2VTdWNjZXNzJywgX3JvdXRlQ2hhbmdlU3VjY2Vzcyk7XG5cdFx0XHQkc2NvcGUuJG9uKCckcm91dGVDaGFuZ2VFcnJvcicsIF9yb3V0ZUNoYW5nZUVycm9yKTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBFbnRlciBtb2JpbGUgbWVkaWEgcXVlcnlcblx0XHQgKiAkYnJvYWRjYXN0ICdlbnRlci1tb2JpbGUnIGV2ZW50XG5cdFx0ICpcblx0XHQgKiBAcHJpdmF0ZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIF9lbnRlck1vYmlsZSgpIHtcblx0XHRcdCRzY29wZS4kYnJvYWRjYXN0KCdlbnRlci1tb2JpbGUnKTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBFeGl0IG1vYmlsZSBtZWRpYSBxdWVyeVxuXHRcdCAqICRicm9hZGNhc3QgJ2V4aXQtbW9iaWxlJyBldmVudFxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZSgpIHtcblx0XHRcdCRzY29wZS4kYnJvYWRjYXN0KCdleGl0LW1vYmlsZScpO1xuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIFR1cm4gb24gbG9hZGluZyBzdGF0ZVxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfbG9hZGluZ09uKCkge1xuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2xvYWRpbmctb24nKTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBUdXJuIG9mZiBsb2FkaW5nIHN0YXRlXG5cdFx0ICpcblx0XHQgKiBAcHJpdmF0ZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIF9sb2FkaW5nT2ZmKCkge1xuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2xvYWRpbmctb2ZmJyk7XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogUm91dGUgY2hhbmdlIHN0YXJ0IGhhbmRsZXJcblx0XHQgKiBJZiBuZXh0IHJvdXRlIGhhcyByZXNvbHZlLCB0dXJuIG9uIGxvYWRpbmdcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cblx0XHQgKiBAcGFyYW0gbmV4dCB7b2JqZWN0fVxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfcm91dGVDaGFuZ2VTdGFydCgkZXZlbnQsIG5leHQsIGN1cnJlbnQpIHtcblx0XHRcdGlmIChuZXh0LiQkcm91dGUgJiYgbmV4dC4kJHJvdXRlLnJlc29sdmUpIHsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBhbmd1bGFyL25vLXByaXZhdGUtY2FsbFxuXHRcdFx0XHRfbG9hZGluZ09uKCk7XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogUm91dGUgY2hhbmdlIHN1Y2Nlc3MgaGFuZGxlclxuXHRcdCAqIE1hdGNoIGN1cnJlbnQgbWVkaWEgcXVlcnkgYW5kIHJ1biBhcHByb3ByaWF0ZSBmdW5jdGlvblxuXHRcdCAqIElmIGN1cnJlbnQgcm91dGUgaGFzIGJlZW4gcmVzb2x2ZWQsIHR1cm4gb2ZmIGxvYWRpbmdcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cblx0XHQgKiBAcGFyYW0gY3VycmVudCB7b2JqZWN0fVxuXHRcdCAqIEBwYXJhbSBwcmV2aW91cyB7b2JqZWN0fVxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX3JvdXRlQ2hhbmdlU3VjY2VzcygkZXZlbnQsIGN1cnJlbnQsIHByZXZpb3VzKSB7XG5cdFx0XHRfbWMubWF0Y2hDdXJyZW50KE1RLlNNQUxMKTtcblxuXHRcdFx0aWYgKGN1cnJlbnQuJCRyb3V0ZSAmJiBjdXJyZW50LiQkcm91dGUucmVzb2x2ZSkgeyAgIC8vIGVzbGludC1kaXNhYmxlLWxpbmUgYW5ndWxhci9uby1wcml2YXRlLWNhbGxcblx0XHRcdFx0X2xvYWRpbmdPZmYoKTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBSb3V0ZSBjaGFuZ2UgZXJyb3IgaGFuZGxlclxuXHRcdCAqIEhhbmRsZSByb3V0ZSByZXNvbHZlIGZhaWx1cmVzXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gJGV2ZW50IHtvYmplY3R9XG5cdFx0ICogQHBhcmFtIGN1cnJlbnQge29iamVjdH1cblx0XHQgKiBAcGFyYW0gcHJldmlvdXMge29iamVjdH1cblx0XHQgKiBAcGFyYW0gcmVqZWN0aW9uIHtvYmplY3R9XG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfcm91dGVDaGFuZ2VFcnJvcigkZXZlbnQsIGN1cnJlbnQsIHByZXZpb3VzLCByZWplY3Rpb24pIHtcblx0XHRcdHZhciBkZXN0aW5hdGlvbiA9IChjdXJyZW50ICYmIChjdXJyZW50LnRpdGxlIHx8IGN1cnJlbnQubmFtZSB8fCBjdXJyZW50LmxvYWRlZFRlbXBsYXRlVXJsKSkgfHwgJ3Vua25vd24gdGFyZ2V0Jztcblx0XHRcdHZhciBtc2cgPSAnRXJyb3Igcm91dGluZyB0byAnICsgZGVzdGluYXRpb24gKyAnLiAnICsgKHJlamVjdGlvbi5tc2cgfHwgJycpO1xuXG5cdFx0XHRpZiAoX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvcikge1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cblx0XHRcdF9oYW5kbGluZ1JvdXRlQ2hhbmdlRXJyb3IgPSB0cnVlO1xuXHRcdFx0X2xvYWRpbmdPZmYoKTtcblxuXHRcdFx0JGxvZy5lcnJvcihtc2cpO1xuXHRcdH1cblx0fVxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XG5cdCd1c2Ugc3RyaWN0JztcblxuXHRhbmd1bGFyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXG5cdFx0LmZhY3RvcnkoJ1BhZ2UnLCBQYWdlKTtcblxuXHRmdW5jdGlvbiBQYWdlKCkge1xuXHRcdC8vIHByaXZhdGUgdmFyc1xuXHRcdHZhciBzaXRlVGl0bGUgPSAncmVTdGFydCBBbmd1bGFyJztcblx0XHR2YXIgcGFnZVRpdGxlID0gJ0hvbWUnO1xuXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRnZXRUaXRsZTogZ2V0VGl0bGUsXG5cdFx0XHRzZXRUaXRsZTogc2V0VGl0bGVcblx0XHR9O1xuXG5cdFx0LyoqXG5cdFx0ICogVGl0bGUgZnVuY3Rpb25cblx0XHQgKiBTZXRzIHNpdGUgdGl0bGUgYW5kIHBhZ2UgdGl0bGVcblx0XHQgKlxuXHRcdCAqIEByZXR1cm5zIHtzdHJpbmd9IHNpdGUgdGl0bGUgKyBwYWdlIHRpdGxlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gZ2V0VGl0bGUoKSB7XG5cdFx0XHRyZXR1cm4gc2l0ZVRpdGxlICsgJyB8ICcgKyBwYWdlVGl0bGU7XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogU2V0IHBhZ2UgdGl0bGVcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSBuZXdUaXRsZSB7c3RyaW5nfVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIHNldFRpdGxlKG5ld1RpdGxlKSB7XG5cdFx0XHRwYWdlVGl0bGUgPSBuZXdUaXRsZTtcblx0XHR9XG5cdH1cbn0oKSk7IiwiLy8gXCJnbG9iYWxcIiBvYmplY3QgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5mYWN0b3J5KCdVdGlscycsIFV0aWxzKTtcblxuXHRmdW5jdGlvbiBVdGlscygpIHtcblx0XHR2YXIgZ3JlZXRpbmcgPSAnSGVsbG8nO1xuXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRncmVldGluZzogZ3JlZXRpbmcsXG5cdFx0XHRhbGVydEdyZWV0aW5nOiBhbGVydEdyZWV0aW5nXG5cdFx0fTtcblxuXHRcdC8qKlxuXHRcdCAqIEFsZXJ0IGdyZWV0aW5nXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gbmFtZSB7c3RyaW5nfVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIGFsZXJ0R3JlZXRpbmcobmFtZSkge1xuXHRcdFx0YWxlcnQoZ3JlZXRpbmcgKyAnLCAnICsgbmFtZSArICchJyk7XG5cdFx0fVxuXHR9XG59KCkpOyIsIi8vIGFwcGxpY2F0aW9uIGNvbmZpZ1xuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5jb25maWcoYXBwQ29uZmlnKTtcblxuXHRhcHBDb25maWcuJGluamVjdCA9IFsnJHJvdXRlUHJvdmlkZXInLCAnJGxvY2F0aW9uUHJvdmlkZXInXTtcblxuXHRmdW5jdGlvbiBhcHBDb25maWcoJHJvdXRlUHJvdmlkZXIsICRsb2NhdGlvblByb3ZpZGVyKSB7XG5cdFx0JHJvdXRlUHJvdmlkZXJcblx0XHRcdC53aGVuKCcvJywge1xuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL2hvbWUvSG9tZS52aWV3Lmh0bWwnLFxuXHRcdFx0XHRjb250cm9sbGVyOiAnSG9tZUN0cmwnLFxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdob21lJ1xuXHRcdFx0fSlcblx0XHRcdC53aGVuKCcvc3VicGFnZScsIHtcblx0XHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvU3ViLnZpZXcuaHRtbCcsXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdTdWJDdHJsJyxcblx0XHRcdFx0Y29udHJvbGxlckFzOiAnc3ViJyxcblx0XHRcdFx0cmVzb2x2ZToge1xuXHRcdFx0XHRcdHJlc29sdmVMb2NhbERhdGE6IHJlc29sdmVMb2NhbERhdGFcblx0XHRcdFx0fVxuXHRcdFx0fSlcblx0XHRcdC5vdGhlcndpc2Uoe1xuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LnZpZXcuaHRtbCcsXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdFcnJvcjQwNEN0cmwnLFxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdlNDA0J1xuXHRcdFx0fSk7XG5cblx0XHQkbG9jYXRpb25Qcm92aWRlclxuXHRcdFx0Lmh0bWw1TW9kZSh7XG5cdFx0XHRcdGVuYWJsZWQ6IHRydWVcblx0XHRcdH0pXG5cdFx0XHQuaGFzaFByZWZpeCgnIScpO1xuXHR9XG5cblx0cmVzb2x2ZUxvY2FsRGF0YS4kaW5qZWN0ID0gWydKU09ORGF0YSddO1xuXHQvKipcblx0ICogR2V0IGxvY2FsIGRhdGEgZm9yIHJvdXRlIHJlc29sdmVcblx0ICpcblx0ICogQHBhcmFtIEpTT05EYXRhIHtmYWN0b3J5fVxuXHQgKiBAcmV0dXJucyB7cHJvbWlzZX0gZGF0YVxuXHQgKi9cblx0ZnVuY3Rpb24gcmVzb2x2ZUxvY2FsRGF0YShKU09ORGF0YSkge1xuXHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKTtcblx0fVxufSgpKTsiLCIvLyBmZXRjaCBKU09OIGRhdGEgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5mYWN0b3J5KCdKU09ORGF0YScsIEpTT05EYXRhKTtcblxuXHRKU09ORGF0YS4kaW5qZWN0ID0gWyckaHR0cCcsICdSZXMnXTtcblxuXHRmdW5jdGlvbiBKU09ORGF0YSgkaHR0cCwgUmVzKSB7XG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRnZXRMb2NhbERhdGE6IGdldExvY2FsRGF0YVxuXHRcdH07XG5cblx0XHQvKipcblx0XHQgKiBHRVQgbG9jYWwgSlNPTiBkYXRhIGZpbGUgYW5kIHJldHVybiByZXN1bHRzXG5cdFx0ICpcblx0XHQgKiBAcmV0dXJucyB7cHJvbWlzZX1cblx0XHQgKi9cblx0XHRmdW5jdGlvbiBnZXRMb2NhbERhdGEoKSB7XG5cdFx0XHRyZXR1cm4gJGh0dHBcblx0XHRcdFx0LmdldCgnL2RhdGEvZGF0YS5qc29uJylcblx0XHRcdFx0LnRoZW4oUmVzLnN1Y2Nlc3MsIFJlcy5lcnJvcik7XG5cdFx0fVxuXHR9XG59KCkpOyIsIihmdW5jdGlvbigpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG5cdGFuZ3VsYXJcblx0XHQubW9kdWxlKCdyZVN0YXJ0Jylcblx0XHQuZmFjdG9yeSgnUmVzJywgUmVzKTtcblxuXHRmdW5jdGlvbiBSZXMoKSB7XG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRzdWNjZXNzOiBzdWNjZXNzLFxuXHRcdFx0ZXJyb3I6IGVycm9yXG5cdFx0fTtcblxuXHRcdC8qKlxuXHRcdCAqIFByb21pc2UgcmVzcG9uc2UgZnVuY3Rpb25cblx0XHQgKiBDaGVja3MgdHlwZW9mIGRhdGEgcmV0dXJuZWQgYW5kIHN1Y2NlZWRzIGlmIEpTIG9iamVjdCwgdGhyb3dzIGVycm9yIGlmIG5vdFxuXHRcdCAqIFVzZWZ1bCBmb3IgQVBJcyAoaWUsIHdpdGggbmdpbngpIHdoZXJlIHNlcnZlciBlcnJvciBIVE1MIHBhZ2UgbWF5IGJlIHJldHVybmVkIGluIGVycm9yXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gcmVzcG9uc2Ugeyp9IGRhdGEgZnJvbSAkaHR0cFxuXHRcdCAqIEByZXR1cm5zIHsqfSBvYmplY3QsIGFycmF5XG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gc3VjY2VzcyhyZXNwb25zZSkge1xuXHRcdFx0aWYgKGFuZ3VsYXIuaXNPYmplY3QocmVzcG9uc2UuZGF0YSkpIHtcblx0XHRcdFx0cmV0dXJuIHJlc3BvbnNlLmRhdGE7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHR0aHJvdyBuZXcgRXJyb3IoJ3JldHJpZXZlZCBkYXRhIGlzIG5vdCB0eXBlb2Ygb2JqZWN0LicpO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIFByb21pc2UgcmVzcG9uc2UgZnVuY3Rpb24gLSBlcnJvclxuXHRcdCAqIFRocm93cyBhbiBlcnJvciB3aXRoIGVycm9yIGRhdGFcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSBlcnJvciB7b2JqZWN0fVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIGVycm9yKGVycm9yKSB7XG5cdFx0XHR0aHJvdyBuZXcgRXJyb3IoJ0Vycm9yIHJldHJpZXZpbmcgZGF0YScsIGVycm9yKTtcblx0XHR9XG5cdH1cbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0Ly8gbWVkaWEgcXVlcnkgY29uc3RhbnRzXG5cdHZhciBNUSA9IHtcblx0XHRTTUFMTDogJyhtYXgtd2lkdGg6IDc2N3B4KScsXG5cdFx0TEFSR0U6ICcobWluLXdpZHRoOiA3NjhweCknXG5cdH07XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5jb25zdGFudCgnTVEnLCBNUSk7XG59KCkpOyIsIihmdW5jdGlvbigpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG5cdGFuZ3VsYXJcblx0XHQubW9kdWxlKCdyZVN0YXJ0Jylcblx0XHQuZGlyZWN0aXZlKCdsb2FkaW5nJywgbG9hZGluZyk7XG5cblx0bG9hZGluZy4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xuXG5cdGZ1bmN0aW9uIGxvYWRpbmcoJHdpbmRvdywgcmVzaXplKSB7XG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxuXHRcdHJldHVybiB7XG5cdFx0XHRyZXN0cmljdDogJ0VBJyxcblx0XHRcdHJlcGxhY2U6IHRydWUsXG5cdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL2NvcmUvdWkvbG9hZGluZy50cGwuaHRtbCcsXG5cdFx0XHR0cmFuc2NsdWRlOiB0cnVlLFxuXHRcdFx0Y29udHJvbGxlcjogbG9hZGluZ0N0cmwsXG5cdFx0XHRjb250cm9sbGVyQXM6ICdsb2FkaW5nJyxcblx0XHRcdGJpbmRUb0NvbnRyb2xsZXI6IHRydWUsXG5cdFx0XHRsaW5rOiBsb2FkaW5nTGlua1xuXHRcdH07XG5cblx0XHQvKipcblx0XHQgKiBsb2FkaW5nIExJTktcblx0XHQgKiBEaXNhYmxlcyBwYWdlIHNjcm9sbGluZyB3aGVuIGxvYWRpbmcgb3ZlcmxheSBpcyBvcGVuXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gJHNjb3BlXG5cdFx0ICogQHBhcmFtICRlbGVtZW50XG5cdFx0ICogQHBhcmFtICRhdHRyc1xuXHRcdCAqIEBwYXJhbSBsb2FkaW5nIHtjb250cm9sbGVyfVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIGxvYWRpbmdMaW5rKCRzY29wZSwgJGVsZW1lbnQsICRhdHRycywgbG9hZGluZykge1xuXHRcdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcblx0XHRcdHZhciBfJGJvZHkgPSBhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKTtcblx0XHRcdHZhciBfd2luSGVpZ2h0ID0gJHdpbmRvdy5pbm5lckhlaWdodCArICdweCc7XG5cblx0XHRcdF9pbml0KCk7XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcblx0XHRcdFx0Ly8gaW5pdGlhbGl6ZSBkZWJvdW5jZWQgcmVzaXplXG5cdFx0XHRcdHZhciBfcnMgPSByZXNpemUuaW5pdCh7XG5cdFx0XHRcdFx0c2NvcGU6ICRzY29wZSxcblx0XHRcdFx0XHRyZXNpemVkRm46IF9yZXNpemVkLFxuXHRcdFx0XHRcdGRlYm91bmNlOiAyMDBcblx0XHRcdFx0fSk7XG5cblx0XHRcdFx0Ly8gJHdhdGNoIGFjdGl2ZSBzdGF0ZVxuXHRcdFx0XHQkc2NvcGUuJHdhdGNoKCdsb2FkaW5nLmFjdGl2ZScsIF8kd2F0Y2hBY3RpdmUpO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIFdpbmRvdyByZXNpemVkXG5cdFx0XHQgKiBJZiBsb2FkaW5nLCByZWFwcGx5IGJvZHkgaGVpZ2h0XG5cdFx0XHQgKiB0byBwcmV2ZW50IHNjcm9sbGJhclxuXHRcdFx0ICpcblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF9yZXNpemVkKCkge1xuXHRcdFx0XHRfd2luSGVpZ2h0ID0gJHdpbmRvdy5pbm5lckhlaWdodCArICdweCc7XG5cblx0XHRcdFx0aWYgKGxvYWRpbmcuYWN0aXZlKSB7XG5cdFx0XHRcdFx0XyRib2R5LmNzcyh7XG5cdFx0XHRcdFx0XHRoZWlnaHQ6IF93aW5IZWlnaHQsXG5cdFx0XHRcdFx0XHRvdmVyZmxvd1k6ICdoaWRkZW4nXG5cdFx0XHRcdFx0fSk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiAkd2F0Y2ggbG9hZGluZy5hY3RpdmVcblx0XHRcdCAqXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHtib29sZWFufVxuXHRcdFx0ICogQHBhcmFtIG9sZFZhbCB7dW5kZWZpbmVkfGJvb2xlYW59XG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfJHdhdGNoQWN0aXZlKG5ld1ZhbCwgb2xkVmFsKSB7XG5cdFx0XHRcdGlmIChuZXdWYWwpIHtcblx0XHRcdFx0XHRfb3BlbigpO1xuXHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdF9jbG9zZSgpO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogT3BlbiBsb2FkaW5nXG5cdFx0XHQgKiBEaXNhYmxlIHNjcm9sbFxuXHRcdFx0ICpcblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF9vcGVuKCkge1xuXHRcdFx0XHRfJGJvZHkuY3NzKHtcblx0XHRcdFx0XHRoZWlnaHQ6IF93aW5IZWlnaHQsXG5cdFx0XHRcdFx0b3ZlcmZsb3dZOiAnaGlkZGVuJ1xuXHRcdFx0XHR9KTtcblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBDbG9zZSBsb2FkaW5nXG5cdFx0XHQgKiBFbmFibGUgc2Nyb2xsXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX2Nsb3NlKCkge1xuXHRcdFx0XHRfJGJvZHkuY3NzKHtcblx0XHRcdFx0XHRoZWlnaHQ6ICdhdXRvJyxcblx0XHRcdFx0XHRvdmVyZmxvd1k6ICdhdXRvJ1xuXHRcdFx0XHR9KTtcblx0XHRcdH1cblx0XHR9XG5cdH1cblxuXHRsb2FkaW5nQ3RybC4kaW5qZWN0ID0gWyckc2NvcGUnXTtcblx0LyoqXG5cdCAqIGxvYWRpbmcgQ09OVFJPTExFUlxuXHQgKiBVcGRhdGUgdGhlIGxvYWRpbmcgc3RhdHVzIGJhc2VkXG5cdCAqIG9uIHJvdXRlQ2hhbmdlIHN0YXRlXG5cdCAqL1xuXHRmdW5jdGlvbiBsb2FkaW5nQ3RybCgkc2NvcGUpIHtcblx0XHR2YXIgbG9hZGluZyA9IHRoaXM7XG5cblx0XHRfaW5pdCgpO1xuXG5cdFx0LyoqXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XG5cdFx0XHQvLyB0dXJuIG9uIGxvYWRpbmcgZm9yIGluaXRpYWwgcGFnZSBsb2FkXG5cdFx0XHRfbG9hZGluZ0FjdGl2ZSgpO1xuXG5cdFx0XHQkc2NvcGUuJG9uKCdsb2FkaW5nLW9uJywgX2xvYWRpbmdBY3RpdmUpO1xuXHRcdFx0JHNjb3BlLiRvbignbG9hZGluZy1vZmYnLCBfbG9hZGluZ0luYWN0aXZlKTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBTZXQgbG9hZGluZyB0byBhY3RpdmVcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdBY3RpdmUoKSB7XG5cdFx0XHRsb2FkaW5nLmFjdGl2ZSA9IHRydWU7XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogU2V0IGxvYWRpbmcgdG8gaW5hY3RpdmVcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdJbmFjdGl2ZSgpIHtcblx0XHRcdGxvYWRpbmcuYWN0aXZlID0gZmFsc2U7XG5cdFx0fVxuXHR9XG5cbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5maWx0ZXIoJ3RydXN0QXNIVE1MJywgdHJ1c3RBc0hUTUwpO1xuXG5cdHRydXN0QXNIVE1MLiRpbmplY3QgPSBbJyRzY2UnXTtcblxuXHRmdW5jdGlvbiB0cnVzdEFzSFRNTCgkc2NlKSB7XG5cdFx0cmV0dXJuIGZ1bmN0aW9uKHRleHQpIHtcblx0XHRcdHJldHVybiAkc2NlLnRydXN0QXNIdG1sKHRleHQpO1xuXHRcdH07XG5cdH1cbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignSGVhZGVyQ3RybCcsIEhlYWRlckN0cmwpO1xyXG5cclxuXHRIZWFkZXJDdHJsLiRpbmplY3QgPSBbJyRsb2NhdGlvbicsICdKU09ORGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBIZWFkZXJDdHJsKCRsb2NhdGlvbiwgSlNPTkRhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBoZWFkZXIgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGhlYWRlci5pbmRleElzQWN0aXZlID0gaW5kZXhJc0FjdGl2ZTtcclxuXHRcdGhlYWRlci5uYXZJc0FjdGl2ZSA9IG5hdklzQWN0aXZlO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBhY3RpdmF0ZSBjb250cm9sbGVyXHJcblx0XHRcdF9hY3RpdmF0ZSgpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQ29udHJvbGxlciBhY3RpdmF0ZVxyXG5cdFx0ICogR2V0IEpTT04gZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHsqfVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2FjdGl2YXRlKCkge1xyXG5cdFx0XHQvLyBnZXQgdGhlIGRhdGEgZnJvbSBKU09OXHJcblx0XHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKS50aGVuKF9nZXRKc29uU3VjY2Vzcyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTdWNjZXNzZnVsIHByb21pc2UgZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSBkYXRhIHtqc29ufVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2dldEpzb25TdWNjZXNzKGRhdGEpIHtcclxuXHRcdFx0aGVhZGVyLmpzb24gPSBkYXRhO1xyXG5cdFx0XHRyZXR1cm4gaGVhZGVyLmpzb247XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBBcHBseSBjbGFzcyB0byBpbmRleCBuYXYgaWYgYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIHtzdHJpbmd9IHBhdGhcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gaW5kZXhJc0FjdGl2ZShwYXRoKSB7XHJcblx0XHRcdC8vIHBhdGggc2hvdWxkIGJlICcvJ1xyXG5cdFx0XHRyZXR1cm4gJGxvY2F0aW9uLnBhdGgoKSA9PT0gcGF0aDtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFwcGx5IGNsYXNzIHRvIGN1cnJlbnRseSBhY3RpdmUgbmF2IGl0ZW1cclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0ge3N0cmluZ30gcGF0aFxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBuYXZJc0FjdGl2ZShwYXRoKSB7XHJcblx0XHRcdHJldHVybiAkbG9jYXRpb24ucGF0aCgpLnN1YnN0cigwLCBwYXRoLmxlbmd0aCkgPT09IHBhdGg7XHJcblx0XHR9XHJcblx0fVxyXG5cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XG5cdCd1c2Ugc3RyaWN0JztcblxuXHRhbmd1bGFyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXG5cdFx0LmRpcmVjdGl2ZSgnbmF2Q29udHJvbCcsIG5hdkNvbnRyb2wpO1xuXG5cdG5hdkNvbnRyb2wuJGluamVjdCA9IFsnJHdpbmRvdycsICdyZXNpemUnXTtcblxuXHRmdW5jdGlvbiBuYXZDb250cm9sKCR3aW5kb3csIHJlc2l6ZSkge1xuXHRcdC8vIHJldHVybiBkaXJlY3RpdmVcblx0XHRyZXR1cm4ge1xuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXG5cdFx0XHRsaW5rOiBuYXZDb250cm9sTGlua1xuXHRcdH07XG5cblx0XHQvKipcblx0XHQgKiBuYXZDb250cm9sIExJTksgZnVuY3Rpb25cblx0XHQgKlxuXHRcdCAqIEBwYXJhbSAkc2NvcGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBuYXZDb250cm9sTGluaygkc2NvcGUpIHtcblx0XHRcdC8vIHByaXZhdGUgdmFyaWFibGVzXG5cdFx0XHR2YXIgXyRib2R5ID0gYW5ndWxhci5lbGVtZW50KCdib2R5Jyk7XG5cdFx0XHR2YXIgX2xheW91dENhbnZhcyA9IF8kYm9keS5maW5kKCcubGF5b3V0LWNhbnZhcycpO1xuXHRcdFx0dmFyIF9uYXZPcGVuO1xuXG5cdFx0XHQvLyBkYXRhIG1vZGVsXG5cdFx0XHQkc2NvcGUubmF2ID0ge307XG5cblx0XHRcdF9pbml0KCk7XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcblx0XHRcdFx0Ly8gaW5pdGlhbGl6ZSBkZWJvdW5jZWQgcmVzaXplXG5cdFx0XHRcdHZhciBfcnMgPSByZXNpemUuaW5pdCh7XG5cdFx0XHRcdFx0c2NvcGU6ICRzY29wZSxcblx0XHRcdFx0XHRyZXNpemVkRm46IF9yZXNpemVkLFxuXHRcdFx0XHRcdGRlYm91bmNlOiAxMDBcblx0XHRcdFx0fSk7XG5cblx0XHRcdFx0JHNjb3BlLiRvbignJGxvY2F0aW9uQ2hhbmdlU3RhcnQnLCBfJGxvY2F0aW9uQ2hhbmdlU3RhcnQpO1xuXHRcdFx0XHQkc2NvcGUuJG9uKCdlbnRlci1tb2JpbGUnLCBfZW50ZXJNb2JpbGUpO1xuXHRcdFx0XHQkc2NvcGUuJG9uKCdleGl0LW1vYmlsZScsIF9leGl0TW9iaWxlKTtcblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBSZXNpemVkIHdpbmRvdyAoZGVib3VuY2VkKVxuXHRcdFx0ICpcblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF9yZXNpemVkKCkge1xuXHRcdFx0XHRfbGF5b3V0Q2FudmFzLmNzcyh7XG5cdFx0XHRcdFx0bWluSGVpZ2h0OiAkd2luZG93LmlubmVySGVpZ2h0ICsgJ3B4J1xuXHRcdFx0XHR9KTtcblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBPcGVuIG1vYmlsZSBuYXZpZ2F0aW9uXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX29wZW5OYXYoKSB7XG5cdFx0XHRcdF8kYm9keVxuXHRcdFx0XHRcdC5yZW1vdmVDbGFzcygnbmF2LWNsb3NlZCcpXG5cdFx0XHRcdFx0LmFkZENsYXNzKCduYXYtb3BlbicpO1xuXG5cdFx0XHRcdF9uYXZPcGVuID0gdHJ1ZTtcblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBDbG9zZSBtb2JpbGUgbmF2aWdhdGlvblxuXHRcdFx0ICpcblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF9jbG9zZU5hdigpIHtcblx0XHRcdFx0XyRib2R5XG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduYXYtb3BlbicpXG5cdFx0XHRcdFx0LmFkZENsYXNzKCduYXYtY2xvc2VkJyk7XG5cblx0XHRcdFx0X25hdk9wZW4gPSBmYWxzZTtcblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBUb2dnbGUgbmF2IG9wZW4vY2xvc2VkXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIHRvZ2dsZU5hdigpIHtcblx0XHRcdFx0aWYgKCFfbmF2T3Blbikge1xuXHRcdFx0XHRcdF9vcGVuTmF2KCk7XG5cdFx0XHRcdH0gZWxzZSB7XG5cdFx0XHRcdFx0X2Nsb3NlTmF2KCk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBXaGVuIGNoYW5naW5nIGxvY2F0aW9uLCBjbG9zZSB0aGUgbmF2IGlmIGl0J3Mgb3BlblxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfJGxvY2F0aW9uQ2hhbmdlU3RhcnQoKSB7XG5cdFx0XHRcdGlmIChfbmF2T3Blbikge1xuXHRcdFx0XHRcdF9jbG9zZU5hdigpO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogRnVuY3Rpb24gdG8gZXhlY3V0ZSB3aGVuIGVudGVyaW5nIG1vYmlsZSBtZWRpYSBxdWVyeVxuXHRcdFx0ICogQ2xvc2UgbmF2IGFuZCBzZXQgdXAgbWVudSB0b2dnbGluZyBmdW5jdGlvbmFsaXR5XG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX2VudGVyTW9iaWxlKG1xKSB7XG5cdFx0XHRcdF9jbG9zZU5hdigpO1xuXG5cdFx0XHRcdC8vIGJpbmQgZnVuY3Rpb24gdG8gdG9nZ2xlIG1vYmlsZSBuYXZpZ2F0aW9uIG9wZW4vY2xvc2VkXG5cdFx0XHRcdCRzY29wZS5uYXYudG9nZ2xlTmF2ID0gdG9nZ2xlTmF2O1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIEZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2hlbiBleGl0aW5nIG1vYmlsZSBtZWRpYSBxdWVyeVxuXHRcdFx0ICogRGlzYWJsZSBtZW51IHRvZ2dsaW5nIGFuZCByZW1vdmUgYm9keSBjbGFzc2VzXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX2V4aXRNb2JpbGUobXEpIHtcblx0XHRcdFx0Ly8gdW5iaW5kIGZ1bmN0aW9uIHRvIHRvZ2dsZSBtb2JpbGUgbmF2aWdhdGlvbiBvcGVuL2Nsb3NlZFxuXHRcdFx0XHQkc2NvcGUubmF2LnRvZ2dsZU5hdiA9IG51bGw7XG5cblx0XHRcdFx0XyRib2R5LnJlbW92ZUNsYXNzKCduYXYtY2xvc2VkIG5hdi1vcGVuJyk7XG5cdFx0XHR9XG5cdFx0fVxuXHR9XG5cbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5jb250cm9sbGVyKCdFcnJvcjQwNEN0cmwnLCBFcnJvcjQwNEN0cmwpO1xuXG5cdEVycm9yNDA0Q3RybC4kaW5qZWN0ID0gWyckc2NvcGUnLCAnUGFnZSddO1xuXG5cdGZ1bmN0aW9uIEVycm9yNDA0Q3RybCgkc2NvcGUsIFBhZ2UpIHtcblx0XHR2YXIgZTQwNCA9IHRoaXM7XG5cblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXG5cdFx0ZTQwNC50aXRsZSA9ICc0MDQgLSBQYWdlIE5vdCBGb3VuZCc7XG5cblx0XHRfaW5pdCgpO1xuXG5cdFx0LyoqXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XG5cdFx0XHQvLyBzZXQgcGFnZSA8dGl0bGU+XG5cdFx0XHRQYWdlLnNldFRpdGxlKGU0MDQudGl0bGUpO1xuXG5cdFx0XHQvLyBubyBkYXRhIHRvIGxvYWQsIGJ1dCBsb2FkaW5nIHN0YXRlIG1pZ2h0IGJlIG9uXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb2ZmJyk7XG5cdFx0fVxuXHR9XG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnRyb2xsZXIoJ0hvbWVDdHJsJywgSG9tZUN0cmwpO1xyXG5cclxuXHRIb21lQ3RybC4kaW5qZWN0ID0gWyckc2NvcGUnLCAnVXRpbHMnLCAnUGFnZScsICdKU09ORGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBIb21lQ3RybCgkc2NvcGUsIFV0aWxzLCBQYWdlLCBKU09ORGF0YSkge1xyXG5cdFx0Ly8gY29udHJvbGxlckFzIFZpZXdNb2RlbFxyXG5cdFx0dmFyIGhvbWUgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGhvbWUudGl0bGUgPSAnSG9tZSc7XHJcblx0XHRob21lLmdsb2JhbCA9IFV0aWxzO1xyXG5cdFx0aG9tZS5uYW1lID0gJ1Zpc2l0b3InO1xyXG5cdFx0aG9tZS5hbGVydEdyZWV0aW5nID0gVXRpbHMuYWxlcnRHcmVldGluZztcclxuXHRcdGhvbWUuc3RyaW5nT2ZIVE1MID0gJzxzdHJvbmcgc3R5bGU9XCJjb2xvcjogZ3JlZW47XCI+U29tZSBncmVlbiB0ZXh0PC9zdHJvbmc+IGJvdW5kIGFzIEhUTUwgd2l0aCBhIDxhIGhyZWY9XCIjXCI+bGluazwvYT4sIHRydXN0ZWQgd2l0aCBTQ0UhJztcclxuXHRcdGhvbWUudmlld2Zvcm1hdCA9IG51bGw7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cclxuXHRcdFx0UGFnZS5zZXRUaXRsZShob21lLnRpdGxlKTtcclxuXHJcblx0XHRcdC8vIGFjdGl2YXRlIGNvbnRyb2xsZXJcclxuXHRcdFx0X2FjdGl2YXRlKCk7XHJcblxyXG5cdFx0XHQvLyBtZWRpYXF1ZXJ5IGV2ZW50c1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdlbnRlci1tb2JpbGUnLCBfZW50ZXJNb2JpbGUpO1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdleGl0LW1vYmlsZScsIF9leGl0TW9iaWxlKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIENvbnRyb2xsZXIgYWN0aXZhdGVcclxuXHRcdCAqIEdldCBKU09OIGRhdGFcclxuXHRcdCAqXHJcblx0XHQgKiBAcmV0dXJucyB7Kn1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9hY3RpdmF0ZSgpIHtcclxuXHRcdFx0Ly8gc3RhcnQgbG9hZGluZ1xyXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb24nKTtcclxuXHJcblx0XHRcdC8vIGdldCB0aGUgZGF0YSBmcm9tIEpTT05cclxuXHRcdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpLnRoZW4oX2dldEpzb25TdWNjZXNzKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFN1Y2Nlc3NmdWwgcHJvbWlzZSBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGRhdGEge2pzb259XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZ2V0SnNvblN1Y2Nlc3MoZGF0YSkge1xyXG5cdFx0XHRob21lLmpzb24gPSBkYXRhO1xyXG5cclxuXHRcdFx0Ly8gc3RvcCBsb2FkaW5nXHJcblx0XHRcdCRzY29wZS4kZW1pdCgnbG9hZGluZy1vZmYnKTtcclxuXHJcblx0XHRcdHJldHVybiBob21lLmpzb247XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFbnRlciBzbWFsbCBtcVxyXG5cdFx0ICogU2V0IGhvbWUudmlld2Zvcm1hdFxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9lbnRlck1vYmlsZSgpIHtcclxuXHRcdFx0aG9tZS52aWV3Zm9ybWF0ID0gJ3NtYWxsJztcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEV4aXQgc21hbGwgbXFcclxuXHRcdCAqIFNldCBob21lLnZpZXdmb3JtYXRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZSgpIHtcclxuXHRcdFx0aG9tZS52aWV3Zm9ybWF0ID0gJ2xhcmdlJztcclxuXHRcdH1cclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignU3ViQ3RybCcsIFN1YkN0cmwpO1xyXG5cclxuXHRTdWJDdHJsLiRpbmplY3QgPSBbJ1V0aWxzJywgJ1BhZ2UnLCAncmVzb2x2ZUxvY2FsRGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBTdWJDdHJsKFV0aWxzLCBQYWdlLCByZXNvbHZlTG9jYWxEYXRhKSB7XHJcblx0XHQvLyBjb250cm9sbGVyQXMgVmlld01vZGVsXHJcblx0XHR2YXIgc3ViID0gdGhpcztcclxuXHJcblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXHJcblx0XHRzdWIudGl0bGUgPSAnU3VicGFnZSc7XHJcblx0XHRzdWIuZ2xvYmFsID0gVXRpbHM7XHJcblx0XHRzdWIuanNvbiA9IHJlc29sdmVMb2NhbERhdGE7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cclxuXHRcdFx0UGFnZS5zZXRUaXRsZShzdWIudGl0bGUpO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiLCIvKipcclxuICogRGlyZWN0aXZlcyAoYW5kIGFzc29jaWF0ZWQgYXR0cmlidXRlcykgYXJlIGFsd2F5cyBkZWNsYXJlZCBhcyBjYW1lbENhc2UgaW4gSlMgYW5kIHNuYWtlLWNhc2UgaW4gSFRNTFxyXG4gKiBBbmd1bGFyJ3MgYnVpbHQtaW4gPGE+IGRpcmVjdGl2ZSBhdXRvbWF0aWNhbGx5IGltcGxlbWVudHMgcHJldmVudERlZmF1bHQgb24gbGlua3MgdGhhdCBkb24ndCBoYXZlIGFuIGhyZWYgYXR0cmlidXRlXHJcbiAqIENvbXBsZXggSmF2YVNjcmlwdCBET00gbWFuaXB1bGF0aW9uIHNob3VsZCBhbHdheXMgYmUgZG9uZSBpbiBkaXJlY3RpdmUgbGluayBmdW5jdGlvbnMsIGFuZCAkYXBwbHkgc2hvdWxkIG5ldmVyIGJlIHVzZWQgaW4gYSBjb250cm9sbGVyISBTaW1wbGUgRE9NIG1hbmlwdWxhdGlvbiBzaG91bGQgYmUgaW4gdGhlIHZpZXcuXHJcbiAqL1xyXG5cclxuLyotLS0gU2FtcGxlIERpcmVjdGl2ZSB3aXRoIGEgJHdhdGNoIC0tLSovXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmRpcmVjdGl2ZSgnc2FtcGxlRGlyZWN0aXZlJywgc2FtcGxlRGlyZWN0aXZlKTtcclxuXHJcblx0c2FtcGxlRGlyZWN0aXZlLiRpbmplY3QgPSBbJyR0aW1lb3V0J107XHJcblxyXG5cdGZ1bmN0aW9uIHNhbXBsZURpcmVjdGl2ZSgkdGltZW91dCkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdHJlcGxhY2U6IHRydWUsXHJcblx0XHRcdHNjb3BlOiB7fSxcclxuXHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvc2FtcGxlLnRwbC5odG1sJyxcclxuXHRcdFx0dHJhbnNjbHVkZTogdHJ1ZSxcclxuXHRcdFx0Y29udHJvbGxlcjogU2FtcGxlRGlyZWN0aXZlQ3RybCxcclxuXHRcdFx0Y29udHJvbGxlckFzOiAnc2QnLFxyXG5cdFx0XHRiaW5kVG9Db250cm9sbGVyOiB7XHJcblx0XHRcdFx0anNvbkRhdGE6ICc9J1xyXG5cdFx0XHR9LFxyXG5cdFx0XHRsaW5rOiBzYW1wbGVEaXJlY3RpdmVMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogc2FtcGxlRGlyZWN0aXZlIExJTksgZnVuY3Rpb25cclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJHNjb3BlXHJcblx0XHQgKiBAcGFyYW0gJGVsZW1lbnRcclxuXHRcdCAqIEBwYXJhbSAkYXR0cnNcclxuXHRcdCAqIEBwYXJhbSBzZCB7Y29udHJvbGxlcn1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gc2FtcGxlRGlyZWN0aXZlTGluaygkc2NvcGUsICRlbGVtZW50LCAkYXR0cnMsIHNkKSB7XHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIHdhdGNoIGZvciBhc3luYyBkYXRhIHRvIGJlY29tZSBhdmFpbGFibGUgYW5kIHVwZGF0ZVxyXG5cdFx0XHRcdCRzY29wZS4kd2F0Y2goJ3NkLmpzb25EYXRhJywgXyR3YXRjaEpzb25EYXRhKTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqICR3YXRjaCBmb3Igc2QuanNvbkRhdGEgdG8gYmVjb21lIGF2YWlsYWJsZVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHsqfVxyXG5cdFx0XHQgKiBAcGFyYW0gb2xkVmFsIHsqfVxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gXyR3YXRjaEpzb25EYXRhKG5ld1ZhbCwgb2xkVmFsKSB7XHJcblx0XHRcdFx0aWYgKG5ld1ZhbCkge1xyXG5cdFx0XHRcdFx0c2QuanNvbkRhdGEgPSBuZXdWYWw7XHJcblxyXG5cdFx0XHRcdFx0JHRpbWVvdXQoZnVuY3Rpb24oKSB7XHJcblx0XHRcdFx0XHRcdGNvbnNvbGUubG9nKCdkZW1vbnN0cmF0ZSAkdGltZW91dCBpbmplY3Rpb24gaW4gYSBkaXJlY3RpdmUgbGluayBmdW5jdGlvbicpO1xyXG5cdFx0XHRcdFx0fSwgMTAwMCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fVxyXG5cclxuXHRTYW1wbGVEaXJlY3RpdmVDdHJsLiRpbmplY3QgPSBbXTtcclxuXHQvKipcclxuXHQgKiBzYW1wbGVEaXJlY3RpdmUgQ09OVFJPTExFUlxyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIFNhbXBsZURpcmVjdGl2ZUN0cmwoKSB7XHJcblx0XHR2YXIgc2QgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGNvbnRyb2xsZXIgbG9naWMgZ29lcyBoZXJlXHJcblx0fVxyXG5cclxufSgpKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= \ No newline at end of file +(function() { + 'use strict'; + + angular + .module('reStart') + .controller('SubCtrl', SubCtrl); + + SubCtrl.$inject = ['Utils', 'Page', 'resolveLocalData']; + + function SubCtrl(Utils, Page, resolveLocalData) { + // controllerAs ViewModel + var sub = this; + + // bindable members + sub.title = 'Subpage'; + sub.global = Utils; + sub.json = resolveLocalData; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // set page <title> + Page.setTitle(sub.title); + } + } +}()); +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5tb2R1bGUuanMiLCJjb3JlL1BhZ2UuY3RybC5qcyIsImNvcmUvUGFnZS5mYWN0b3J5LmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LmpzIiwiY29yZS9hcHAtc2V0dXAvYXBwLmNvbmZpZy5qcyIsImNvcmUvZ2V0LWRhdGEvSlNPTkRhdGEuZmFjdG9yeS5qcyIsImNvcmUvZ2V0LWRhdGEvUmVzLmZhY3RvcnkuanMiLCJjb3JlL3VpL2xvYWRpbmcuZGlyLmpzIiwiY29yZS91aS9NUS5jb25zdGFudC5qcyIsImNvcmUvdWkvdHJ1c3RBc0hUTUwuZmlsdGVyLmpzIiwibW9kdWxlcy9oZWFkZXIvSGVhZGVyLmN0cmwuanMiLCJtb2R1bGVzL2hlYWRlci9uYXZDb250cm9sLmRpci5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuanMiLCJwYWdlcy9ob21lL0hvbWUuY3RybC5qcyIsInBhZ2VzL3N1Yi9zYW1wbGUuZGlyLmpzIiwicGFnZXMvc3ViL1N1Yi5jdHJsLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDTkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM3SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3JDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQy9KQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDeEVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDdklBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNsQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUN0R0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2xGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJyZVN0YXJ0LWFwcC5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8vIGFwcGxpY2F0aW9uIG1vZHVsZSBzZXR0ZXJcclxuKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcsIFsnbmdSb3V0ZScsICduZ1Jlc291cmNlJywgJ25nU2FuaXRpemUnLCAnbWVkaWFDaGVjaycsICdyZXNpemUnXSk7XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignUGFnZUN0cmwnLCBQYWdlQ3RybCk7XHJcblxyXG5cdFBhZ2VDdHJsLiRpbmplY3QgPSBbJ1BhZ2UnLCAnJHNjb3BlJywgJ01RJywgJ21lZGlhQ2hlY2snLCAnJGxvZyddO1xyXG5cclxuXHRmdW5jdGlvbiBQYWdlQ3RybChQYWdlLCAkc2NvcGUsIE1RLCBtZWRpYUNoZWNrLCAkbG9nKSB7XHJcblx0XHR2YXIgcGFnZSA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcclxuXHRcdHZhciBfaGFuZGxpbmdSb3V0ZUNoYW5nZUVycm9yID0gZmFsc2U7XHJcblx0XHQvLyBTZXQgdXAgZnVuY3Rpb25hbGl0eSB0byBydW4gb24gZW50ZXIvZXhpdCBvZiBtZWRpYSBxdWVyeVxyXG5cdFx0dmFyIF9tYyA9IG1lZGlhQ2hlY2suaW5pdCh7XHJcblx0XHRcdHNjb3BlOiAkc2NvcGUsXHJcblx0XHRcdG1lZGlhOiB7XHJcblx0XHRcdFx0bXE6IE1RLlNNQUxMLFxyXG5cdFx0XHRcdGVudGVyOiBfZW50ZXJNb2JpbGUsXHJcblx0XHRcdFx0ZXhpdDogX2V4aXRNb2JpbGVcclxuXHRcdFx0fSxcclxuXHRcdFx0ZGVib3VuY2U6IDIwMFxyXG5cdFx0fSk7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIGFzc29jaWF0ZSBwYWdlIDx0aXRsZT5cclxuXHRcdFx0cGFnZS5wYWdlVGl0bGUgPSBQYWdlO1xyXG5cclxuXHRcdFx0JHNjb3BlLiRvbignJHJvdXRlQ2hhbmdlU3RhcnQnLCBfcm91dGVDaGFuZ2VTdGFydCk7XHJcblx0XHRcdCRzY29wZS4kb24oJyRyb3V0ZUNoYW5nZVN1Y2Nlc3MnLCBfcm91dGVDaGFuZ2VTdWNjZXNzKTtcclxuXHRcdFx0JHNjb3BlLiRvbignJHJvdXRlQ2hhbmdlRXJyb3InLCBfcm91dGVDaGFuZ2VFcnJvcik7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFbnRlciBtb2JpbGUgbWVkaWEgcXVlcnlcclxuXHRcdCAqICRicm9hZGNhc3QgJ2VudGVyLW1vYmlsZScgZXZlbnRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZW50ZXJNb2JpbGUoKSB7XHJcblx0XHRcdCRzY29wZS4kYnJvYWRjYXN0KCdlbnRlci1tb2JpbGUnKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEV4aXQgbW9iaWxlIG1lZGlhIHF1ZXJ5XHJcblx0XHQgKiAkYnJvYWRjYXN0ICdleGl0LW1vYmlsZScgZXZlbnRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZSgpIHtcclxuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2V4aXQtbW9iaWxlJyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBUdXJuIG9uIGxvYWRpbmcgc3RhdGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfbG9hZGluZ09uKCkge1xyXG5cdFx0XHQkc2NvcGUuJGJyb2FkY2FzdCgnbG9hZGluZy1vbicpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogVHVybiBvZmYgbG9hZGluZyBzdGF0ZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9sb2FkaW5nT2ZmKCkge1xyXG5cdFx0XHQkc2NvcGUuJGJyb2FkY2FzdCgnbG9hZGluZy1vZmYnKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFJvdXRlIGNoYW5nZSBzdGFydCBoYW5kbGVyXHJcblx0XHQgKiBJZiBuZXh0IHJvdXRlIGhhcyByZXNvbHZlLCB0dXJuIG9uIGxvYWRpbmdcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJGV2ZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gbmV4dCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIGN1cnJlbnQge29iamVjdH1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9yb3V0ZUNoYW5nZVN0YXJ0KCRldmVudCwgbmV4dCwgY3VycmVudCkge1xyXG5cdFx0XHRpZiAobmV4dC4kJHJvdXRlICYmIG5leHQuJCRyb3V0ZS5yZXNvbHZlKSB7IC8vIGVzbGludC1kaXNhYmxlLWxpbmUgYW5ndWxhci9uby1wcml2YXRlLWNhbGxcclxuXHRcdFx0XHRfbG9hZGluZ09uKCk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFJvdXRlIGNoYW5nZSBzdWNjZXNzIGhhbmRsZXJcclxuXHRcdCAqIE1hdGNoIGN1cnJlbnQgbWVkaWEgcXVlcnkgYW5kIHJ1biBhcHByb3ByaWF0ZSBmdW5jdGlvblxyXG5cdFx0ICogSWYgY3VycmVudCByb3V0ZSBoYXMgYmVlbiByZXNvbHZlZCwgdHVybiBvZmYgbG9hZGluZ1xyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gcHJldmlvdXMge29iamVjdH1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9yb3V0ZUNoYW5nZVN1Y2Nlc3MoJGV2ZW50LCBjdXJyZW50LCBwcmV2aW91cykge1xyXG5cdFx0XHRfbWMubWF0Y2hDdXJyZW50KE1RLlNNQUxMKTtcclxuXHJcblx0XHRcdGlmIChjdXJyZW50LiQkcm91dGUgJiYgY3VycmVudC4kJHJvdXRlLnJlc29sdmUpIHsgICAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIGFuZ3VsYXIvbm8tcHJpdmF0ZS1jYWxsXHJcblx0XHRcdFx0X2xvYWRpbmdPZmYoKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogUm91dGUgY2hhbmdlIGVycm9yIGhhbmRsZXJcclxuXHRcdCAqIEhhbmRsZSByb3V0ZSByZXNvbHZlIGZhaWx1cmVzXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRldmVudCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIGN1cnJlbnQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBwcmV2aW91cyB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIHJlamVjdGlvbiB7b2JqZWN0fVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX3JvdXRlQ2hhbmdlRXJyb3IoJGV2ZW50LCBjdXJyZW50LCBwcmV2aW91cywgcmVqZWN0aW9uKSB7XHJcblx0XHRcdHZhciBkZXN0aW5hdGlvbiA9IChjdXJyZW50ICYmIChjdXJyZW50LnRpdGxlIHx8IGN1cnJlbnQubmFtZSB8fCBjdXJyZW50LmxvYWRlZFRlbXBsYXRlVXJsKSkgfHwgJ3Vua25vd24gdGFyZ2V0JztcclxuXHRcdFx0dmFyIG1zZyA9ICdFcnJvciByb3V0aW5nIHRvICcgKyBkZXN0aW5hdGlvbiArICcuICcgKyAocmVqZWN0aW9uLm1zZyB8fCAnJyk7XHJcblxyXG5cdFx0XHRpZiAoX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvcikge1xyXG5cdFx0XHRcdHJldHVybjtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0X2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvciA9IHRydWU7XHJcblx0XHRcdF9sb2FkaW5nT2ZmKCk7XHJcblxyXG5cdFx0XHQkbG9nLmVycm9yKG1zZyk7XHJcblx0XHR9XHJcblx0XHRQYWdlQ3RybC5lbnRlck1vYmlsZSA9IF9lbnRlck1vYmlsZTsvL3Rlc3QgY29kZVxyXG5cdFx0UGFnZUN0cmwuZXhpdE1vYmlsZSA9IF9leGl0TW9iaWxlOy8vdGVzdCBjb2RlXHJcblx0XHRQYWdlQ3RybC5sb2FkaW5nT24gPSBfbG9hZGluZ09uOy8vdGVzdCBjb2RlXHJcblx0XHRQYWdlQ3RybC5sb2FkaW5nT2ZmID0gX2xvYWRpbmdPZmY7Ly90ZXN0IGNvZGVcclxuXHRcdHJldHVybiBQYWdlQ3RybDsvL3Rlc3QgY29kZVxyXG5cdH1cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5mYWN0b3J5KCdQYWdlJywgUGFnZSk7XHJcblxyXG5cdGZ1bmN0aW9uIFBhZ2UoKSB7XHJcblx0XHQvLyBwcml2YXRlIHZhcnNcclxuXHRcdHZhciBzaXRlVGl0bGUgPSAncmVTdGFydCBBbmd1bGFyJztcclxuXHRcdHZhciBwYWdlVGl0bGUgPSAnSG9tZSc7XHJcblxyXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0Z2V0VGl0bGU6IGdldFRpdGxlLFxyXG5cdFx0XHRzZXRUaXRsZTogc2V0VGl0bGVcclxuXHRcdH07XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBUaXRsZSBmdW5jdGlvblxyXG5cdFx0ICogU2V0cyBzaXRlIHRpdGxlIGFuZCBwYWdlIHRpdGxlXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMge3N0cmluZ30gc2l0ZSB0aXRsZSArIHBhZ2UgdGl0bGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gZ2V0VGl0bGUoKSB7XHJcblx0XHRcdHJldHVybiBzaXRlVGl0bGUgKyAnIHwgJyArIHBhZ2VUaXRsZTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFNldCBwYWdlIHRpdGxlXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIG5ld1RpdGxlIHtzdHJpbmd9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIHNldFRpdGxlKG5ld1RpdGxlKSB7XHJcblx0XHRcdHBhZ2VUaXRsZSA9IG5ld1RpdGxlO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiLCIvLyBcImdsb2JhbFwiIG9iamVjdCB0byBzaGFyZSBiZXR3ZWVuIGNvbnRyb2xsZXJzXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmZhY3RvcnkoJ1V0aWxzJywgVXRpbHMpO1xyXG5cclxuXHRmdW5jdGlvbiBVdGlscygpIHtcclxuXHRcdHZhciBncmVldGluZyA9ICdIZWxsbyc7XHJcblxyXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0Z3JlZXRpbmc6IGdyZWV0aW5nLFxyXG5cdFx0XHRhbGVydEdyZWV0aW5nOiBhbGVydEdyZWV0aW5nXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQWxlcnQgZ3JlZXRpbmdcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gbmFtZSB7c3RyaW5nfVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBhbGVydEdyZWV0aW5nKG5hbWUpIHtcclxuXHRcdFx0YWxlcnQoZ3JlZXRpbmcgKyAnLCAnICsgbmFtZSArICchJyk7XHJcblx0XHR9XHJcblx0fVxyXG59KCkpOyIsIi8vIGFwcGxpY2F0aW9uIGNvbmZpZ1xyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb25maWcoYXBwQ29uZmlnKTtcclxuXHJcblx0YXBwQ29uZmlnLiRpbmplY3QgPSBbJyRyb3V0ZVByb3ZpZGVyJywgJyRsb2NhdGlvblByb3ZpZGVyJ107XHJcblxyXG5cdGZ1bmN0aW9uIGFwcENvbmZpZygkcm91dGVQcm92aWRlciwgJGxvY2F0aW9uUHJvdmlkZXIpIHtcclxuXHRcdCRyb3V0ZVByb3ZpZGVyXHJcblx0XHRcdC53aGVuKCcvJywge1xyXG5cdFx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvcGFnZXMvaG9tZS9Ib21lLnZpZXcuaHRtbCcsXHJcblx0XHRcdFx0Y29udHJvbGxlcjogJ0hvbWVDdHJsJyxcclxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdob21lJ1xyXG5cdFx0XHR9KVxyXG5cdFx0XHQud2hlbignL3N1YnBhZ2UnLCB7XHJcblx0XHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvU3ViLnZpZXcuaHRtbCcsXHJcblx0XHRcdFx0Y29udHJvbGxlcjogJ1N1YkN0cmwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXJBczogJ3N1YicsXHJcblx0XHRcdFx0cmVzb2x2ZToge1xyXG5cdFx0XHRcdFx0cmVzb2x2ZUxvY2FsRGF0YTogcmVzb2x2ZUxvY2FsRGF0YVxyXG5cdFx0XHRcdH1cclxuXHRcdFx0fSlcclxuXHRcdFx0Lm90aGVyd2lzZSh7XHJcblx0XHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9lcnJvcjQwNC9FcnJvcjQwNC52aWV3Lmh0bWwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdFcnJvcjQwNEN0cmwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXJBczogJ2U0MDQnXHJcblx0XHRcdH0pO1xyXG5cclxuXHRcdCRsb2NhdGlvblByb3ZpZGVyXHJcblx0XHRcdC5odG1sNU1vZGUoe1xyXG5cdFx0XHRcdGVuYWJsZWQ6IHRydWVcclxuXHRcdFx0fSlcclxuXHRcdFx0Lmhhc2hQcmVmaXgoJyEnKTtcclxuXHR9XHJcblxyXG5cdHJlc29sdmVMb2NhbERhdGEuJGluamVjdCA9IFsnSlNPTkRhdGEnXTtcclxuXHQvKipcclxuXHQgKiBHZXQgbG9jYWwgZGF0YSBmb3Igcm91dGUgcmVzb2x2ZVxyXG5cdCAqXHJcblx0ICogQHBhcmFtIEpTT05EYXRhIHtmYWN0b3J5fVxyXG5cdCAqIEByZXR1cm5zIHtwcm9taXNlfSBkYXRhXHJcblx0ICovXHJcblx0ZnVuY3Rpb24gcmVzb2x2ZUxvY2FsRGF0YShKU09ORGF0YSkge1xyXG5cdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpO1xyXG5cdH1cclxufSgpKTsiLCIvLyBmZXRjaCBKU09OIGRhdGEgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5mYWN0b3J5KCdKU09ORGF0YScsIEpTT05EYXRhKTtcclxuXHJcblx0SlNPTkRhdGEuJGluamVjdCA9IFsnJGh0dHAnLCAnUmVzJ107XHJcblxyXG5cdGZ1bmN0aW9uIEpTT05EYXRhKCRodHRwLCBSZXMpIHtcclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdGdldExvY2FsRGF0YTogZ2V0TG9jYWxEYXRhXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogR0VUIGxvY2FsIEpTT04gZGF0YSBmaWxlIGFuZCByZXR1cm4gcmVzdWx0c1xyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHtwcm9taXNlfVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBnZXRMb2NhbERhdGEoKSB7XHJcblx0XHRcdHJldHVybiAkaHR0cFxyXG5cdFx0XHRcdC5nZXQoJy9kYXRhL2RhdGEuanNvbicpXHJcblx0XHRcdFx0LnRoZW4oUmVzLnN1Y2Nlc3MsIFJlcy5lcnJvcik7XHJcblx0XHR9XHJcblx0fVxyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmZhY3RvcnkoJ1JlcycsIFJlcyk7XHJcblxyXG5cdGZ1bmN0aW9uIFJlcygpIHtcclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdHN1Y2Nlc3M6IHN1Y2Nlc3MsXHJcblx0XHRcdGVycm9yOiBlcnJvclxyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFByb21pc2UgcmVzcG9uc2UgZnVuY3Rpb25cclxuXHRcdCAqIENoZWNrcyB0eXBlb2YgZGF0YSByZXR1cm5lZCBhbmQgc3VjY2VlZHMgaWYgSlMgb2JqZWN0LCB0aHJvd3MgZXJyb3IgaWYgbm90XHJcblx0XHQgKiBVc2VmdWwgZm9yIEFQSXMgKGllLCB3aXRoIG5naW54KSB3aGVyZSBzZXJ2ZXIgZXJyb3IgSFRNTCBwYWdlIG1heSBiZSByZXR1cm5lZCBpbiBlcnJvclxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSByZXNwb25zZSB7Kn0gZGF0YSBmcm9tICRodHRwXHJcblx0XHQgKiBAcmV0dXJucyB7Kn0gb2JqZWN0LCBhcnJheVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBzdWNjZXNzKHJlc3BvbnNlKSB7XHJcblx0XHRcdGlmIChhbmd1bGFyLmlzT2JqZWN0KHJlc3BvbnNlLmRhdGEpKSB7XHJcblx0XHRcdFx0cmV0dXJuIHJlc3BvbnNlLmRhdGE7XHJcblx0XHRcdH0gZWxzZSB7XHJcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKCdyZXRyaWV2ZWQgZGF0YSBpcyBub3QgdHlwZW9mIG9iamVjdC4nKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogUHJvbWlzZSByZXNwb25zZSBmdW5jdGlvbiAtIGVycm9yXHJcblx0XHQgKiBUaHJvd3MgYW4gZXJyb3Igd2l0aCBlcnJvciBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGVycm9yIHtvYmplY3R9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGVycm9yKGVycm9yKSB7XHJcblx0XHRcdHRocm93IG5ldyBFcnJvcignRXJyb3IgcmV0cmlldmluZyBkYXRhJywgZXJyb3IpO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5kaXJlY3RpdmUoJ2xvYWRpbmcnLCBsb2FkaW5nKTtcclxuXHJcblx0bG9hZGluZy4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xyXG5cclxuXHRmdW5jdGlvbiBsb2FkaW5nKCR3aW5kb3csIHJlc2l6ZSkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdHJlcGxhY2U6IHRydWUsXHJcblx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvY29yZS91aS9sb2FkaW5nLnRwbC5odG1sJyxcclxuXHRcdFx0dHJhbnNjbHVkZTogdHJ1ZSxcclxuXHRcdFx0Y29udHJvbGxlcjogbG9hZGluZ0N0cmwsXHJcblx0XHRcdGNvbnRyb2xsZXJBczogJ2xvYWRpbmcnLFxyXG5cdFx0XHRiaW5kVG9Db250cm9sbGVyOiB0cnVlLFxyXG5cdFx0XHRsaW5rOiBsb2FkaW5nTGlua1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIGxvYWRpbmcgTElOS1xyXG5cdFx0ICogRGlzYWJsZXMgcGFnZSBzY3JvbGxpbmcgd2hlbiBsb2FkaW5nIG92ZXJsYXkgaXMgb3BlblxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkc2NvcGVcclxuXHRcdCAqIEBwYXJhbSAkZWxlbWVudFxyXG5cdFx0ICogQHBhcmFtICRhdHRyc1xyXG5cdFx0ICogQHBhcmFtIGxvYWRpbmcge2NvbnRyb2xsZXJ9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGxvYWRpbmdMaW5rKCRzY29wZSwgJGVsZW1lbnQsICRhdHRycywgbG9hZGluZykge1xyXG5cdFx0XHQvLyBwcml2YXRlIHZhcmlhYmxlc1xyXG5cdFx0XHR2YXIgXyRib2R5ID0gYW5ndWxhci5lbGVtZW50KCdib2R5Jyk7XHJcblx0XHRcdHZhciBfd2luSGVpZ2h0ID0gJHdpbmRvdy5pbm5lckhlaWdodCArICdweCc7XHJcblxyXG5cdFx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0XHQvLyBpbml0aWFsaXplIGRlYm91bmNlZCByZXNpemVcclxuXHRcdFx0XHR2YXIgX3JzID0gcmVzaXplLmluaXQoe1xyXG5cdFx0XHRcdFx0c2NvcGU6ICRzY29wZSxcclxuXHRcdFx0XHRcdHJlc2l6ZWRGbjogX3Jlc2l6ZWQsXHJcblx0XHRcdFx0XHRkZWJvdW5jZTogMjAwXHJcblx0XHRcdFx0fSk7XHJcblxyXG5cdFx0XHRcdC8vICR3YXRjaCBhY3RpdmUgc3RhdGVcclxuXHRcdFx0XHQkc2NvcGUuJHdhdGNoKCdsb2FkaW5nLmFjdGl2ZScsIF8kd2F0Y2hBY3RpdmUpO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogV2luZG93IHJlc2l6ZWRcclxuXHRcdFx0ICogSWYgbG9hZGluZywgcmVhcHBseSBib2R5IGhlaWdodFxyXG5cdFx0XHQgKiB0byBwcmV2ZW50IHNjcm9sbGJhclxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX3Jlc2l6ZWQoKSB7XHJcblx0XHRcdFx0X3dpbkhlaWdodCA9ICR3aW5kb3cuaW5uZXJIZWlnaHQgKyAncHgnO1xyXG5cclxuXHRcdFx0XHRpZiAobG9hZGluZy5hY3RpdmUpIHtcclxuXHRcdFx0XHRcdF8kYm9keS5jc3Moe1xyXG5cdFx0XHRcdFx0XHRoZWlnaHQ6IF93aW5IZWlnaHQsXHJcblx0XHRcdFx0XHRcdG92ZXJmbG93WTogJ2hpZGRlbidcclxuXHRcdFx0XHRcdH0pO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqICR3YXRjaCBsb2FkaW5nLmFjdGl2ZVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHtib29sZWFufVxyXG5cdFx0XHQgKiBAcGFyYW0gb2xkVmFsIHt1bmRlZmluZWR8Ym9vbGVhbn1cclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF8kd2F0Y2hBY3RpdmUobmV3VmFsLCBvbGRWYWwpIHtcclxuXHRcdFx0XHRpZiAobmV3VmFsKSB7XHJcblx0XHRcdFx0XHRfb3BlbigpO1xyXG5cdFx0XHRcdH0gZWxzZSB7XHJcblx0XHRcdFx0XHRfY2xvc2UoKTtcclxuXHRcdFx0XHR9XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBPcGVuIGxvYWRpbmdcclxuXHRcdFx0ICogRGlzYWJsZSBzY3JvbGxcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9vcGVuKCkge1xyXG5cdFx0XHRcdF8kYm9keS5jc3Moe1xyXG5cdFx0XHRcdFx0aGVpZ2h0OiBfd2luSGVpZ2h0LFxyXG5cdFx0XHRcdFx0b3ZlcmZsb3dZOiAnaGlkZGVuJ1xyXG5cdFx0XHRcdH0pO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogQ2xvc2UgbG9hZGluZ1xyXG5cdFx0XHQgKiBFbmFibGUgc2Nyb2xsXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfY2xvc2UoKSB7XHJcblx0XHRcdFx0XyRib2R5LmNzcyh7XHJcblx0XHRcdFx0XHRoZWlnaHQ6ICdhdXRvJyxcclxuXHRcdFx0XHRcdG92ZXJmbG93WTogJ2F1dG8nXHJcblx0XHRcdFx0fSk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHR9XHJcblxyXG5cdGxvYWRpbmdDdHJsLiRpbmplY3QgPSBbJyRzY29wZSddO1xyXG5cdC8qKlxyXG5cdCAqIGxvYWRpbmcgQ09OVFJPTExFUlxyXG5cdCAqIFVwZGF0ZSB0aGUgbG9hZGluZyBzdGF0dXMgYmFzZWRcclxuXHQgKiBvbiByb3V0ZUNoYW5nZSBzdGF0ZVxyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGxvYWRpbmdDdHJsKCRzY29wZSkge1xyXG5cdFx0dmFyIGxvYWRpbmcgPSB0aGlzO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyB0dXJuIG9uIGxvYWRpbmcgZm9yIGluaXRpYWwgcGFnZSBsb2FkXHJcblx0XHRcdF9sb2FkaW5nQWN0aXZlKCk7XHJcblxyXG5cdFx0XHQkc2NvcGUuJG9uKCdsb2FkaW5nLW9uJywgX2xvYWRpbmdBY3RpdmUpO1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdsb2FkaW5nLW9mZicsIF9sb2FkaW5nSW5hY3RpdmUpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogU2V0IGxvYWRpbmcgdG8gYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdBY3RpdmUoKSB7XHJcblx0XHRcdGxvYWRpbmcuYWN0aXZlID0gdHJ1ZTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFNldCBsb2FkaW5nIHRvIGluYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdJbmFjdGl2ZSgpIHtcclxuXHRcdFx0bG9hZGluZy5hY3RpdmUgPSBmYWxzZTtcclxuXHRcdH1cclxuXHR9XHJcblxyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdC8vIG1lZGlhIHF1ZXJ5IGNvbnN0YW50c1xyXG5cdHZhciBNUSA9IHtcclxuXHRcdFNNQUxMOiAnKG1heC13aWR0aDogNzY3cHgpJyxcclxuXHRcdExBUkdFOiAnKG1pbi13aWR0aDogNzY4cHgpJ1xyXG5cdH07XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnN0YW50KCdNUScsIE1RKTtcclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5maWx0ZXIoJ3RydXN0QXNIVE1MJywgdHJ1c3RBc0hUTUwpO1xyXG5cclxuXHR0cnVzdEFzSFRNTC4kaW5qZWN0ID0gWyckc2NlJ107XHJcblxyXG5cdGZ1bmN0aW9uIHRydXN0QXNIVE1MKCRzY2UpIHtcclxuXHRcdHJldHVybiBmdW5jdGlvbih0ZXh0KSB7XHJcblx0XHRcdHJldHVybiAkc2NlLnRydXN0QXNIdG1sKHRleHQpO1xyXG5cdFx0fTtcclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignSGVhZGVyQ3RybCcsIEhlYWRlckN0cmwpO1xyXG5cclxuXHRIZWFkZXJDdHJsLiRpbmplY3QgPSBbJyRsb2NhdGlvbicsICdKU09ORGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBIZWFkZXJDdHJsKCRsb2NhdGlvbiwgSlNPTkRhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBoZWFkZXIgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGhlYWRlci5pbmRleElzQWN0aXZlID0gaW5kZXhJc0FjdGl2ZTtcclxuXHRcdGhlYWRlci5uYXZJc0FjdGl2ZSA9IG5hdklzQWN0aXZlO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBhY3RpdmF0ZSBjb250cm9sbGVyXHJcblx0XHRcdF9hY3RpdmF0ZSgpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQ29udHJvbGxlciBhY3RpdmF0ZVxyXG5cdFx0ICogR2V0IEpTT04gZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHsqfVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2FjdGl2YXRlKCkge1xyXG5cdFx0XHQvLyBnZXQgdGhlIGRhdGEgZnJvbSBKU09OXHJcblx0XHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKS50aGVuKF9nZXRKc29uU3VjY2Vzcyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTdWNjZXNzZnVsIHByb21pc2UgZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSBkYXRhIHtqc29ufVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2dldEpzb25TdWNjZXNzKGRhdGEpIHtcclxuXHRcdFx0aGVhZGVyLmpzb24gPSBkYXRhO1xyXG5cdFx0XHRyZXR1cm4gaGVhZGVyLmpzb247XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBBcHBseSBjbGFzcyB0byBpbmRleCBuYXYgaWYgYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIHtzdHJpbmd9IHBhdGhcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gaW5kZXhJc0FjdGl2ZShwYXRoKSB7XHJcblx0XHRcdC8vIHBhdGggc2hvdWxkIGJlICcvJ1xyXG5cdFx0XHRyZXR1cm4gJGxvY2F0aW9uLnBhdGgoKSA9PT0gcGF0aDtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFwcGx5IGNsYXNzIHRvIGN1cnJlbnRseSBhY3RpdmUgbmF2IGl0ZW1cclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0ge3N0cmluZ30gcGF0aFxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBuYXZJc0FjdGl2ZShwYXRoKSB7XHJcblx0XHRcdHJldHVybiAkbG9jYXRpb24ucGF0aCgpLnN1YnN0cigwLCBwYXRoLmxlbmd0aCkgPT09IHBhdGg7XHJcblx0XHR9XHJcblx0fVxyXG5cclxufSgpKTsiLCIoZnVuY3Rpb24gKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuZGlyZWN0aXZlKCduYXZDb250cm9sJywgbmF2Q29udHJvbCk7XHJcblxyXG5cdG5hdkNvbnRyb2wuJGluamVjdCA9IFsnJHdpbmRvdycsICdyZXNpemUnXTtcclxuXHJcblx0ZnVuY3Rpb24gbmF2Q29udHJvbCgkd2luZG93LCByZXNpemUpIHtcclxuXHRcdC8vIHJldHVybiBkaXJlY3RpdmVcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdHJlc3RyaWN0OiAnRUEnLFxyXG5cdFx0XHRsaW5rOiBuYXZDb250cm9sTGlua1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIG5hdkNvbnRyb2wgTElOSyBmdW5jdGlvblxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkc2NvcGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gbmF2Q29udHJvbExpbmsoJHNjb3BlKSB7XHJcblx0XHRcdC8vIHByaXZhdGUgdmFyaWFibGVzXHJcblx0XHRcdHZhciBfJGJvZHkgPSBhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKTtcclxuXHRcdFx0dmFyIF9sYXlvdXRDYW52YXMgPSBfJGJvZHkuZmluZCgnLmxheW91dC1jYW52YXMnKTtcclxuXHRcdFx0dmFyIF9uYXZPcGVuO1xyXG5cclxuXHRcdFx0Ly8gZGF0YSBtb2RlbFxyXG5cdFx0XHQkc2NvcGUubmF2ID0ge307XHJcblxyXG5cdFx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0XHQvLyBpbml0aWFsaXplIGRlYm91bmNlZCByZXNpemVcclxuXHRcdFx0XHR2YXIgX3JzID0gcmVzaXplLmluaXQoe1xyXG5cdFx0XHRcdFx0c2NvcGU6ICRzY29wZSxcclxuXHRcdFx0XHRcdHJlc2l6ZWRGbjogX3Jlc2l6ZWQsXHJcblx0XHRcdFx0XHRkZWJvdW5jZTogMTAwXHJcblx0XHRcdFx0fSk7XHJcblxyXG5cdFx0XHRcdCRzY29wZS4kb24oJyRsb2NhdGlvbkNoYW5nZVN0YXJ0JywgXyRsb2NhdGlvbkNoYW5nZVN0YXJ0KTtcclxuXHRcdFx0XHQkc2NvcGUuJG9uKCdlbnRlci1tb2JpbGUnLCBfZW50ZXJNb2JpbGUpO1xyXG5cdFx0XHRcdCRzY29wZS4kb24oJ2V4aXQtbW9iaWxlJywgX2V4aXRNb2JpbGUpO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogUmVzaXplZCB3aW5kb3cgKGRlYm91bmNlZClcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9yZXNpemVkKCkge1xyXG5cdFx0XHRcdF9sYXlvdXRDYW52YXMuY3NzKHtcclxuXHRcdFx0XHRcdG1pbkhlaWdodDogJHdpbmRvdy5pbm5lckhlaWdodCArICdweCdcclxuXHRcdFx0XHR9KTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIE9wZW4gbW9iaWxlIG5hdmlnYXRpb25cclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9vcGVuTmF2KCkge1xyXG5cdFx0XHRcdF8kYm9keVxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduYXYtY2xvc2VkJylcclxuXHRcdFx0XHRcdC5hZGRDbGFzcygnbmF2LW9wZW4nKTtcclxuXHJcblx0XHRcdFx0X25hdk9wZW4gPSB0cnVlO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogQ2xvc2UgbW9iaWxlIG5hdmlnYXRpb25cclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9jbG9zZU5hdigpIHtcclxuXHRcdFx0XHRfJGJvZHlcclxuXHRcdFx0XHRcdC5yZW1vdmVDbGFzcygnbmF2LW9wZW4nKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCduYXYtY2xvc2VkJyk7XHJcblxyXG5cdFx0XHRcdF9uYXZPcGVuID0gZmFsc2U7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBUb2dnbGUgbmF2IG9wZW4vY2xvc2VkXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiB0b2dnbGVOYXYoKSB7XHJcblx0XHRcdFx0aWYgKCFfbmF2T3Blbikge1xyXG5cdFx0XHRcdFx0X29wZW5OYXYoKTtcclxuXHRcdFx0XHR9IGVsc2Uge1xyXG5cdFx0XHRcdFx0X2Nsb3NlTmF2KCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogV2hlbiBjaGFuZ2luZyBsb2NhdGlvbiwgY2xvc2UgdGhlIG5hdiBpZiBpdCdzIG9wZW5cclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF8kbG9jYXRpb25DaGFuZ2VTdGFydCgpIHtcclxuXHRcdFx0XHRpZiAoX25hdk9wZW4pIHtcclxuXHRcdFx0XHRcdF9jbG9zZU5hdigpO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIEZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2hlbiBlbnRlcmluZyBtb2JpbGUgbWVkaWEgcXVlcnlcclxuXHRcdFx0ICogQ2xvc2UgbmF2IGFuZCBzZXQgdXAgbWVudSB0b2dnbGluZyBmdW5jdGlvbmFsaXR5XHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfZW50ZXJNb2JpbGUobXEpIHtcclxuXHRcdFx0XHRfY2xvc2VOYXYoKTtcclxuXHJcblx0XHRcdFx0Ly8gYmluZCBmdW5jdGlvbiB0byB0b2dnbGUgbW9iaWxlIG5hdmlnYXRpb24gb3Blbi9jbG9zZWRcclxuXHRcdFx0XHQkc2NvcGUubmF2LnRvZ2dsZU5hdiA9IHRvZ2dsZU5hdjtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIEZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2hlbiBleGl0aW5nIG1vYmlsZSBtZWRpYSBxdWVyeVxyXG5cdFx0XHQgKiBEaXNhYmxlIG1lbnUgdG9nZ2xpbmcgYW5kIHJlbW92ZSBib2R5IGNsYXNzZXNcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9leGl0TW9iaWxlKG1xKSB7XHJcblx0XHRcdFx0Ly8gdW5iaW5kIGZ1bmN0aW9uIHRvIHRvZ2dsZSBtb2JpbGUgbmF2aWdhdGlvbiBvcGVuL2Nsb3NlZFxyXG5cdFx0XHRcdCRzY29wZS5uYXYudG9nZ2xlTmF2ID0gbnVsbDtcclxuXHJcblx0XHRcdFx0XyRib2R5LnJlbW92ZUNsYXNzKCduYXYtY2xvc2VkIG5hdi1vcGVuJyk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHR9XHJcblxyXG59KCkpOyIsIihmdW5jdGlvbiAoKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdFcnJvcjQwNEN0cmwnLCBFcnJvcjQwNEN0cmwpO1xyXG5cclxuXHRFcnJvcjQwNEN0cmwuJGluamVjdCA9IFsnJHNjb3BlJywgJ1BhZ2UnXTtcclxuXHJcblx0ZnVuY3Rpb24gRXJyb3I0MDRDdHJsKCRzY29wZSwgUGFnZSkge1xyXG5cdFx0dmFyIGU0MDQgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGU0MDQudGl0bGUgPSAnNDA0IC0gUGFnZSBOb3QgRm91bmQnO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBzZXQgcGFnZSA8dGl0bGU+XHJcblx0XHRcdFBhZ2Uuc2V0VGl0bGUoZTQwNC50aXRsZSk7XHJcblxyXG5cdFx0XHQvLyBubyBkYXRhIHRvIGxvYWQsIGJ1dCBsb2FkaW5nIHN0YXRlIG1pZ2h0IGJlIG9uXHJcblx0XHRcdCRzY29wZS4kZW1pdCgnbG9hZGluZy1vZmYnKTtcclxuXHRcdH1cclxuXHJcblx0XHRyZXR1cm4geyAgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdFx0aW5pdDogX2luaXQgLy90ZXN0IGNvZGVcclxuXHRcdH07ICAgICAgICAgICAgICAgLy90ZXN0IGNvZGVcclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uICgpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnRyb2xsZXIoJ0hvbWVDdHJsJywgSG9tZUN0cmwpO1xyXG5cclxuXHRIb21lQ3RybC4kaW5qZWN0ID0gWyckc2NvcGUnLCAnVXRpbHMnLCAnUGFnZScsICdKU09ORGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBIb21lQ3RybCgkc2NvcGUsIFV0aWxzLCBQYWdlLCBKU09ORGF0YSkge1xyXG5cdFx0Ly8gY29udHJvbGxlckFzIFZpZXdNb2RlbFxyXG5cdFx0dmFyIGhvbWUgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGhvbWUudGl0bGUgPSAnSG9tZSc7XHJcblx0XHRob21lLmdsb2JhbCA9IFV0aWxzO1xyXG5cdFx0aG9tZS5uYW1lID0gJ1Zpc2l0b3InO1xyXG5cdFx0aG9tZS5hbGVydEdyZWV0aW5nID0gVXRpbHMuYWxlcnRHcmVldGluZztcclxuXHRcdGhvbWUuc3RyaW5nT2ZIVE1MID0gJzxzdHJvbmcgc3R5bGU9XCJjb2xvcjogZ3JlZW47XCI+U29tZSBncmVlbiB0ZXh0PC9zdHJvbmc+IGJvdW5kIGFzIEhUTUwgd2l0aCBhIDxhIGhyZWY9XCIjXCI+bGluazwvYT4sIHRydXN0ZWQgd2l0aCBTQ0UhJztcclxuXHRcdGhvbWUudmlld2Zvcm1hdCA9IG51bGw7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cclxuXHRcdFx0UGFnZS5zZXRUaXRsZShob21lLnRpdGxlKTtcclxuXHJcblx0XHRcdC8vIGFjdGl2YXRlIGNvbnRyb2xsZXJcclxuXHRcdFx0X2FjdGl2YXRlKCk7XHJcblxyXG5cdFx0XHQvLyBtZWRpYXF1ZXJ5IGV2ZW50c1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdlbnRlci1tb2JpbGUnLCBfZW50ZXJNb2JpbGUpO1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdleGl0LW1vYmlsZScsIF9leGl0TW9iaWxlKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIENvbnRyb2xsZXIgYWN0aXZhdGVcclxuXHRcdCAqIEdldCBKU09OIGRhdGFcclxuXHRcdCAqXHJcblx0XHQgKiBAcmV0dXJucyB7Kn1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9hY3RpdmF0ZSgpIHtcclxuXHRcdFx0Ly8gc3RhcnQgbG9hZGluZ1xyXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb24nKTtcclxuXHJcblx0XHRcdC8vIGdldCB0aGUgZGF0YSBmcm9tIEpTT05cclxuXHRcdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpLnRoZW4oX2dldEpzb25TdWNjZXNzKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFN1Y2Nlc3NmdWwgcHJvbWlzZSBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGRhdGEge2pzb259XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZ2V0SnNvblN1Y2Nlc3MoZGF0YSkge1xyXG5cdFx0XHRob21lLmpzb24gPSBkYXRhO1xyXG5cclxuXHRcdFx0Ly8gc3RvcCBsb2FkaW5nXHJcblx0XHRcdCRzY29wZS4kZW1pdCgnbG9hZGluZy1vZmYnKTtcclxuXHJcblx0XHRcdHJldHVybiBob21lLmpzb247XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFbnRlciBzbWFsbCBtcVxyXG5cdFx0ICogU2V0IGhvbWUudmlld2Zvcm1hdFxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9lbnRlck1vYmlsZSgpIHtcclxuXHRcdFx0aG9tZS52aWV3Zm9ybWF0ID0gJ3NtYWxsJztcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEV4aXQgc21hbGwgbXFcclxuXHRcdCAqIFNldCBob21lLnZpZXdmb3JtYXRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZSgpIHtcclxuXHRcdFx0aG9tZS52aWV3Zm9ybWF0ID0gJ2xhcmdlJztcclxuXHRcdH1cclxuXHJcblx0XHRmdW5jdGlvbiBnZXRWaWV3KCkgeyAgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0XHRyZXR1cm4gaG9tZS52aWV3Zm9ybWF0OyAvL3Rlc3QgY29kZVxyXG5cdFx0fSAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vdGVzdCBjb2RlXHJcblxyXG5cdFx0aG9tZS5lbnRlck1vYmlsZSA9IF9lbnRlck1vYmlsZTsgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0aG9tZS5leGl0TW9iaWxlID0gX2V4aXRNb2JpbGU7ICAgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0aG9tZS5nZXRKc29uU3VjZXNzID0gX2dldEpzb25TdWNjZXNzOyAvL3Rlc3QgY29kZVxyXG5cdFx0aG9tZS5hY3RpdmF0ZSA9IF9hY3RpdmF0ZTsgICAgICAgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0aG9tZS5nZXRWaWV3ID0gZ2V0VmlldzsgICAgICAgICAgICAgICAvL3Rlc3QgY29kZVxyXG5cclxuXHRcdHJldHVybiBob21lO1xyXG5cdH1cclxufSgpKTsiLCIvKipcclxuICogRGlyZWN0aXZlcyAoYW5kIGFzc29jaWF0ZWQgYXR0cmlidXRlcykgYXJlIGFsd2F5cyBkZWNsYXJlZCBhcyBjYW1lbENhc2UgaW4gSlMgYW5kIHNuYWtlLWNhc2UgaW4gSFRNTFxyXG4gKiBBbmd1bGFyJ3MgYnVpbHQtaW4gPGE+IGRpcmVjdGl2ZSBhdXRvbWF0aWNhbGx5IGltcGxlbWVudHMgcHJldmVudERlZmF1bHQgb24gbGlua3MgdGhhdCBkb24ndCBoYXZlIGFuIGhyZWYgYXR0cmlidXRlXHJcbiAqIENvbXBsZXggSmF2YVNjcmlwdCBET00gbWFuaXB1bGF0aW9uIHNob3VsZCBhbHdheXMgYmUgZG9uZSBpbiBkaXJlY3RpdmUgbGluayBmdW5jdGlvbnMsIGFuZCAkYXBwbHkgc2hvdWxkIG5ldmVyIGJlIHVzZWQgaW4gYSBjb250cm9sbGVyISBTaW1wbGUgRE9NIG1hbmlwdWxhdGlvbiBzaG91bGQgYmUgaW4gdGhlIHZpZXcuXHJcbiAqL1xyXG5cclxuLyotLS0gU2FtcGxlIERpcmVjdGl2ZSB3aXRoIGEgJHdhdGNoIC0tLSovXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmRpcmVjdGl2ZSgnc2FtcGxlRGlyZWN0aXZlJywgc2FtcGxlRGlyZWN0aXZlKTtcclxuXHJcblx0c2FtcGxlRGlyZWN0aXZlLiRpbmplY3QgPSBbJyR0aW1lb3V0J107XHJcblxyXG5cdGZ1bmN0aW9uIHNhbXBsZURpcmVjdGl2ZSgkdGltZW91dCkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdHJlcGxhY2U6IHRydWUsXHJcblx0XHRcdHNjb3BlOiB7fSxcclxuXHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvc2FtcGxlLnRwbC5odG1sJyxcclxuXHRcdFx0dHJhbnNjbHVkZTogdHJ1ZSxcclxuXHRcdFx0Y29udHJvbGxlcjogU2FtcGxlRGlyZWN0aXZlQ3RybCxcclxuXHRcdFx0Y29udHJvbGxlckFzOiAnc2QnLFxyXG5cdFx0XHRiaW5kVG9Db250cm9sbGVyOiB7XHJcblx0XHRcdFx0anNvbkRhdGE6ICc9J1xyXG5cdFx0XHR9LFxyXG5cdFx0XHRsaW5rOiBzYW1wbGVEaXJlY3RpdmVMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogc2FtcGxlRGlyZWN0aXZlIExJTksgZnVuY3Rpb25cclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJHNjb3BlXHJcblx0XHQgKiBAcGFyYW0gJGVsZW1lbnRcclxuXHRcdCAqIEBwYXJhbSAkYXR0cnNcclxuXHRcdCAqIEBwYXJhbSBzZCB7Y29udHJvbGxlcn1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gc2FtcGxlRGlyZWN0aXZlTGluaygkc2NvcGUsICRlbGVtZW50LCAkYXR0cnMsIHNkKSB7XHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIHdhdGNoIGZvciBhc3luYyBkYXRhIHRvIGJlY29tZSBhdmFpbGFibGUgYW5kIHVwZGF0ZVxyXG5cdFx0XHRcdCRzY29wZS4kd2F0Y2goJ3NkLmpzb25EYXRhJywgXyR3YXRjaEpzb25EYXRhKTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqICR3YXRjaCBmb3Igc2QuanNvbkRhdGEgdG8gYmVjb21lIGF2YWlsYWJsZVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHsqfVxyXG5cdFx0XHQgKiBAcGFyYW0gb2xkVmFsIHsqfVxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gXyR3YXRjaEpzb25EYXRhKG5ld1ZhbCwgb2xkVmFsKSB7XHJcblx0XHRcdFx0aWYgKG5ld1ZhbCkge1xyXG5cdFx0XHRcdFx0c2QuanNvbkRhdGEgPSBuZXdWYWw7XHJcblxyXG5cdFx0XHRcdFx0JHRpbWVvdXQoZnVuY3Rpb24oKSB7XHJcblx0XHRcdFx0XHRcdGNvbnNvbGUubG9nKCdkZW1vbnN0cmF0ZSAkdGltZW91dCBpbmplY3Rpb24gaW4gYSBkaXJlY3RpdmUgbGluayBmdW5jdGlvbicpO1xyXG5cdFx0XHRcdFx0fSwgMTAwMCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fVxyXG5cclxuXHRTYW1wbGVEaXJlY3RpdmVDdHJsLiRpbmplY3QgPSBbXTtcclxuXHQvKipcclxuXHQgKiBzYW1wbGVEaXJlY3RpdmUgQ09OVFJPTExFUlxyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIFNhbXBsZURpcmVjdGl2ZUN0cmwoKSB7XHJcblx0XHR2YXIgc2QgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGNvbnRyb2xsZXIgbG9naWMgZ29lcyBoZXJlXHJcblx0fVxyXG5cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdTdWJDdHJsJywgU3ViQ3RybCk7XHJcblxyXG5cdFN1YkN0cmwuJGluamVjdCA9IFsnVXRpbHMnLCAnUGFnZScsICdyZXNvbHZlTG9jYWxEYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIFN1YkN0cmwoVXRpbHMsIFBhZ2UsIHJlc29sdmVMb2NhbERhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBzdWIgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdHN1Yi50aXRsZSA9ICdTdWJwYWdlJztcclxuXHRcdHN1Yi5nbG9iYWwgPSBVdGlscztcclxuXHRcdHN1Yi5qc29uID0gcmVzb2x2ZUxvY2FsRGF0YTtcclxuXHJcblx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0Ly8gc2V0IHBhZ2UgPHRpdGxlPlxyXG5cdFx0XHRQYWdlLnNldFRpdGxlKHN1Yi50aXRsZSk7XHJcblx0XHR9XHJcblx0fVxyXG59KCkpOyJdLCJzb3VyY2VSb290IjoiL3NvdXJjZS8ifQ== From 9fab205ac98ec780aa74501263cdd21e73b3e30e Mon Sep 17 00:00:00 2001 From: Damian Strong <thewhitewolf079@gmail.com> Date: Thu, 10 Dec 2015 10:08:25 -0500 Subject: [PATCH 3/4] fixed semi-colon in gulpfile --- Gulpfile.js | 2 +- src/reStart-app/reStart-app.js | 423 ++++++++++++++-------------- src/reStart-app/reStart-app.spec.js | 144 +++++----- 3 files changed, 284 insertions(+), 285 deletions(-) diff --git a/Gulpfile.js b/Gulpfile.js index db07116..44f2f0c 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -13,7 +13,7 @@ var gulp = require('gulp'), deleteLines = require('gulp-delete-lines'), Server = require('karma').Server, fpath = require('path'), - child_process = require('child_process'); + child_process = require('child_process'), eslint = require('gulp-eslint'); /** * File paths diff --git a/src/reStart-app/reStart-app.js b/src/reStart-app/reStart-app.js index f7c419f..5d78b68 100644 --- a/src/reStart-app/reStart-app.js +++ b/src/reStart-app/reStart-app.js @@ -212,6 +212,215 @@ } } }()); +(function() { + 'use strict'; + + angular + .module('reStart') + .controller('HeaderCtrl', HeaderCtrl); + + HeaderCtrl.$inject = ['$location', 'JSONData']; + + function HeaderCtrl($location, JSONData) { + // controllerAs ViewModel + var header = this; + + // bindable members + header.indexIsActive = indexIsActive; + header.navIsActive = navIsActive; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // activate controller + _activate(); + } + + /** + * Controller activate + * Get JSON data + * + * @returns {*} + * @private + */ + function _activate() { + // get the data from JSON + return JSONData.getLocalData().then(_getJsonSuccess); + } + + /** + * Successful promise data + * + * @param data {json} + * @private + */ + function _getJsonSuccess(data) { + header.json = data; + return header.json; + } + + /** + * Apply class to index nav if active + * + * @param {string} path + */ + function indexIsActive(path) { + // path should be '/' + return $location.path() === path; + } + + /** + * Apply class to currently active nav item + * + * @param {string} path + */ + function navIsActive(path) { + return $location.path().substr(0, path.length) === path; + } + } + +}()); +(function () { + 'use strict'; + + angular + .module('reStart') + .directive('navControl', navControl); + + navControl.$inject = ['$window', 'resize']; + + function navControl($window, resize) { + // return directive + return { + restrict: 'EA', + link: navControlLink + }; + + /** + * navControl LINK function + * + * @param $scope + */ + function navControlLink($scope) { + // private variables + var _$body = angular.element('body'); + var _layoutCanvas = _$body.find('.layout-canvas'); + var _navOpen; + + // data model + $scope.nav = {}; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // initialize debounced resize + var _rs = resize.init({ + scope: $scope, + resizedFn: _resized, + debounce: 100 + }); + + $scope.$on('$locationChangeStart', _$locationChangeStart); + $scope.$on('enter-mobile', _enterMobile); + $scope.$on('exit-mobile', _exitMobile); + } + + /** + * Resized window (debounced) + * + * @private + */ + function _resized() { + _layoutCanvas.css({ + minHeight: $window.innerHeight + 'px' + }); + } + + /** + * Open mobile navigation + * + * @private + */ + function _openNav() { + _$body + .removeClass('nav-closed') + .addClass('nav-open'); + + _navOpen = true; + } + + /** + * Close mobile navigation + * + * @private + */ + function _closeNav() { + _$body + .removeClass('nav-open') + .addClass('nav-closed'); + + _navOpen = false; + } + + /** + * Toggle nav open/closed + */ + function toggleNav() { + if (!_navOpen) { + _openNav(); + } else { + _closeNav(); + } + } + + /** + * When changing location, close the nav if it's open + */ + function _$locationChangeStart() { + if (_navOpen) { + _closeNav(); + } + } + + /** + * Function to execute when entering mobile media query + * Close nav and set up menu toggling functionality + * + * @private + */ + function _enterMobile(mq) { + _closeNav(); + + // bind function to toggle mobile navigation open/closed + $scope.nav.toggleNav = toggleNav; + } + + /** + * Function to execute when exiting mobile media query + * Disable menu toggling and remove body classes + * + * @private + */ + function _exitMobile(mq) { + // unbind function to toggle mobile navigation open/closed + $scope.nav.toggleNav = null; + + _$body.removeClass('nav-closed nav-open'); + } + } + } + +}()); // application config (function() { 'use strict'; @@ -518,215 +727,6 @@ }; } }()); -(function() { - 'use strict'; - - angular - .module('reStart') - .controller('HeaderCtrl', HeaderCtrl); - - HeaderCtrl.$inject = ['$location', 'JSONData']; - - function HeaderCtrl($location, JSONData) { - // controllerAs ViewModel - var header = this; - - // bindable members - header.indexIsActive = indexIsActive; - header.navIsActive = navIsActive; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // activate controller - _activate(); - } - - /** - * Controller activate - * Get JSON data - * - * @returns {*} - * @private - */ - function _activate() { - // get the data from JSON - return JSONData.getLocalData().then(_getJsonSuccess); - } - - /** - * Successful promise data - * - * @param data {json} - * @private - */ - function _getJsonSuccess(data) { - header.json = data; - return header.json; - } - - /** - * Apply class to index nav if active - * - * @param {string} path - */ - function indexIsActive(path) { - // path should be '/' - return $location.path() === path; - } - - /** - * Apply class to currently active nav item - * - * @param {string} path - */ - function navIsActive(path) { - return $location.path().substr(0, path.length) === path; - } - } - -}()); -(function () { - 'use strict'; - - angular - .module('reStart') - .directive('navControl', navControl); - - navControl.$inject = ['$window', 'resize']; - - function navControl($window, resize) { - // return directive - return { - restrict: 'EA', - link: navControlLink - }; - - /** - * navControl LINK function - * - * @param $scope - */ - function navControlLink($scope) { - // private variables - var _$body = angular.element('body'); - var _layoutCanvas = _$body.find('.layout-canvas'); - var _navOpen; - - // data model - $scope.nav = {}; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // initialize debounced resize - var _rs = resize.init({ - scope: $scope, - resizedFn: _resized, - debounce: 100 - }); - - $scope.$on('$locationChangeStart', _$locationChangeStart); - $scope.$on('enter-mobile', _enterMobile); - $scope.$on('exit-mobile', _exitMobile); - } - - /** - * Resized window (debounced) - * - * @private - */ - function _resized() { - _layoutCanvas.css({ - minHeight: $window.innerHeight + 'px' - }); - } - - /** - * Open mobile navigation - * - * @private - */ - function _openNav() { - _$body - .removeClass('nav-closed') - .addClass('nav-open'); - - _navOpen = true; - } - - /** - * Close mobile navigation - * - * @private - */ - function _closeNav() { - _$body - .removeClass('nav-open') - .addClass('nav-closed'); - - _navOpen = false; - } - - /** - * Toggle nav open/closed - */ - function toggleNav() { - if (!_navOpen) { - _openNav(); - } else { - _closeNav(); - } - } - - /** - * When changing location, close the nav if it's open - */ - function _$locationChangeStart() { - if (_navOpen) { - _closeNav(); - } - } - - /** - * Function to execute when entering mobile media query - * Close nav and set up menu toggling functionality - * - * @private - */ - function _enterMobile(mq) { - _closeNav(); - - // bind function to toggle mobile navigation open/closed - $scope.nav.toggleNav = toggleNav; - } - - /** - * Function to execute when exiting mobile media query - * Disable menu toggling and remove body classes - * - * @private - */ - function _exitMobile(mq) { - // unbind function to toggle mobile navigation open/closed - $scope.nav.toggleNav = null; - - _$body.removeClass('nav-closed nav-open'); - } - } - } - -}()); (function () { 'use strict'; @@ -861,8 +861,7 @@ home.getJsonSucess = _getJsonSuccess; //test code home.activate = _activate; //test code home.getView = getView; //test code - - return home; + return home; //test code } }()); /** @@ -979,4 +978,4 @@ } } }()); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5tb2R1bGUuanMiLCJjb3JlL1BhZ2UuY3RybC5qcyIsImNvcmUvUGFnZS5mYWN0b3J5LmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LmpzIiwiY29yZS9hcHAtc2V0dXAvYXBwLmNvbmZpZy5qcyIsImNvcmUvZ2V0LWRhdGEvSlNPTkRhdGEuZmFjdG9yeS5qcyIsImNvcmUvZ2V0LWRhdGEvUmVzLmZhY3RvcnkuanMiLCJjb3JlL3VpL2xvYWRpbmcuZGlyLmpzIiwiY29yZS91aS9NUS5jb25zdGFudC5qcyIsImNvcmUvdWkvdHJ1c3RBc0hUTUwuZmlsdGVyLmpzIiwibW9kdWxlcy9oZWFkZXIvSGVhZGVyLmN0cmwuanMiLCJtb2R1bGVzL2hlYWRlci9uYXZDb250cm9sLmRpci5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuanMiLCJwYWdlcy9ob21lL0hvbWUuY3RybC5qcyIsInBhZ2VzL3N1Yi9zYW1wbGUuZGlyLmpzIiwicGFnZXMvc3ViL1N1Yi5jdHJsLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDTkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM3SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3JDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQy9KQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDeEVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDdklBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNsQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUN0R0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2xGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJyZVN0YXJ0LWFwcC5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8vIGFwcGxpY2F0aW9uIG1vZHVsZSBzZXR0ZXJcclxuKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcsIFsnbmdSb3V0ZScsICduZ1Jlc291cmNlJywgJ25nU2FuaXRpemUnLCAnbWVkaWFDaGVjaycsICdyZXNpemUnXSk7XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignUGFnZUN0cmwnLCBQYWdlQ3RybCk7XHJcblxyXG5cdFBhZ2VDdHJsLiRpbmplY3QgPSBbJ1BhZ2UnLCAnJHNjb3BlJywgJ01RJywgJ21lZGlhQ2hlY2snLCAnJGxvZyddO1xyXG5cclxuXHRmdW5jdGlvbiBQYWdlQ3RybChQYWdlLCAkc2NvcGUsIE1RLCBtZWRpYUNoZWNrLCAkbG9nKSB7XHJcblx0XHR2YXIgcGFnZSA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcclxuXHRcdHZhciBfaGFuZGxpbmdSb3V0ZUNoYW5nZUVycm9yID0gZmFsc2U7XHJcblx0XHQvLyBTZXQgdXAgZnVuY3Rpb25hbGl0eSB0byBydW4gb24gZW50ZXIvZXhpdCBvZiBtZWRpYSBxdWVyeVxyXG5cdFx0dmFyIF9tYyA9IG1lZGlhQ2hlY2suaW5pdCh7XHJcblx0XHRcdHNjb3BlOiAkc2NvcGUsXHJcblx0XHRcdG1lZGlhOiB7XHJcblx0XHRcdFx0bXE6IE1RLlNNQUxMLFxyXG5cdFx0XHRcdGVudGVyOiBfZW50ZXJNb2JpbGUsXHJcblx0XHRcdFx0ZXhpdDogX2V4aXRNb2JpbGVcclxuXHRcdFx0fSxcclxuXHRcdFx0ZGVib3VuY2U6IDIwMFxyXG5cdFx0fSk7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIGFzc29jaWF0ZSBwYWdlIDx0aXRsZT5cclxuXHRcdFx0cGFnZS5wYWdlVGl0bGUgPSBQYWdlO1xyXG5cclxuXHRcdFx0JHNjb3BlLiRvbignJHJvdXRlQ2hhbmdlU3RhcnQnLCBfcm91dGVDaGFuZ2VTdGFydCk7XHJcblx0XHRcdCRzY29wZS4kb24oJyRyb3V0ZUNoYW5nZVN1Y2Nlc3MnLCBfcm91dGVDaGFuZ2VTdWNjZXNzKTtcclxuXHRcdFx0JHNjb3BlLiRvbignJHJvdXRlQ2hhbmdlRXJyb3InLCBfcm91dGVDaGFuZ2VFcnJvcik7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFbnRlciBtb2JpbGUgbWVkaWEgcXVlcnlcclxuXHRcdCAqICRicm9hZGNhc3QgJ2VudGVyLW1vYmlsZScgZXZlbnRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZW50ZXJNb2JpbGUoKSB7XHJcblx0XHRcdCRzY29wZS4kYnJvYWRjYXN0KCdlbnRlci1tb2JpbGUnKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEV4aXQgbW9iaWxlIG1lZGlhIHF1ZXJ5XHJcblx0XHQgKiAkYnJvYWRjYXN0ICdleGl0LW1vYmlsZScgZXZlbnRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZSgpIHtcclxuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2V4aXQtbW9iaWxlJyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBUdXJuIG9uIGxvYWRpbmcgc3RhdGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfbG9hZGluZ09uKCkge1xyXG5cdFx0XHQkc2NvcGUuJGJyb2FkY2FzdCgnbG9hZGluZy1vbicpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogVHVybiBvZmYgbG9hZGluZyBzdGF0ZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9sb2FkaW5nT2ZmKCkge1xyXG5cdFx0XHQkc2NvcGUuJGJyb2FkY2FzdCgnbG9hZGluZy1vZmYnKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFJvdXRlIGNoYW5nZSBzdGFydCBoYW5kbGVyXHJcblx0XHQgKiBJZiBuZXh0IHJvdXRlIGhhcyByZXNvbHZlLCB0dXJuIG9uIGxvYWRpbmdcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJGV2ZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gbmV4dCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIGN1cnJlbnQge29iamVjdH1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9yb3V0ZUNoYW5nZVN0YXJ0KCRldmVudCwgbmV4dCwgY3VycmVudCkge1xyXG5cdFx0XHRpZiAobmV4dC4kJHJvdXRlICYmIG5leHQuJCRyb3V0ZS5yZXNvbHZlKSB7IC8vIGVzbGludC1kaXNhYmxlLWxpbmUgYW5ndWxhci9uby1wcml2YXRlLWNhbGxcclxuXHRcdFx0XHRfbG9hZGluZ09uKCk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFJvdXRlIGNoYW5nZSBzdWNjZXNzIGhhbmRsZXJcclxuXHRcdCAqIE1hdGNoIGN1cnJlbnQgbWVkaWEgcXVlcnkgYW5kIHJ1biBhcHByb3ByaWF0ZSBmdW5jdGlvblxyXG5cdFx0ICogSWYgY3VycmVudCByb3V0ZSBoYXMgYmVlbiByZXNvbHZlZCwgdHVybiBvZmYgbG9hZGluZ1xyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gcHJldmlvdXMge29iamVjdH1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9yb3V0ZUNoYW5nZVN1Y2Nlc3MoJGV2ZW50LCBjdXJyZW50LCBwcmV2aW91cykge1xyXG5cdFx0XHRfbWMubWF0Y2hDdXJyZW50KE1RLlNNQUxMKTtcclxuXHJcblx0XHRcdGlmIChjdXJyZW50LiQkcm91dGUgJiYgY3VycmVudC4kJHJvdXRlLnJlc29sdmUpIHsgICAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIGFuZ3VsYXIvbm8tcHJpdmF0ZS1jYWxsXHJcblx0XHRcdFx0X2xvYWRpbmdPZmYoKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogUm91dGUgY2hhbmdlIGVycm9yIGhhbmRsZXJcclxuXHRcdCAqIEhhbmRsZSByb3V0ZSByZXNvbHZlIGZhaWx1cmVzXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRldmVudCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIGN1cnJlbnQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBwcmV2aW91cyB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIHJlamVjdGlvbiB7b2JqZWN0fVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX3JvdXRlQ2hhbmdlRXJyb3IoJGV2ZW50LCBjdXJyZW50LCBwcmV2aW91cywgcmVqZWN0aW9uKSB7XHJcblx0XHRcdHZhciBkZXN0aW5hdGlvbiA9IChjdXJyZW50ICYmIChjdXJyZW50LnRpdGxlIHx8IGN1cnJlbnQubmFtZSB8fCBjdXJyZW50LmxvYWRlZFRlbXBsYXRlVXJsKSkgfHwgJ3Vua25vd24gdGFyZ2V0JztcclxuXHRcdFx0dmFyIG1zZyA9ICdFcnJvciByb3V0aW5nIHRvICcgKyBkZXN0aW5hdGlvbiArICcuICcgKyAocmVqZWN0aW9uLm1zZyB8fCAnJyk7XHJcblxyXG5cdFx0XHRpZiAoX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvcikge1xyXG5cdFx0XHRcdHJldHVybjtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0X2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvciA9IHRydWU7XHJcblx0XHRcdF9sb2FkaW5nT2ZmKCk7XHJcblxyXG5cdFx0XHQkbG9nLmVycm9yKG1zZyk7XHJcblx0XHR9XHJcblx0XHRQYWdlQ3RybC5lbnRlck1vYmlsZSA9IF9lbnRlck1vYmlsZTsvL3Rlc3QgY29kZVxyXG5cdFx0UGFnZUN0cmwuZXhpdE1vYmlsZSA9IF9leGl0TW9iaWxlOy8vdGVzdCBjb2RlXHJcblx0XHRQYWdlQ3RybC5sb2FkaW5nT24gPSBfbG9hZGluZ09uOy8vdGVzdCBjb2RlXHJcblx0XHRQYWdlQ3RybC5sb2FkaW5nT2ZmID0gX2xvYWRpbmdPZmY7Ly90ZXN0IGNvZGVcclxuXHRcdHJldHVybiBQYWdlQ3RybDsvL3Rlc3QgY29kZVxyXG5cdH1cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5mYWN0b3J5KCdQYWdlJywgUGFnZSk7XHJcblxyXG5cdGZ1bmN0aW9uIFBhZ2UoKSB7XHJcblx0XHQvLyBwcml2YXRlIHZhcnNcclxuXHRcdHZhciBzaXRlVGl0bGUgPSAncmVTdGFydCBBbmd1bGFyJztcclxuXHRcdHZhciBwYWdlVGl0bGUgPSAnSG9tZSc7XHJcblxyXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0Z2V0VGl0bGU6IGdldFRpdGxlLFxyXG5cdFx0XHRzZXRUaXRsZTogc2V0VGl0bGVcclxuXHRcdH07XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBUaXRsZSBmdW5jdGlvblxyXG5cdFx0ICogU2V0cyBzaXRlIHRpdGxlIGFuZCBwYWdlIHRpdGxlXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMge3N0cmluZ30gc2l0ZSB0aXRsZSArIHBhZ2UgdGl0bGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gZ2V0VGl0bGUoKSB7XHJcblx0XHRcdHJldHVybiBzaXRlVGl0bGUgKyAnIHwgJyArIHBhZ2VUaXRsZTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFNldCBwYWdlIHRpdGxlXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIG5ld1RpdGxlIHtzdHJpbmd9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIHNldFRpdGxlKG5ld1RpdGxlKSB7XHJcblx0XHRcdHBhZ2VUaXRsZSA9IG5ld1RpdGxlO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiLCIvLyBcImdsb2JhbFwiIG9iamVjdCB0byBzaGFyZSBiZXR3ZWVuIGNvbnRyb2xsZXJzXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmZhY3RvcnkoJ1V0aWxzJywgVXRpbHMpO1xyXG5cclxuXHRmdW5jdGlvbiBVdGlscygpIHtcclxuXHRcdHZhciBncmVldGluZyA9ICdIZWxsbyc7XHJcblxyXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0Z3JlZXRpbmc6IGdyZWV0aW5nLFxyXG5cdFx0XHRhbGVydEdyZWV0aW5nOiBhbGVydEdyZWV0aW5nXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQWxlcnQgZ3JlZXRpbmdcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gbmFtZSB7c3RyaW5nfVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBhbGVydEdyZWV0aW5nKG5hbWUpIHtcclxuXHRcdFx0YWxlcnQoZ3JlZXRpbmcgKyAnLCAnICsgbmFtZSArICchJyk7XHJcblx0XHR9XHJcblx0fVxyXG59KCkpOyIsIi8vIGFwcGxpY2F0aW9uIGNvbmZpZ1xyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb25maWcoYXBwQ29uZmlnKTtcclxuXHJcblx0YXBwQ29uZmlnLiRpbmplY3QgPSBbJyRyb3V0ZVByb3ZpZGVyJywgJyRsb2NhdGlvblByb3ZpZGVyJ107XHJcblxyXG5cdGZ1bmN0aW9uIGFwcENvbmZpZygkcm91dGVQcm92aWRlciwgJGxvY2F0aW9uUHJvdmlkZXIpIHtcclxuXHRcdCRyb3V0ZVByb3ZpZGVyXHJcblx0XHRcdC53aGVuKCcvJywge1xyXG5cdFx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvcGFnZXMvaG9tZS9Ib21lLnZpZXcuaHRtbCcsXHJcblx0XHRcdFx0Y29udHJvbGxlcjogJ0hvbWVDdHJsJyxcclxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdob21lJ1xyXG5cdFx0XHR9KVxyXG5cdFx0XHQud2hlbignL3N1YnBhZ2UnLCB7XHJcblx0XHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvU3ViLnZpZXcuaHRtbCcsXHJcblx0XHRcdFx0Y29udHJvbGxlcjogJ1N1YkN0cmwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXJBczogJ3N1YicsXHJcblx0XHRcdFx0cmVzb2x2ZToge1xyXG5cdFx0XHRcdFx0cmVzb2x2ZUxvY2FsRGF0YTogcmVzb2x2ZUxvY2FsRGF0YVxyXG5cdFx0XHRcdH1cclxuXHRcdFx0fSlcclxuXHRcdFx0Lm90aGVyd2lzZSh7XHJcblx0XHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9lcnJvcjQwNC9FcnJvcjQwNC52aWV3Lmh0bWwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdFcnJvcjQwNEN0cmwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXJBczogJ2U0MDQnXHJcblx0XHRcdH0pO1xyXG5cclxuXHRcdCRsb2NhdGlvblByb3ZpZGVyXHJcblx0XHRcdC5odG1sNU1vZGUoe1xyXG5cdFx0XHRcdGVuYWJsZWQ6IHRydWVcclxuXHRcdFx0fSlcclxuXHRcdFx0Lmhhc2hQcmVmaXgoJyEnKTtcclxuXHR9XHJcblxyXG5cdHJlc29sdmVMb2NhbERhdGEuJGluamVjdCA9IFsnSlNPTkRhdGEnXTtcclxuXHQvKipcclxuXHQgKiBHZXQgbG9jYWwgZGF0YSBmb3Igcm91dGUgcmVzb2x2ZVxyXG5cdCAqXHJcblx0ICogQHBhcmFtIEpTT05EYXRhIHtmYWN0b3J5fVxyXG5cdCAqIEByZXR1cm5zIHtwcm9taXNlfSBkYXRhXHJcblx0ICovXHJcblx0ZnVuY3Rpb24gcmVzb2x2ZUxvY2FsRGF0YShKU09ORGF0YSkge1xyXG5cdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpO1xyXG5cdH1cclxufSgpKTsiLCIvLyBmZXRjaCBKU09OIGRhdGEgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5mYWN0b3J5KCdKU09ORGF0YScsIEpTT05EYXRhKTtcclxuXHJcblx0SlNPTkRhdGEuJGluamVjdCA9IFsnJGh0dHAnLCAnUmVzJ107XHJcblxyXG5cdGZ1bmN0aW9uIEpTT05EYXRhKCRodHRwLCBSZXMpIHtcclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdGdldExvY2FsRGF0YTogZ2V0TG9jYWxEYXRhXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogR0VUIGxvY2FsIEpTT04gZGF0YSBmaWxlIGFuZCByZXR1cm4gcmVzdWx0c1xyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHtwcm9taXNlfVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBnZXRMb2NhbERhdGEoKSB7XHJcblx0XHRcdHJldHVybiAkaHR0cFxyXG5cdFx0XHRcdC5nZXQoJy9kYXRhL2RhdGEuanNvbicpXHJcblx0XHRcdFx0LnRoZW4oUmVzLnN1Y2Nlc3MsIFJlcy5lcnJvcik7XHJcblx0XHR9XHJcblx0fVxyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmZhY3RvcnkoJ1JlcycsIFJlcyk7XHJcblxyXG5cdGZ1bmN0aW9uIFJlcygpIHtcclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdHN1Y2Nlc3M6IHN1Y2Nlc3MsXHJcblx0XHRcdGVycm9yOiBlcnJvclxyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFByb21pc2UgcmVzcG9uc2UgZnVuY3Rpb25cclxuXHRcdCAqIENoZWNrcyB0eXBlb2YgZGF0YSByZXR1cm5lZCBhbmQgc3VjY2VlZHMgaWYgSlMgb2JqZWN0LCB0aHJvd3MgZXJyb3IgaWYgbm90XHJcblx0XHQgKiBVc2VmdWwgZm9yIEFQSXMgKGllLCB3aXRoIG5naW54KSB3aGVyZSBzZXJ2ZXIgZXJyb3IgSFRNTCBwYWdlIG1heSBiZSByZXR1cm5lZCBpbiBlcnJvclxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSByZXNwb25zZSB7Kn0gZGF0YSBmcm9tICRodHRwXHJcblx0XHQgKiBAcmV0dXJucyB7Kn0gb2JqZWN0LCBhcnJheVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBzdWNjZXNzKHJlc3BvbnNlKSB7XHJcblx0XHRcdGlmIChhbmd1bGFyLmlzT2JqZWN0KHJlc3BvbnNlLmRhdGEpKSB7XHJcblx0XHRcdFx0cmV0dXJuIHJlc3BvbnNlLmRhdGE7XHJcblx0XHRcdH0gZWxzZSB7XHJcblx0XHRcdFx0dGhyb3cgbmV3IEVycm9yKCdyZXRyaWV2ZWQgZGF0YSBpcyBub3QgdHlwZW9mIG9iamVjdC4nKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogUHJvbWlzZSByZXNwb25zZSBmdW5jdGlvbiAtIGVycm9yXHJcblx0XHQgKiBUaHJvd3MgYW4gZXJyb3Igd2l0aCBlcnJvciBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGVycm9yIHtvYmplY3R9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGVycm9yKGVycm9yKSB7XHJcblx0XHRcdHRocm93IG5ldyBFcnJvcignRXJyb3IgcmV0cmlldmluZyBkYXRhJywgZXJyb3IpO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5kaXJlY3RpdmUoJ2xvYWRpbmcnLCBsb2FkaW5nKTtcclxuXHJcblx0bG9hZGluZy4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xyXG5cclxuXHRmdW5jdGlvbiBsb2FkaW5nKCR3aW5kb3csIHJlc2l6ZSkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdHJlcGxhY2U6IHRydWUsXHJcblx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvY29yZS91aS9sb2FkaW5nLnRwbC5odG1sJyxcclxuXHRcdFx0dHJhbnNjbHVkZTogdHJ1ZSxcclxuXHRcdFx0Y29udHJvbGxlcjogbG9hZGluZ0N0cmwsXHJcblx0XHRcdGNvbnRyb2xsZXJBczogJ2xvYWRpbmcnLFxyXG5cdFx0XHRiaW5kVG9Db250cm9sbGVyOiB0cnVlLFxyXG5cdFx0XHRsaW5rOiBsb2FkaW5nTGlua1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIGxvYWRpbmcgTElOS1xyXG5cdFx0ICogRGlzYWJsZXMgcGFnZSBzY3JvbGxpbmcgd2hlbiBsb2FkaW5nIG92ZXJsYXkgaXMgb3BlblxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkc2NvcGVcclxuXHRcdCAqIEBwYXJhbSAkZWxlbWVudFxyXG5cdFx0ICogQHBhcmFtICRhdHRyc1xyXG5cdFx0ICogQHBhcmFtIGxvYWRpbmcge2NvbnRyb2xsZXJ9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGxvYWRpbmdMaW5rKCRzY29wZSwgJGVsZW1lbnQsICRhdHRycywgbG9hZGluZykge1xyXG5cdFx0XHQvLyBwcml2YXRlIHZhcmlhYmxlc1xyXG5cdFx0XHR2YXIgXyRib2R5ID0gYW5ndWxhci5lbGVtZW50KCdib2R5Jyk7XHJcblx0XHRcdHZhciBfd2luSGVpZ2h0ID0gJHdpbmRvdy5pbm5lckhlaWdodCArICdweCc7XHJcblxyXG5cdFx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0XHQvLyBpbml0aWFsaXplIGRlYm91bmNlZCByZXNpemVcclxuXHRcdFx0XHR2YXIgX3JzID0gcmVzaXplLmluaXQoe1xyXG5cdFx0XHRcdFx0c2NvcGU6ICRzY29wZSxcclxuXHRcdFx0XHRcdHJlc2l6ZWRGbjogX3Jlc2l6ZWQsXHJcblx0XHRcdFx0XHRkZWJvdW5jZTogMjAwXHJcblx0XHRcdFx0fSk7XHJcblxyXG5cdFx0XHRcdC8vICR3YXRjaCBhY3RpdmUgc3RhdGVcclxuXHRcdFx0XHQkc2NvcGUuJHdhdGNoKCdsb2FkaW5nLmFjdGl2ZScsIF8kd2F0Y2hBY3RpdmUpO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogV2luZG93IHJlc2l6ZWRcclxuXHRcdFx0ICogSWYgbG9hZGluZywgcmVhcHBseSBib2R5IGhlaWdodFxyXG5cdFx0XHQgKiB0byBwcmV2ZW50IHNjcm9sbGJhclxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX3Jlc2l6ZWQoKSB7XHJcblx0XHRcdFx0X3dpbkhlaWdodCA9ICR3aW5kb3cuaW5uZXJIZWlnaHQgKyAncHgnO1xyXG5cclxuXHRcdFx0XHRpZiAobG9hZGluZy5hY3RpdmUpIHtcclxuXHRcdFx0XHRcdF8kYm9keS5jc3Moe1xyXG5cdFx0XHRcdFx0XHRoZWlnaHQ6IF93aW5IZWlnaHQsXHJcblx0XHRcdFx0XHRcdG92ZXJmbG93WTogJ2hpZGRlbidcclxuXHRcdFx0XHRcdH0pO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqICR3YXRjaCBsb2FkaW5nLmFjdGl2ZVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHtib29sZWFufVxyXG5cdFx0XHQgKiBAcGFyYW0gb2xkVmFsIHt1bmRlZmluZWR8Ym9vbGVhbn1cclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF8kd2F0Y2hBY3RpdmUobmV3VmFsLCBvbGRWYWwpIHtcclxuXHRcdFx0XHRpZiAobmV3VmFsKSB7XHJcblx0XHRcdFx0XHRfb3BlbigpO1xyXG5cdFx0XHRcdH0gZWxzZSB7XHJcblx0XHRcdFx0XHRfY2xvc2UoKTtcclxuXHRcdFx0XHR9XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBPcGVuIGxvYWRpbmdcclxuXHRcdFx0ICogRGlzYWJsZSBzY3JvbGxcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9vcGVuKCkge1xyXG5cdFx0XHRcdF8kYm9keS5jc3Moe1xyXG5cdFx0XHRcdFx0aGVpZ2h0OiBfd2luSGVpZ2h0LFxyXG5cdFx0XHRcdFx0b3ZlcmZsb3dZOiAnaGlkZGVuJ1xyXG5cdFx0XHRcdH0pO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogQ2xvc2UgbG9hZGluZ1xyXG5cdFx0XHQgKiBFbmFibGUgc2Nyb2xsXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfY2xvc2UoKSB7XHJcblx0XHRcdFx0XyRib2R5LmNzcyh7XHJcblx0XHRcdFx0XHRoZWlnaHQ6ICdhdXRvJyxcclxuXHRcdFx0XHRcdG92ZXJmbG93WTogJ2F1dG8nXHJcblx0XHRcdFx0fSk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHR9XHJcblxyXG5cdGxvYWRpbmdDdHJsLiRpbmplY3QgPSBbJyRzY29wZSddO1xyXG5cdC8qKlxyXG5cdCAqIGxvYWRpbmcgQ09OVFJPTExFUlxyXG5cdCAqIFVwZGF0ZSB0aGUgbG9hZGluZyBzdGF0dXMgYmFzZWRcclxuXHQgKiBvbiByb3V0ZUNoYW5nZSBzdGF0ZVxyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIGxvYWRpbmdDdHJsKCRzY29wZSkge1xyXG5cdFx0dmFyIGxvYWRpbmcgPSB0aGlzO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyB0dXJuIG9uIGxvYWRpbmcgZm9yIGluaXRpYWwgcGFnZSBsb2FkXHJcblx0XHRcdF9sb2FkaW5nQWN0aXZlKCk7XHJcblxyXG5cdFx0XHQkc2NvcGUuJG9uKCdsb2FkaW5nLW9uJywgX2xvYWRpbmdBY3RpdmUpO1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdsb2FkaW5nLW9mZicsIF9sb2FkaW5nSW5hY3RpdmUpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogU2V0IGxvYWRpbmcgdG8gYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdBY3RpdmUoKSB7XHJcblx0XHRcdGxvYWRpbmcuYWN0aXZlID0gdHJ1ZTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFNldCBsb2FkaW5nIHRvIGluYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdJbmFjdGl2ZSgpIHtcclxuXHRcdFx0bG9hZGluZy5hY3RpdmUgPSBmYWxzZTtcclxuXHRcdH1cclxuXHR9XHJcblxyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdC8vIG1lZGlhIHF1ZXJ5IGNvbnN0YW50c1xyXG5cdHZhciBNUSA9IHtcclxuXHRcdFNNQUxMOiAnKG1heC13aWR0aDogNzY3cHgpJyxcclxuXHRcdExBUkdFOiAnKG1pbi13aWR0aDogNzY4cHgpJ1xyXG5cdH07XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnN0YW50KCdNUScsIE1RKTtcclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5maWx0ZXIoJ3RydXN0QXNIVE1MJywgdHJ1c3RBc0hUTUwpO1xyXG5cclxuXHR0cnVzdEFzSFRNTC4kaW5qZWN0ID0gWyckc2NlJ107XHJcblxyXG5cdGZ1bmN0aW9uIHRydXN0QXNIVE1MKCRzY2UpIHtcclxuXHRcdHJldHVybiBmdW5jdGlvbih0ZXh0KSB7XHJcblx0XHRcdHJldHVybiAkc2NlLnRydXN0QXNIdG1sKHRleHQpO1xyXG5cdFx0fTtcclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignSGVhZGVyQ3RybCcsIEhlYWRlckN0cmwpO1xyXG5cclxuXHRIZWFkZXJDdHJsLiRpbmplY3QgPSBbJyRsb2NhdGlvbicsICdKU09ORGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBIZWFkZXJDdHJsKCRsb2NhdGlvbiwgSlNPTkRhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBoZWFkZXIgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGhlYWRlci5pbmRleElzQWN0aXZlID0gaW5kZXhJc0FjdGl2ZTtcclxuXHRcdGhlYWRlci5uYXZJc0FjdGl2ZSA9IG5hdklzQWN0aXZlO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBhY3RpdmF0ZSBjb250cm9sbGVyXHJcblx0XHRcdF9hY3RpdmF0ZSgpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQ29udHJvbGxlciBhY3RpdmF0ZVxyXG5cdFx0ICogR2V0IEpTT04gZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHsqfVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2FjdGl2YXRlKCkge1xyXG5cdFx0XHQvLyBnZXQgdGhlIGRhdGEgZnJvbSBKU09OXHJcblx0XHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKS50aGVuKF9nZXRKc29uU3VjY2Vzcyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTdWNjZXNzZnVsIHByb21pc2UgZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSBkYXRhIHtqc29ufVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2dldEpzb25TdWNjZXNzKGRhdGEpIHtcclxuXHRcdFx0aGVhZGVyLmpzb24gPSBkYXRhO1xyXG5cdFx0XHRyZXR1cm4gaGVhZGVyLmpzb247XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBBcHBseSBjbGFzcyB0byBpbmRleCBuYXYgaWYgYWN0aXZlXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIHtzdHJpbmd9IHBhdGhcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gaW5kZXhJc0FjdGl2ZShwYXRoKSB7XHJcblx0XHRcdC8vIHBhdGggc2hvdWxkIGJlICcvJ1xyXG5cdFx0XHRyZXR1cm4gJGxvY2F0aW9uLnBhdGgoKSA9PT0gcGF0aDtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFwcGx5IGNsYXNzIHRvIGN1cnJlbnRseSBhY3RpdmUgbmF2IGl0ZW1cclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0ge3N0cmluZ30gcGF0aFxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBuYXZJc0FjdGl2ZShwYXRoKSB7XHJcblx0XHRcdHJldHVybiAkbG9jYXRpb24ucGF0aCgpLnN1YnN0cigwLCBwYXRoLmxlbmd0aCkgPT09IHBhdGg7XHJcblx0XHR9XHJcblx0fVxyXG5cclxufSgpKTsiLCIoZnVuY3Rpb24gKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuZGlyZWN0aXZlKCduYXZDb250cm9sJywgbmF2Q29udHJvbCk7XHJcblxyXG5cdG5hdkNvbnRyb2wuJGluamVjdCA9IFsnJHdpbmRvdycsICdyZXNpemUnXTtcclxuXHJcblx0ZnVuY3Rpb24gbmF2Q29udHJvbCgkd2luZG93LCByZXNpemUpIHtcclxuXHRcdC8vIHJldHVybiBkaXJlY3RpdmVcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdHJlc3RyaWN0OiAnRUEnLFxyXG5cdFx0XHRsaW5rOiBuYXZDb250cm9sTGlua1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIG5hdkNvbnRyb2wgTElOSyBmdW5jdGlvblxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkc2NvcGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gbmF2Q29udHJvbExpbmsoJHNjb3BlKSB7XHJcblx0XHRcdC8vIHByaXZhdGUgdmFyaWFibGVzXHJcblx0XHRcdHZhciBfJGJvZHkgPSBhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKTtcclxuXHRcdFx0dmFyIF9sYXlvdXRDYW52YXMgPSBfJGJvZHkuZmluZCgnLmxheW91dC1jYW52YXMnKTtcclxuXHRcdFx0dmFyIF9uYXZPcGVuO1xyXG5cclxuXHRcdFx0Ly8gZGF0YSBtb2RlbFxyXG5cdFx0XHQkc2NvcGUubmF2ID0ge307XHJcblxyXG5cdFx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0XHQvLyBpbml0aWFsaXplIGRlYm91bmNlZCByZXNpemVcclxuXHRcdFx0XHR2YXIgX3JzID0gcmVzaXplLmluaXQoe1xyXG5cdFx0XHRcdFx0c2NvcGU6ICRzY29wZSxcclxuXHRcdFx0XHRcdHJlc2l6ZWRGbjogX3Jlc2l6ZWQsXHJcblx0XHRcdFx0XHRkZWJvdW5jZTogMTAwXHJcblx0XHRcdFx0fSk7XHJcblxyXG5cdFx0XHRcdCRzY29wZS4kb24oJyRsb2NhdGlvbkNoYW5nZVN0YXJ0JywgXyRsb2NhdGlvbkNoYW5nZVN0YXJ0KTtcclxuXHRcdFx0XHQkc2NvcGUuJG9uKCdlbnRlci1tb2JpbGUnLCBfZW50ZXJNb2JpbGUpO1xyXG5cdFx0XHRcdCRzY29wZS4kb24oJ2V4aXQtbW9iaWxlJywgX2V4aXRNb2JpbGUpO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogUmVzaXplZCB3aW5kb3cgKGRlYm91bmNlZClcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9yZXNpemVkKCkge1xyXG5cdFx0XHRcdF9sYXlvdXRDYW52YXMuY3NzKHtcclxuXHRcdFx0XHRcdG1pbkhlaWdodDogJHdpbmRvdy5pbm5lckhlaWdodCArICdweCdcclxuXHRcdFx0XHR9KTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIE9wZW4gbW9iaWxlIG5hdmlnYXRpb25cclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9vcGVuTmF2KCkge1xyXG5cdFx0XHRcdF8kYm9keVxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduYXYtY2xvc2VkJylcclxuXHRcdFx0XHRcdC5hZGRDbGFzcygnbmF2LW9wZW4nKTtcclxuXHJcblx0XHRcdFx0X25hdk9wZW4gPSB0cnVlO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogQ2xvc2UgbW9iaWxlIG5hdmlnYXRpb25cclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9jbG9zZU5hdigpIHtcclxuXHRcdFx0XHRfJGJvZHlcclxuXHRcdFx0XHRcdC5yZW1vdmVDbGFzcygnbmF2LW9wZW4nKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCduYXYtY2xvc2VkJyk7XHJcblxyXG5cdFx0XHRcdF9uYXZPcGVuID0gZmFsc2U7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBUb2dnbGUgbmF2IG9wZW4vY2xvc2VkXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiB0b2dnbGVOYXYoKSB7XHJcblx0XHRcdFx0aWYgKCFfbmF2T3Blbikge1xyXG5cdFx0XHRcdFx0X29wZW5OYXYoKTtcclxuXHRcdFx0XHR9IGVsc2Uge1xyXG5cdFx0XHRcdFx0X2Nsb3NlTmF2KCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogV2hlbiBjaGFuZ2luZyBsb2NhdGlvbiwgY2xvc2UgdGhlIG5hdiBpZiBpdCdzIG9wZW5cclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF8kbG9jYXRpb25DaGFuZ2VTdGFydCgpIHtcclxuXHRcdFx0XHRpZiAoX25hdk9wZW4pIHtcclxuXHRcdFx0XHRcdF9jbG9zZU5hdigpO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIEZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2hlbiBlbnRlcmluZyBtb2JpbGUgbWVkaWEgcXVlcnlcclxuXHRcdFx0ICogQ2xvc2UgbmF2IGFuZCBzZXQgdXAgbWVudSB0b2dnbGluZyBmdW5jdGlvbmFsaXR5XHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfZW50ZXJNb2JpbGUobXEpIHtcclxuXHRcdFx0XHRfY2xvc2VOYXYoKTtcclxuXHJcblx0XHRcdFx0Ly8gYmluZCBmdW5jdGlvbiB0byB0b2dnbGUgbW9iaWxlIG5hdmlnYXRpb24gb3Blbi9jbG9zZWRcclxuXHRcdFx0XHQkc2NvcGUubmF2LnRvZ2dsZU5hdiA9IHRvZ2dsZU5hdjtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIEZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2hlbiBleGl0aW5nIG1vYmlsZSBtZWRpYSBxdWVyeVxyXG5cdFx0XHQgKiBEaXNhYmxlIG1lbnUgdG9nZ2xpbmcgYW5kIHJlbW92ZSBib2R5IGNsYXNzZXNcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9leGl0TW9iaWxlKG1xKSB7XHJcblx0XHRcdFx0Ly8gdW5iaW5kIGZ1bmN0aW9uIHRvIHRvZ2dsZSBtb2JpbGUgbmF2aWdhdGlvbiBvcGVuL2Nsb3NlZFxyXG5cdFx0XHRcdCRzY29wZS5uYXYudG9nZ2xlTmF2ID0gbnVsbDtcclxuXHJcblx0XHRcdFx0XyRib2R5LnJlbW92ZUNsYXNzKCduYXYtY2xvc2VkIG5hdi1vcGVuJyk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHR9XHJcblxyXG59KCkpOyIsIihmdW5jdGlvbiAoKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdFcnJvcjQwNEN0cmwnLCBFcnJvcjQwNEN0cmwpO1xyXG5cclxuXHRFcnJvcjQwNEN0cmwuJGluamVjdCA9IFsnJHNjb3BlJywgJ1BhZ2UnXTtcclxuXHJcblx0ZnVuY3Rpb24gRXJyb3I0MDRDdHJsKCRzY29wZSwgUGFnZSkge1xyXG5cdFx0dmFyIGU0MDQgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGU0MDQudGl0bGUgPSAnNDA0IC0gUGFnZSBOb3QgRm91bmQnO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBzZXQgcGFnZSA8dGl0bGU+XHJcblx0XHRcdFBhZ2Uuc2V0VGl0bGUoZTQwNC50aXRsZSk7XHJcblxyXG5cdFx0XHQvLyBubyBkYXRhIHRvIGxvYWQsIGJ1dCBsb2FkaW5nIHN0YXRlIG1pZ2h0IGJlIG9uXHJcblx0XHRcdCRzY29wZS4kZW1pdCgnbG9hZGluZy1vZmYnKTtcclxuXHRcdH1cclxuXHJcblx0XHRyZXR1cm4geyAgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdFx0aW5pdDogX2luaXQgLy90ZXN0IGNvZGVcclxuXHRcdH07ICAgICAgICAgICAgICAgLy90ZXN0IGNvZGVcclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uICgpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnRyb2xsZXIoJ0hvbWVDdHJsJywgSG9tZUN0cmwpO1xyXG5cclxuXHRIb21lQ3RybC4kaW5qZWN0ID0gWyckc2NvcGUnLCAnVXRpbHMnLCAnUGFnZScsICdKU09ORGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBIb21lQ3RybCgkc2NvcGUsIFV0aWxzLCBQYWdlLCBKU09ORGF0YSkge1xyXG5cdFx0Ly8gY29udHJvbGxlckFzIFZpZXdNb2RlbFxyXG5cdFx0dmFyIGhvbWUgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGhvbWUudGl0bGUgPSAnSG9tZSc7XHJcblx0XHRob21lLmdsb2JhbCA9IFV0aWxzO1xyXG5cdFx0aG9tZS5uYW1lID0gJ1Zpc2l0b3InO1xyXG5cdFx0aG9tZS5hbGVydEdyZWV0aW5nID0gVXRpbHMuYWxlcnRHcmVldGluZztcclxuXHRcdGhvbWUuc3RyaW5nT2ZIVE1MID0gJzxzdHJvbmcgc3R5bGU9XCJjb2xvcjogZ3JlZW47XCI+U29tZSBncmVlbiB0ZXh0PC9zdHJvbmc+IGJvdW5kIGFzIEhUTUwgd2l0aCBhIDxhIGhyZWY9XCIjXCI+bGluazwvYT4sIHRydXN0ZWQgd2l0aCBTQ0UhJztcclxuXHRcdGhvbWUudmlld2Zvcm1hdCA9IG51bGw7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cclxuXHRcdFx0UGFnZS5zZXRUaXRsZShob21lLnRpdGxlKTtcclxuXHJcblx0XHRcdC8vIGFjdGl2YXRlIGNvbnRyb2xsZXJcclxuXHRcdFx0X2FjdGl2YXRlKCk7XHJcblxyXG5cdFx0XHQvLyBtZWRpYXF1ZXJ5IGV2ZW50c1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdlbnRlci1tb2JpbGUnLCBfZW50ZXJNb2JpbGUpO1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdleGl0LW1vYmlsZScsIF9leGl0TW9iaWxlKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIENvbnRyb2xsZXIgYWN0aXZhdGVcclxuXHRcdCAqIEdldCBKU09OIGRhdGFcclxuXHRcdCAqXHJcblx0XHQgKiBAcmV0dXJucyB7Kn1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9hY3RpdmF0ZSgpIHtcclxuXHRcdFx0Ly8gc3RhcnQgbG9hZGluZ1xyXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb24nKTtcclxuXHJcblx0XHRcdC8vIGdldCB0aGUgZGF0YSBmcm9tIEpTT05cclxuXHRcdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpLnRoZW4oX2dldEpzb25TdWNjZXNzKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFN1Y2Nlc3NmdWwgcHJvbWlzZSBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGRhdGEge2pzb259XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZ2V0SnNvblN1Y2Nlc3MoZGF0YSkge1xyXG5cdFx0XHRob21lLmpzb24gPSBkYXRhO1xyXG5cclxuXHRcdFx0Ly8gc3RvcCBsb2FkaW5nXHJcblx0XHRcdCRzY29wZS4kZW1pdCgnbG9hZGluZy1vZmYnKTtcclxuXHJcblx0XHRcdHJldHVybiBob21lLmpzb247XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFbnRlciBzbWFsbCBtcVxyXG5cdFx0ICogU2V0IGhvbWUudmlld2Zvcm1hdFxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9lbnRlck1vYmlsZSgpIHtcclxuXHRcdFx0aG9tZS52aWV3Zm9ybWF0ID0gJ3NtYWxsJztcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEV4aXQgc21hbGwgbXFcclxuXHRcdCAqIFNldCBob21lLnZpZXdmb3JtYXRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZSgpIHtcclxuXHRcdFx0aG9tZS52aWV3Zm9ybWF0ID0gJ2xhcmdlJztcclxuXHRcdH1cclxuXHJcblx0XHRmdW5jdGlvbiBnZXRWaWV3KCkgeyAgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0XHRyZXR1cm4gaG9tZS52aWV3Zm9ybWF0OyAvL3Rlc3QgY29kZVxyXG5cdFx0fSAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vdGVzdCBjb2RlXHJcblxyXG5cdFx0aG9tZS5lbnRlck1vYmlsZSA9IF9lbnRlck1vYmlsZTsgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0aG9tZS5leGl0TW9iaWxlID0gX2V4aXRNb2JpbGU7ICAgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0aG9tZS5nZXRKc29uU3VjZXNzID0gX2dldEpzb25TdWNjZXNzOyAvL3Rlc3QgY29kZVxyXG5cdFx0aG9tZS5hY3RpdmF0ZSA9IF9hY3RpdmF0ZTsgICAgICAgICAgICAvL3Rlc3QgY29kZVxyXG5cdFx0aG9tZS5nZXRWaWV3ID0gZ2V0VmlldzsgICAgICAgICAgICAgICAvL3Rlc3QgY29kZVxyXG5cclxuXHRcdHJldHVybiBob21lO1xyXG5cdH1cclxufSgpKTsiLCIvKipcclxuICogRGlyZWN0aXZlcyAoYW5kIGFzc29jaWF0ZWQgYXR0cmlidXRlcykgYXJlIGFsd2F5cyBkZWNsYXJlZCBhcyBjYW1lbENhc2UgaW4gSlMgYW5kIHNuYWtlLWNhc2UgaW4gSFRNTFxyXG4gKiBBbmd1bGFyJ3MgYnVpbHQtaW4gPGE+IGRpcmVjdGl2ZSBhdXRvbWF0aWNhbGx5IGltcGxlbWVudHMgcHJldmVudERlZmF1bHQgb24gbGlua3MgdGhhdCBkb24ndCBoYXZlIGFuIGhyZWYgYXR0cmlidXRlXHJcbiAqIENvbXBsZXggSmF2YVNjcmlwdCBET00gbWFuaXB1bGF0aW9uIHNob3VsZCBhbHdheXMgYmUgZG9uZSBpbiBkaXJlY3RpdmUgbGluayBmdW5jdGlvbnMsIGFuZCAkYXBwbHkgc2hvdWxkIG5ldmVyIGJlIHVzZWQgaW4gYSBjb250cm9sbGVyISBTaW1wbGUgRE9NIG1hbmlwdWxhdGlvbiBzaG91bGQgYmUgaW4gdGhlIHZpZXcuXHJcbiAqL1xyXG5cclxuLyotLS0gU2FtcGxlIERpcmVjdGl2ZSB3aXRoIGEgJHdhdGNoIC0tLSovXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmRpcmVjdGl2ZSgnc2FtcGxlRGlyZWN0aXZlJywgc2FtcGxlRGlyZWN0aXZlKTtcclxuXHJcblx0c2FtcGxlRGlyZWN0aXZlLiRpbmplY3QgPSBbJyR0aW1lb3V0J107XHJcblxyXG5cdGZ1bmN0aW9uIHNhbXBsZURpcmVjdGl2ZSgkdGltZW91dCkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdHJlcGxhY2U6IHRydWUsXHJcblx0XHRcdHNjb3BlOiB7fSxcclxuXHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvc2FtcGxlLnRwbC5odG1sJyxcclxuXHRcdFx0dHJhbnNjbHVkZTogdHJ1ZSxcclxuXHRcdFx0Y29udHJvbGxlcjogU2FtcGxlRGlyZWN0aXZlQ3RybCxcclxuXHRcdFx0Y29udHJvbGxlckFzOiAnc2QnLFxyXG5cdFx0XHRiaW5kVG9Db250cm9sbGVyOiB7XHJcblx0XHRcdFx0anNvbkRhdGE6ICc9J1xyXG5cdFx0XHR9LFxyXG5cdFx0XHRsaW5rOiBzYW1wbGVEaXJlY3RpdmVMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogc2FtcGxlRGlyZWN0aXZlIExJTksgZnVuY3Rpb25cclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJHNjb3BlXHJcblx0XHQgKiBAcGFyYW0gJGVsZW1lbnRcclxuXHRcdCAqIEBwYXJhbSAkYXR0cnNcclxuXHRcdCAqIEBwYXJhbSBzZCB7Y29udHJvbGxlcn1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gc2FtcGxlRGlyZWN0aXZlTGluaygkc2NvcGUsICRlbGVtZW50LCAkYXR0cnMsIHNkKSB7XHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIHdhdGNoIGZvciBhc3luYyBkYXRhIHRvIGJlY29tZSBhdmFpbGFibGUgYW5kIHVwZGF0ZVxyXG5cdFx0XHRcdCRzY29wZS4kd2F0Y2goJ3NkLmpzb25EYXRhJywgXyR3YXRjaEpzb25EYXRhKTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqICR3YXRjaCBmb3Igc2QuanNvbkRhdGEgdG8gYmVjb21lIGF2YWlsYWJsZVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHsqfVxyXG5cdFx0XHQgKiBAcGFyYW0gb2xkVmFsIHsqfVxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gXyR3YXRjaEpzb25EYXRhKG5ld1ZhbCwgb2xkVmFsKSB7XHJcblx0XHRcdFx0aWYgKG5ld1ZhbCkge1xyXG5cdFx0XHRcdFx0c2QuanNvbkRhdGEgPSBuZXdWYWw7XHJcblxyXG5cdFx0XHRcdFx0JHRpbWVvdXQoZnVuY3Rpb24oKSB7XHJcblx0XHRcdFx0XHRcdGNvbnNvbGUubG9nKCdkZW1vbnN0cmF0ZSAkdGltZW91dCBpbmplY3Rpb24gaW4gYSBkaXJlY3RpdmUgbGluayBmdW5jdGlvbicpO1xyXG5cdFx0XHRcdFx0fSwgMTAwMCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fVxyXG5cclxuXHRTYW1wbGVEaXJlY3RpdmVDdHJsLiRpbmplY3QgPSBbXTtcclxuXHQvKipcclxuXHQgKiBzYW1wbGVEaXJlY3RpdmUgQ09OVFJPTExFUlxyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIFNhbXBsZURpcmVjdGl2ZUN0cmwoKSB7XHJcblx0XHR2YXIgc2QgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGNvbnRyb2xsZXIgbG9naWMgZ29lcyBoZXJlXHJcblx0fVxyXG5cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdTdWJDdHJsJywgU3ViQ3RybCk7XHJcblxyXG5cdFN1YkN0cmwuJGluamVjdCA9IFsnVXRpbHMnLCAnUGFnZScsICdyZXNvbHZlTG9jYWxEYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIFN1YkN0cmwoVXRpbHMsIFBhZ2UsIHJlc29sdmVMb2NhbERhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBzdWIgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdHN1Yi50aXRsZSA9ICdTdWJwYWdlJztcclxuXHRcdHN1Yi5nbG9iYWwgPSBVdGlscztcclxuXHRcdHN1Yi5qc29uID0gcmVzb2x2ZUxvY2FsRGF0YTtcclxuXHJcblx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0Ly8gc2V0IHBhZ2UgPHRpdGxlPlxyXG5cdFx0XHRQYWdlLnNldFRpdGxlKHN1Yi50aXRsZSk7XHJcblx0XHR9XHJcblx0fVxyXG59KCkpOyJdLCJzb3VyY2VSb290IjoiL3NvdXJjZS8ifQ== +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5tb2R1bGUuanMiLCJjb3JlL1BhZ2UuY3RybC5qcyIsImNvcmUvUGFnZS5mYWN0b3J5LmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LmpzIiwibW9kdWxlcy9oZWFkZXIvSGVhZGVyLmN0cmwuanMiLCJtb2R1bGVzL2hlYWRlci9uYXZDb250cm9sLmRpci5qcyIsImNvcmUvYXBwLXNldHVwL2FwcC5jb25maWcuanMiLCJjb3JlL2dldC1kYXRhL0pTT05EYXRhLmZhY3RvcnkuanMiLCJjb3JlL2dldC1kYXRhL1Jlcy5mYWN0b3J5LmpzIiwiY29yZS91aS9sb2FkaW5nLmRpci5qcyIsImNvcmUvdWkvTVEuY29uc3RhbnQuanMiLCJjb3JlL3VpL3RydXN0QXNIVE1MLmZpbHRlci5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuanMiLCJwYWdlcy9ob21lL0hvbWUuY3RybC5qcyIsInBhZ2VzL3N1Yi9zYW1wbGUuZGlyLmpzIiwicGFnZXMvc3ViL1N1Yi5jdHJsLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDTkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM3SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3JDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUN4RUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUN2SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQy9KQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNsQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDckdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNsRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoicmVTdGFydC1hcHAuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBhcHBsaWNhdGlvbiBtb2R1bGUgc2V0dGVyXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnLCBbJ25nUm91dGUnLCAnbmdSZXNvdXJjZScsICduZ1Nhbml0aXplJywgJ21lZGlhQ2hlY2snLCAncmVzaXplJ10pO1xyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnRyb2xsZXIoJ1BhZ2VDdHJsJywgUGFnZUN0cmwpO1xyXG5cclxuXHRQYWdlQ3RybC4kaW5qZWN0ID0gWydQYWdlJywgJyRzY29wZScsICdNUScsICdtZWRpYUNoZWNrJywgJyRsb2cnXTtcclxuXHJcblx0ZnVuY3Rpb24gUGFnZUN0cmwoUGFnZSwgJHNjb3BlLCBNUSwgbWVkaWFDaGVjaywgJGxvZykge1xyXG5cdFx0dmFyIHBhZ2UgPSB0aGlzO1xyXG5cclxuXHRcdC8vIHByaXZhdGUgdmFyaWFibGVzXHJcblx0XHR2YXIgX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvciA9IGZhbHNlO1xyXG5cdFx0Ly8gU2V0IHVwIGZ1bmN0aW9uYWxpdHkgdG8gcnVuIG9uIGVudGVyL2V4aXQgb2YgbWVkaWEgcXVlcnlcclxuXHRcdHZhciBfbWMgPSBtZWRpYUNoZWNrLmluaXQoe1xyXG5cdFx0XHRzY29wZTogJHNjb3BlLFxyXG5cdFx0XHRtZWRpYToge1xyXG5cdFx0XHRcdG1xOiBNUS5TTUFMTCxcclxuXHRcdFx0XHRlbnRlcjogX2VudGVyTW9iaWxlLFxyXG5cdFx0XHRcdGV4aXQ6IF9leGl0TW9iaWxlXHJcblx0XHRcdH0sXHJcblx0XHRcdGRlYm91bmNlOiAyMDBcclxuXHRcdH0pO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBhc3NvY2lhdGUgcGFnZSA8dGl0bGU+XHJcblx0XHRcdHBhZ2UucGFnZVRpdGxlID0gUGFnZTtcclxuXHJcblx0XHRcdCRzY29wZS4kb24oJyRyb3V0ZUNoYW5nZVN0YXJ0JywgX3JvdXRlQ2hhbmdlU3RhcnQpO1xyXG5cdFx0XHQkc2NvcGUuJG9uKCckcm91dGVDaGFuZ2VTdWNjZXNzJywgX3JvdXRlQ2hhbmdlU3VjY2Vzcyk7XHJcblx0XHRcdCRzY29wZS4kb24oJyRyb3V0ZUNoYW5nZUVycm9yJywgX3JvdXRlQ2hhbmdlRXJyb3IpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogRW50ZXIgbW9iaWxlIG1lZGlhIHF1ZXJ5XHJcblx0XHQgKiAkYnJvYWRjYXN0ICdlbnRlci1tb2JpbGUnIGV2ZW50XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2VudGVyTW9iaWxlKCkge1xyXG5cdFx0XHQkc2NvcGUuJGJyb2FkY2FzdCgnZW50ZXItbW9iaWxlJyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFeGl0IG1vYmlsZSBtZWRpYSBxdWVyeVxyXG5cdFx0ICogJGJyb2FkY2FzdCAnZXhpdC1tb2JpbGUnIGV2ZW50XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2V4aXRNb2JpbGUoKSB7XHJcblx0XHRcdCRzY29wZS4kYnJvYWRjYXN0KCdleGl0LW1vYmlsZScpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogVHVybiBvbiBsb2FkaW5nIHN0YXRlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdPbigpIHtcclxuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2xvYWRpbmctb24nKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFR1cm4gb2ZmIGxvYWRpbmcgc3RhdGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfbG9hZGluZ09mZigpIHtcclxuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2xvYWRpbmctb2ZmJyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBSb3V0ZSBjaGFuZ2Ugc3RhcnQgaGFuZGxlclxyXG5cdFx0ICogSWYgbmV4dCByb3V0ZSBoYXMgcmVzb2x2ZSwgdHVybiBvbiBsb2FkaW5nXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRldmVudCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIG5leHQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfcm91dGVDaGFuZ2VTdGFydCgkZXZlbnQsIG5leHQsIGN1cnJlbnQpIHtcclxuXHRcdFx0aWYgKG5leHQuJCRyb3V0ZSAmJiBuZXh0LiQkcm91dGUucmVzb2x2ZSkgeyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIGFuZ3VsYXIvbm8tcHJpdmF0ZS1jYWxsXHJcblx0XHRcdFx0X2xvYWRpbmdPbigpO1xyXG5cdFx0XHR9XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBSb3V0ZSBjaGFuZ2Ugc3VjY2VzcyBoYW5kbGVyXHJcblx0XHQgKiBNYXRjaCBjdXJyZW50IG1lZGlhIHF1ZXJ5IGFuZCBydW4gYXBwcm9wcmlhdGUgZnVuY3Rpb25cclxuXHRcdCAqIElmIGN1cnJlbnQgcm91dGUgaGFzIGJlZW4gcmVzb2x2ZWQsIHR1cm4gb2ZmIGxvYWRpbmdcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJGV2ZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gY3VycmVudCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIHByZXZpb3VzIHtvYmplY3R9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfcm91dGVDaGFuZ2VTdWNjZXNzKCRldmVudCwgY3VycmVudCwgcHJldmlvdXMpIHtcclxuXHRcdFx0X21jLm1hdGNoQ3VycmVudChNUS5TTUFMTCk7XHJcblxyXG5cdFx0XHRpZiAoY3VycmVudC4kJHJvdXRlICYmIGN1cnJlbnQuJCRyb3V0ZS5yZXNvbHZlKSB7ICAgLy8gZXNsaW50LWRpc2FibGUtbGluZSBhbmd1bGFyL25vLXByaXZhdGUtY2FsbFxyXG5cdFx0XHRcdF9sb2FkaW5nT2ZmKCk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFJvdXRlIGNoYW5nZSBlcnJvciBoYW5kbGVyXHJcblx0XHQgKiBIYW5kbGUgcm91dGUgcmVzb2x2ZSBmYWlsdXJlc1xyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gcHJldmlvdXMge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSByZWplY3Rpb24ge29iamVjdH1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9yb3V0ZUNoYW5nZUVycm9yKCRldmVudCwgY3VycmVudCwgcHJldmlvdXMsIHJlamVjdGlvbikge1xyXG5cdFx0XHR2YXIgZGVzdGluYXRpb24gPSAoY3VycmVudCAmJiAoY3VycmVudC50aXRsZSB8fCBjdXJyZW50Lm5hbWUgfHwgY3VycmVudC5sb2FkZWRUZW1wbGF0ZVVybCkpIHx8ICd1bmtub3duIHRhcmdldCc7XHJcblx0XHRcdHZhciBtc2cgPSAnRXJyb3Igcm91dGluZyB0byAnICsgZGVzdGluYXRpb24gKyAnLiAnICsgKHJlamVjdGlvbi5tc2cgfHwgJycpO1xyXG5cclxuXHRcdFx0aWYgKF9oYW5kbGluZ1JvdXRlQ2hhbmdlRXJyb3IpIHtcclxuXHRcdFx0XHRyZXR1cm47XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdF9oYW5kbGluZ1JvdXRlQ2hhbmdlRXJyb3IgPSB0cnVlO1xyXG5cdFx0XHRfbG9hZGluZ09mZigpO1xyXG5cclxuXHRcdFx0JGxvZy5lcnJvcihtc2cpO1xyXG5cdFx0fVxyXG5cdFx0UGFnZUN0cmwuZW50ZXJNb2JpbGUgPSBfZW50ZXJNb2JpbGU7Ly90ZXN0IGNvZGVcclxuXHRcdFBhZ2VDdHJsLmV4aXRNb2JpbGUgPSBfZXhpdE1vYmlsZTsvL3Rlc3QgY29kZVxyXG5cdFx0UGFnZUN0cmwubG9hZGluZ09uID0gX2xvYWRpbmdPbjsvL3Rlc3QgY29kZVxyXG5cdFx0UGFnZUN0cmwubG9hZGluZ09mZiA9IF9sb2FkaW5nT2ZmOy8vdGVzdCBjb2RlXHJcblx0XHRyZXR1cm4gUGFnZUN0cmw7Ly90ZXN0IGNvZGVcclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuZmFjdG9yeSgnUGFnZScsIFBhZ2UpO1xyXG5cclxuXHRmdW5jdGlvbiBQYWdlKCkge1xyXG5cdFx0Ly8gcHJpdmF0ZSB2YXJzXHJcblx0XHR2YXIgc2l0ZVRpdGxlID0gJ3JlU3RhcnQgQW5ndWxhcic7XHJcblx0XHR2YXIgcGFnZVRpdGxlID0gJ0hvbWUnO1xyXG5cclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdGdldFRpdGxlOiBnZXRUaXRsZSxcclxuXHRcdFx0c2V0VGl0bGU6IHNldFRpdGxlXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogVGl0bGUgZnVuY3Rpb25cclxuXHRcdCAqIFNldHMgc2l0ZSB0aXRsZSBhbmQgcGFnZSB0aXRsZVxyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHtzdHJpbmd9IHNpdGUgdGl0bGUgKyBwYWdlIHRpdGxlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGdldFRpdGxlKCkge1xyXG5cdFx0XHRyZXR1cm4gc2l0ZVRpdGxlICsgJyB8ICcgKyBwYWdlVGl0bGU7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTZXQgcGFnZSB0aXRsZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSBuZXdUaXRsZSB7c3RyaW5nfVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBzZXRUaXRsZShuZXdUaXRsZSkge1xyXG5cdFx0XHRwYWdlVGl0bGUgPSBuZXdUaXRsZTtcclxuXHRcdH1cclxuXHR9XHJcbn0oKSk7IiwiLy8gXCJnbG9iYWxcIiBvYmplY3QgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5mYWN0b3J5KCdVdGlscycsIFV0aWxzKTtcclxuXHJcblx0ZnVuY3Rpb24gVXRpbHMoKSB7XHJcblx0XHR2YXIgZ3JlZXRpbmcgPSAnSGVsbG8nO1xyXG5cclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdGdyZWV0aW5nOiBncmVldGluZyxcclxuXHRcdFx0YWxlcnRHcmVldGluZzogYWxlcnRHcmVldGluZ1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFsZXJ0IGdyZWV0aW5nXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIG5hbWUge3N0cmluZ31cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gYWxlcnRHcmVldGluZyhuYW1lKSB7XHJcblx0XHRcdGFsZXJ0KGdyZWV0aW5nICsgJywgJyArIG5hbWUgKyAnIScpO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdIZWFkZXJDdHJsJywgSGVhZGVyQ3RybCk7XHJcblxyXG5cdEhlYWRlckN0cmwuJGluamVjdCA9IFsnJGxvY2F0aW9uJywgJ0pTT05EYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIEhlYWRlckN0cmwoJGxvY2F0aW9uLCBKU09ORGF0YSkge1xyXG5cdFx0Ly8gY29udHJvbGxlckFzIFZpZXdNb2RlbFxyXG5cdFx0dmFyIGhlYWRlciA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gYmluZGFibGUgbWVtYmVyc1xyXG5cdFx0aGVhZGVyLmluZGV4SXNBY3RpdmUgPSBpbmRleElzQWN0aXZlO1xyXG5cdFx0aGVhZGVyLm5hdklzQWN0aXZlID0gbmF2SXNBY3RpdmU7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIGFjdGl2YXRlIGNvbnRyb2xsZXJcclxuXHRcdFx0X2FjdGl2YXRlKCk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBDb250cm9sbGVyIGFjdGl2YXRlXHJcblx0XHQgKiBHZXQgSlNPTiBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMgeyp9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfYWN0aXZhdGUoKSB7XHJcblx0XHRcdC8vIGdldCB0aGUgZGF0YSBmcm9tIEpTT05cclxuXHRcdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpLnRoZW4oX2dldEpzb25TdWNjZXNzKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFN1Y2Nlc3NmdWwgcHJvbWlzZSBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGRhdGEge2pzb259XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZ2V0SnNvblN1Y2Nlc3MoZGF0YSkge1xyXG5cdFx0XHRoZWFkZXIuanNvbiA9IGRhdGE7XHJcblx0XHRcdHJldHVybiBoZWFkZXIuanNvbjtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFwcGx5IGNsYXNzIHRvIGluZGV4IG5hdiBpZiBhY3RpdmVcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0ge3N0cmluZ30gcGF0aFxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBpbmRleElzQWN0aXZlKHBhdGgpIHtcclxuXHRcdFx0Ly8gcGF0aCBzaG91bGQgYmUgJy8nXHJcblx0XHRcdHJldHVybiAkbG9jYXRpb24ucGF0aCgpID09PSBwYXRoO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQXBwbHkgY2xhc3MgdG8gY3VycmVudGx5IGFjdGl2ZSBuYXYgaXRlbVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSB7c3RyaW5nfSBwYXRoXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIG5hdklzQWN0aXZlKHBhdGgpIHtcclxuXHRcdFx0cmV0dXJuICRsb2NhdGlvbi5wYXRoKCkuc3Vic3RyKDAsIHBhdGgubGVuZ3RoKSA9PT0gcGF0aDtcclxuXHRcdH1cclxuXHR9XHJcblxyXG59KCkpOyIsIihmdW5jdGlvbiAoKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5kaXJlY3RpdmUoJ25hdkNvbnRyb2wnLCBuYXZDb250cm9sKTtcclxuXHJcblx0bmF2Q29udHJvbC4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xyXG5cclxuXHRmdW5jdGlvbiBuYXZDb250cm9sKCR3aW5kb3csIHJlc2l6ZSkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdGxpbms6IG5hdkNvbnRyb2xMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogbmF2Q29udHJvbCBMSU5LIGZ1bmN0aW9uXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRzY29wZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBuYXZDb250cm9sTGluaygkc2NvcGUpIHtcclxuXHRcdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcclxuXHRcdFx0dmFyIF8kYm9keSA9IGFuZ3VsYXIuZWxlbWVudCgnYm9keScpO1xyXG5cdFx0XHR2YXIgX2xheW91dENhbnZhcyA9IF8kYm9keS5maW5kKCcubGF5b3V0LWNhbnZhcycpO1xyXG5cdFx0XHR2YXIgX25hdk9wZW47XHJcblxyXG5cdFx0XHQvLyBkYXRhIG1vZGVsXHJcblx0XHRcdCRzY29wZS5uYXYgPSB7fTtcclxuXHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIGluaXRpYWxpemUgZGVib3VuY2VkIHJlc2l6ZVxyXG5cdFx0XHRcdHZhciBfcnMgPSByZXNpemUuaW5pdCh7XHJcblx0XHRcdFx0XHRzY29wZTogJHNjb3BlLFxyXG5cdFx0XHRcdFx0cmVzaXplZEZuOiBfcmVzaXplZCxcclxuXHRcdFx0XHRcdGRlYm91bmNlOiAxMDBcclxuXHRcdFx0XHR9KTtcclxuXHJcblx0XHRcdFx0JHNjb3BlLiRvbignJGxvY2F0aW9uQ2hhbmdlU3RhcnQnLCBfJGxvY2F0aW9uQ2hhbmdlU3RhcnQpO1xyXG5cdFx0XHRcdCRzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIF9lbnRlck1vYmlsZSk7XHJcblx0XHRcdFx0JHNjb3BlLiRvbignZXhpdC1tb2JpbGUnLCBfZXhpdE1vYmlsZSk7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBSZXNpemVkIHdpbmRvdyAoZGVib3VuY2VkKVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX3Jlc2l6ZWQoKSB7XHJcblx0XHRcdFx0X2xheW91dENhbnZhcy5jc3Moe1xyXG5cdFx0XHRcdFx0bWluSGVpZ2h0OiAkd2luZG93LmlubmVySGVpZ2h0ICsgJ3B4J1xyXG5cdFx0XHRcdH0pO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogT3BlbiBtb2JpbGUgbmF2aWdhdGlvblxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX29wZW5OYXYoKSB7XHJcblx0XHRcdFx0XyRib2R5XHJcblx0XHRcdFx0XHQucmVtb3ZlQ2xhc3MoJ25hdi1jbG9zZWQnKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCduYXYtb3BlbicpO1xyXG5cclxuXHRcdFx0XHRfbmF2T3BlbiA9IHRydWU7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBDbG9zZSBtb2JpbGUgbmF2aWdhdGlvblxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX2Nsb3NlTmF2KCkge1xyXG5cdFx0XHRcdF8kYm9keVxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduYXYtb3BlbicpXHJcblx0XHRcdFx0XHQuYWRkQ2xhc3MoJ25hdi1jbG9zZWQnKTtcclxuXHJcblx0XHRcdFx0X25hdk9wZW4gPSBmYWxzZTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIFRvZ2dsZSBuYXYgb3Blbi9jbG9zZWRcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIHRvZ2dsZU5hdigpIHtcclxuXHRcdFx0XHRpZiAoIV9uYXZPcGVuKSB7XHJcblx0XHRcdFx0XHRfb3Blbk5hdigpO1xyXG5cdFx0XHRcdH0gZWxzZSB7XHJcblx0XHRcdFx0XHRfY2xvc2VOYXYoKTtcclxuXHRcdFx0XHR9XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBXaGVuIGNoYW5naW5nIGxvY2F0aW9uLCBjbG9zZSB0aGUgbmF2IGlmIGl0J3Mgb3BlblxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gXyRsb2NhdGlvbkNoYW5nZVN0YXJ0KCkge1xyXG5cdFx0XHRcdGlmIChfbmF2T3Blbikge1xyXG5cdFx0XHRcdFx0X2Nsb3NlTmF2KCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogRnVuY3Rpb24gdG8gZXhlY3V0ZSB3aGVuIGVudGVyaW5nIG1vYmlsZSBtZWRpYSBxdWVyeVxyXG5cdFx0XHQgKiBDbG9zZSBuYXYgYW5kIHNldCB1cCBtZW51IHRvZ2dsaW5nIGZ1bmN0aW9uYWxpdHlcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9lbnRlck1vYmlsZShtcSkge1xyXG5cdFx0XHRcdF9jbG9zZU5hdigpO1xyXG5cclxuXHRcdFx0XHQvLyBiaW5kIGZ1bmN0aW9uIHRvIHRvZ2dsZSBtb2JpbGUgbmF2aWdhdGlvbiBvcGVuL2Nsb3NlZFxyXG5cdFx0XHRcdCRzY29wZS5uYXYudG9nZ2xlTmF2ID0gdG9nZ2xlTmF2O1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogRnVuY3Rpb24gdG8gZXhlY3V0ZSB3aGVuIGV4aXRpbmcgbW9iaWxlIG1lZGlhIHF1ZXJ5XHJcblx0XHRcdCAqIERpc2FibGUgbWVudSB0b2dnbGluZyBhbmQgcmVtb3ZlIGJvZHkgY2xhc3Nlc1xyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX2V4aXRNb2JpbGUobXEpIHtcclxuXHRcdFx0XHQvLyB1bmJpbmQgZnVuY3Rpb24gdG8gdG9nZ2xlIG1vYmlsZSBuYXZpZ2F0aW9uIG9wZW4vY2xvc2VkXHJcblx0XHRcdFx0JHNjb3BlLm5hdi50b2dnbGVOYXYgPSBudWxsO1xyXG5cclxuXHRcdFx0XHRfJGJvZHkucmVtb3ZlQ2xhc3MoJ25hdi1jbG9zZWQgbmF2LW9wZW4nKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxuXHJcbn0oKSk7IiwiLy8gYXBwbGljYXRpb24gY29uZmlnXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbmZpZyhhcHBDb25maWcpO1xyXG5cclxuXHRhcHBDb25maWcuJGluamVjdCA9IFsnJHJvdXRlUHJvdmlkZXInLCAnJGxvY2F0aW9uUHJvdmlkZXInXTtcclxuXHJcblx0ZnVuY3Rpb24gYXBwQ29uZmlnKCRyb3V0ZVByb3ZpZGVyLCAkbG9jYXRpb25Qcm92aWRlcikge1xyXG5cdFx0JHJvdXRlUHJvdmlkZXJcclxuXHRcdFx0LndoZW4oJy8nLCB7XHJcblx0XHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9ob21lL0hvbWUudmlldy5odG1sJyxcclxuXHRcdFx0XHRjb250cm9sbGVyOiAnSG9tZUN0cmwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXJBczogJ2hvbWUnXHJcblx0XHRcdH0pXHJcblx0XHRcdC53aGVuKCcvc3VicGFnZScsIHtcclxuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL3N1Yi9TdWIudmlldy5odG1sJyxcclxuXHRcdFx0XHRjb250cm9sbGVyOiAnU3ViQ3RybCcsXHJcblx0XHRcdFx0Y29udHJvbGxlckFzOiAnc3ViJyxcclxuXHRcdFx0XHRyZXNvbHZlOiB7XHJcblx0XHRcdFx0XHRyZXNvbHZlTG9jYWxEYXRhOiByZXNvbHZlTG9jYWxEYXRhXHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9KVxyXG5cdFx0XHQub3RoZXJ3aXNlKHtcclxuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LnZpZXcuaHRtbCcsXHJcblx0XHRcdFx0Y29udHJvbGxlcjogJ0Vycm9yNDA0Q3RybCcsXHJcblx0XHRcdFx0Y29udHJvbGxlckFzOiAnZTQwNCdcclxuXHRcdFx0fSk7XHJcblxyXG5cdFx0JGxvY2F0aW9uUHJvdmlkZXJcclxuXHRcdFx0Lmh0bWw1TW9kZSh7XHJcblx0XHRcdFx0ZW5hYmxlZDogdHJ1ZVxyXG5cdFx0XHR9KVxyXG5cdFx0XHQuaGFzaFByZWZpeCgnIScpO1xyXG5cdH1cclxuXHJcblx0cmVzb2x2ZUxvY2FsRGF0YS4kaW5qZWN0ID0gWydKU09ORGF0YSddO1xyXG5cdC8qKlxyXG5cdCAqIEdldCBsb2NhbCBkYXRhIGZvciByb3V0ZSByZXNvbHZlXHJcblx0ICpcclxuXHQgKiBAcGFyYW0gSlNPTkRhdGEge2ZhY3Rvcnl9XHJcblx0ICogQHJldHVybnMge3Byb21pc2V9IGRhdGFcclxuXHQgKi9cclxuXHRmdW5jdGlvbiByZXNvbHZlTG9jYWxEYXRhKEpTT05EYXRhKSB7XHJcblx0XHRyZXR1cm4gSlNPTkRhdGEuZ2V0TG9jYWxEYXRhKCk7XHJcblx0fVxyXG59KCkpOyIsIi8vIGZldGNoIEpTT04gZGF0YSB0byBzaGFyZSBiZXR3ZWVuIGNvbnRyb2xsZXJzXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmZhY3RvcnkoJ0pTT05EYXRhJywgSlNPTkRhdGEpO1xyXG5cclxuXHRKU09ORGF0YS4kaW5qZWN0ID0gWyckaHR0cCcsICdSZXMnXTtcclxuXHJcblx0ZnVuY3Rpb24gSlNPTkRhdGEoJGh0dHAsIFJlcykge1xyXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0Z2V0TG9jYWxEYXRhOiBnZXRMb2NhbERhdGFcclxuXHRcdH07XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBHRVQgbG9jYWwgSlNPTiBkYXRhIGZpbGUgYW5kIHJldHVybiByZXN1bHRzXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMge3Byb21pc2V9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGdldExvY2FsRGF0YSgpIHtcclxuXHRcdFx0cmV0dXJuICRodHRwXHJcblx0XHRcdFx0LmdldCgnL2RhdGEvZGF0YS5qc29uJylcclxuXHRcdFx0XHQudGhlbihSZXMuc3VjY2VzcywgUmVzLmVycm9yKTtcclxuXHRcdH1cclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuZmFjdG9yeSgnUmVzJywgUmVzKTtcclxuXHJcblx0ZnVuY3Rpb24gUmVzKCkge1xyXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0c3VjY2Vzczogc3VjY2VzcyxcclxuXHRcdFx0ZXJyb3I6IGVycm9yXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogUHJvbWlzZSByZXNwb25zZSBmdW5jdGlvblxyXG5cdFx0ICogQ2hlY2tzIHR5cGVvZiBkYXRhIHJldHVybmVkIGFuZCBzdWNjZWVkcyBpZiBKUyBvYmplY3QsIHRocm93cyBlcnJvciBpZiBub3RcclxuXHRcdCAqIFVzZWZ1bCBmb3IgQVBJcyAoaWUsIHdpdGggbmdpbngpIHdoZXJlIHNlcnZlciBlcnJvciBIVE1MIHBhZ2UgbWF5IGJlIHJldHVybmVkIGluIGVycm9yXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIHJlc3BvbnNlIHsqfSBkYXRhIGZyb20gJGh0dHBcclxuXHRcdCAqIEByZXR1cm5zIHsqfSBvYmplY3QsIGFycmF5XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIHN1Y2Nlc3MocmVzcG9uc2UpIHtcclxuXHRcdFx0aWYgKGFuZ3VsYXIuaXNPYmplY3QocmVzcG9uc2UuZGF0YSkpIHtcclxuXHRcdFx0XHRyZXR1cm4gcmVzcG9uc2UuZGF0YTtcclxuXHRcdFx0fSBlbHNlIHtcclxuXHRcdFx0XHR0aHJvdyBuZXcgRXJyb3IoJ3JldHJpZXZlZCBkYXRhIGlzIG5vdCB0eXBlb2Ygb2JqZWN0LicpO1xyXG5cdFx0XHR9XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBQcm9taXNlIHJlc3BvbnNlIGZ1bmN0aW9uIC0gZXJyb3JcclxuXHRcdCAqIFRocm93cyBhbiBlcnJvciB3aXRoIGVycm9yIGRhdGFcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gZXJyb3Ige29iamVjdH1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gZXJyb3IoZXJyb3IpIHtcclxuXHRcdFx0dGhyb3cgbmV3IEVycm9yKCdFcnJvciByZXRyaWV2aW5nIGRhdGEnLCBlcnJvcik7XHJcblx0XHR9XHJcblx0fVxyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmRpcmVjdGl2ZSgnbG9hZGluZycsIGxvYWRpbmcpO1xyXG5cclxuXHRsb2FkaW5nLiRpbmplY3QgPSBbJyR3aW5kb3cnLCAncmVzaXplJ107XHJcblxyXG5cdGZ1bmN0aW9uIGxvYWRpbmcoJHdpbmRvdywgcmVzaXplKSB7XHJcblx0XHQvLyByZXR1cm4gZGlyZWN0aXZlXHJcblx0XHRyZXR1cm4ge1xyXG5cdFx0XHRyZXN0cmljdDogJ0VBJyxcclxuXHRcdFx0cmVwbGFjZTogdHJ1ZSxcclxuXHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9jb3JlL3VpL2xvYWRpbmcudHBsLmh0bWwnLFxyXG5cdFx0XHR0cmFuc2NsdWRlOiB0cnVlLFxyXG5cdFx0XHRjb250cm9sbGVyOiBsb2FkaW5nQ3RybCxcclxuXHRcdFx0Y29udHJvbGxlckFzOiAnbG9hZGluZycsXHJcblx0XHRcdGJpbmRUb0NvbnRyb2xsZXI6IHRydWUsXHJcblx0XHRcdGxpbms6IGxvYWRpbmdMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogbG9hZGluZyBMSU5LXHJcblx0XHQgKiBEaXNhYmxlcyBwYWdlIHNjcm9sbGluZyB3aGVuIGxvYWRpbmcgb3ZlcmxheSBpcyBvcGVuXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRzY29wZVxyXG5cdFx0ICogQHBhcmFtICRlbGVtZW50XHJcblx0XHQgKiBAcGFyYW0gJGF0dHJzXHJcblx0XHQgKiBAcGFyYW0gbG9hZGluZyB7Y29udHJvbGxlcn1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gbG9hZGluZ0xpbmsoJHNjb3BlLCAkZWxlbWVudCwgJGF0dHJzLCBsb2FkaW5nKSB7XHJcblx0XHRcdC8vIHByaXZhdGUgdmFyaWFibGVzXHJcblx0XHRcdHZhciBfJGJvZHkgPSBhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKTtcclxuXHRcdFx0dmFyIF93aW5IZWlnaHQgPSAkd2luZG93LmlubmVySGVpZ2h0ICsgJ3B4JztcclxuXHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIGluaXRpYWxpemUgZGVib3VuY2VkIHJlc2l6ZVxyXG5cdFx0XHRcdHZhciBfcnMgPSByZXNpemUuaW5pdCh7XHJcblx0XHRcdFx0XHRzY29wZTogJHNjb3BlLFxyXG5cdFx0XHRcdFx0cmVzaXplZEZuOiBfcmVzaXplZCxcclxuXHRcdFx0XHRcdGRlYm91bmNlOiAyMDBcclxuXHRcdFx0XHR9KTtcclxuXHJcblx0XHRcdFx0Ly8gJHdhdGNoIGFjdGl2ZSBzdGF0ZVxyXG5cdFx0XHRcdCRzY29wZS4kd2F0Y2goJ2xvYWRpbmcuYWN0aXZlJywgXyR3YXRjaEFjdGl2ZSk7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBXaW5kb3cgcmVzaXplZFxyXG5cdFx0XHQgKiBJZiBsb2FkaW5nLCByZWFwcGx5IGJvZHkgaGVpZ2h0XHJcblx0XHRcdCAqIHRvIHByZXZlbnQgc2Nyb2xsYmFyXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfcmVzaXplZCgpIHtcclxuXHRcdFx0XHRfd2luSGVpZ2h0ID0gJHdpbmRvdy5pbm5lckhlaWdodCArICdweCc7XHJcblxyXG5cdFx0XHRcdGlmIChsb2FkaW5nLmFjdGl2ZSkge1xyXG5cdFx0XHRcdFx0XyRib2R5LmNzcyh7XHJcblx0XHRcdFx0XHRcdGhlaWdodDogX3dpbkhlaWdodCxcclxuXHRcdFx0XHRcdFx0b3ZlcmZsb3dZOiAnaGlkZGVuJ1xyXG5cdFx0XHRcdFx0fSk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogJHdhdGNoIGxvYWRpbmcuYWN0aXZlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwYXJhbSBuZXdWYWwge2Jvb2xlYW59XHJcblx0XHRcdCAqIEBwYXJhbSBvbGRWYWwge3VuZGVmaW5lZHxib29sZWFufVxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gXyR3YXRjaEFjdGl2ZShuZXdWYWwsIG9sZFZhbCkge1xyXG5cdFx0XHRcdGlmIChuZXdWYWwpIHtcclxuXHRcdFx0XHRcdF9vcGVuKCk7XHJcblx0XHRcdFx0fSBlbHNlIHtcclxuXHRcdFx0XHRcdF9jbG9zZSgpO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIE9wZW4gbG9hZGluZ1xyXG5cdFx0XHQgKiBEaXNhYmxlIHNjcm9sbFxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX29wZW4oKSB7XHJcblx0XHRcdFx0XyRib2R5LmNzcyh7XHJcblx0XHRcdFx0XHRoZWlnaHQ6IF93aW5IZWlnaHQsXHJcblx0XHRcdFx0XHRvdmVyZmxvd1k6ICdoaWRkZW4nXHJcblx0XHRcdFx0fSk7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBDbG9zZSBsb2FkaW5nXHJcblx0XHRcdCAqIEVuYWJsZSBzY3JvbGxcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9jbG9zZSgpIHtcclxuXHRcdFx0XHRfJGJvZHkuY3NzKHtcclxuXHRcdFx0XHRcdGhlaWdodDogJ2F1dG8nLFxyXG5cdFx0XHRcdFx0b3ZlcmZsb3dZOiAnYXV0bydcclxuXHRcdFx0XHR9KTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxuXHJcblx0bG9hZGluZ0N0cmwuJGluamVjdCA9IFsnJHNjb3BlJ107XHJcblx0LyoqXHJcblx0ICogbG9hZGluZyBDT05UUk9MTEVSXHJcblx0ICogVXBkYXRlIHRoZSBsb2FkaW5nIHN0YXR1cyBiYXNlZFxyXG5cdCAqIG9uIHJvdXRlQ2hhbmdlIHN0YXRlXHJcblx0ICovXHJcblx0ZnVuY3Rpb24gbG9hZGluZ0N0cmwoJHNjb3BlKSB7XHJcblx0XHR2YXIgbG9hZGluZyA9IHRoaXM7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHR1cm4gb24gbG9hZGluZyBmb3IgaW5pdGlhbCBwYWdlIGxvYWRcclxuXHRcdFx0X2xvYWRpbmdBY3RpdmUoKTtcclxuXHJcblx0XHRcdCRzY29wZS4kb24oJ2xvYWRpbmctb24nLCBfbG9hZGluZ0FjdGl2ZSk7XHJcblx0XHRcdCRzY29wZS4kb24oJ2xvYWRpbmctb2ZmJywgX2xvYWRpbmdJbmFjdGl2ZSk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTZXQgbG9hZGluZyB0byBhY3RpdmVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfbG9hZGluZ0FjdGl2ZSgpIHtcclxuXHRcdFx0bG9hZGluZy5hY3RpdmUgPSB0cnVlO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogU2V0IGxvYWRpbmcgdG8gaW5hY3RpdmVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfbG9hZGluZ0luYWN0aXZlKCkge1xyXG5cdFx0XHRsb2FkaW5nLmFjdGl2ZSA9IGZhbHNlO1xyXG5cdFx0fVxyXG5cdH1cclxuXHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0Ly8gbWVkaWEgcXVlcnkgY29uc3RhbnRzXHJcblx0dmFyIE1RID0ge1xyXG5cdFx0U01BTEw6ICcobWF4LXdpZHRoOiA3NjdweCknLFxyXG5cdFx0TEFSR0U6ICcobWluLXdpZHRoOiA3NjhweCknXHJcblx0fTtcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29uc3RhbnQoJ01RJywgTVEpO1xyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmZpbHRlcigndHJ1c3RBc0hUTUwnLCB0cnVzdEFzSFRNTCk7XHJcblxyXG5cdHRydXN0QXNIVE1MLiRpbmplY3QgPSBbJyRzY2UnXTtcclxuXHJcblx0ZnVuY3Rpb24gdHJ1c3RBc0hUTUwoJHNjZSkge1xyXG5cdFx0cmV0dXJuIGZ1bmN0aW9uKHRleHQpIHtcclxuXHRcdFx0cmV0dXJuICRzY2UudHJ1c3RBc0h0bWwodGV4dCk7XHJcblx0XHR9O1xyXG5cdH1cclxufSgpKTsiLCIoZnVuY3Rpb24gKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignRXJyb3I0MDRDdHJsJywgRXJyb3I0MDRDdHJsKTtcclxuXHJcblx0RXJyb3I0MDRDdHJsLiRpbmplY3QgPSBbJyRzY29wZScsICdQYWdlJ107XHJcblxyXG5cdGZ1bmN0aW9uIEVycm9yNDA0Q3RybCgkc2NvcGUsIFBhZ2UpIHtcclxuXHRcdHZhciBlNDA0ID0gdGhpcztcclxuXHJcblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXHJcblx0XHRlNDA0LnRpdGxlID0gJzQwNCAtIFBhZ2UgTm90IEZvdW5kJztcclxuXHJcblx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0Ly8gc2V0IHBhZ2UgPHRpdGxlPlxyXG5cdFx0XHRQYWdlLnNldFRpdGxlKGU0MDQudGl0bGUpO1xyXG5cclxuXHRcdFx0Ly8gbm8gZGF0YSB0byBsb2FkLCBidXQgbG9hZGluZyBzdGF0ZSBtaWdodCBiZSBvblxyXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb2ZmJyk7XHJcblx0XHR9XHJcblxyXG5cdFx0cmV0dXJuIHsgICAgICAgIC8vdGVzdCBjb2RlXHJcblx0XHRcdGluaXQ6IF9pbml0IC8vdGVzdCBjb2RlXHJcblx0XHR9OyAgICAgICAgICAgICAgIC8vdGVzdCBjb2RlXHJcblx0fVxyXG59KCkpOyIsIihmdW5jdGlvbiAoKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdIb21lQ3RybCcsIEhvbWVDdHJsKTtcclxuXHJcblx0SG9tZUN0cmwuJGluamVjdCA9IFsnJHNjb3BlJywgJ1V0aWxzJywgJ1BhZ2UnLCAnSlNPTkRhdGEnXTtcclxuXHJcblx0ZnVuY3Rpb24gSG9tZUN0cmwoJHNjb3BlLCBVdGlscywgUGFnZSwgSlNPTkRhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBob21lID0gdGhpcztcclxuXHJcblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXHJcblx0XHRob21lLnRpdGxlID0gJ0hvbWUnO1xyXG5cdFx0aG9tZS5nbG9iYWwgPSBVdGlscztcclxuXHRcdGhvbWUubmFtZSA9ICdWaXNpdG9yJztcclxuXHRcdGhvbWUuYWxlcnRHcmVldGluZyA9IFV0aWxzLmFsZXJ0R3JlZXRpbmc7XHJcblx0XHRob21lLnN0cmluZ09mSFRNTCA9ICc8c3Ryb25nIHN0eWxlPVwiY29sb3I6IGdyZWVuO1wiPlNvbWUgZ3JlZW4gdGV4dDwvc3Ryb25nPiBib3VuZCBhcyBIVE1MIHdpdGggYSA8YSBocmVmPVwiI1wiPmxpbms8L2E+LCB0cnVzdGVkIHdpdGggU0NFISc7XHJcblx0XHRob21lLnZpZXdmb3JtYXQgPSBudWxsO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBzZXQgcGFnZSA8dGl0bGU+XHJcblx0XHRcdFBhZ2Uuc2V0VGl0bGUoaG9tZS50aXRsZSk7XHJcblxyXG5cdFx0XHQvLyBhY3RpdmF0ZSBjb250cm9sbGVyXHJcblx0XHRcdF9hY3RpdmF0ZSgpO1xyXG5cclxuXHRcdFx0Ly8gbWVkaWFxdWVyeSBldmVudHNcclxuXHRcdFx0JHNjb3BlLiRvbignZW50ZXItbW9iaWxlJywgX2VudGVyTW9iaWxlKTtcclxuXHRcdFx0JHNjb3BlLiRvbignZXhpdC1tb2JpbGUnLCBfZXhpdE1vYmlsZSk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBDb250cm9sbGVyIGFjdGl2YXRlXHJcblx0XHQgKiBHZXQgSlNPTiBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMgeyp9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfYWN0aXZhdGUoKSB7XHJcblx0XHRcdC8vIHN0YXJ0IGxvYWRpbmdcclxuXHRcdFx0JHNjb3BlLiRlbWl0KCdsb2FkaW5nLW9uJyk7XHJcblxyXG5cdFx0XHQvLyBnZXQgdGhlIGRhdGEgZnJvbSBKU09OXHJcblx0XHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKS50aGVuKF9nZXRKc29uU3VjY2Vzcyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTdWNjZXNzZnVsIHByb21pc2UgZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSBkYXRhIHtqc29ufVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2dldEpzb25TdWNjZXNzKGRhdGEpIHtcclxuXHRcdFx0aG9tZS5qc29uID0gZGF0YTtcclxuXHJcblx0XHRcdC8vIHN0b3AgbG9hZGluZ1xyXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb2ZmJyk7XHJcblxyXG5cdFx0XHRyZXR1cm4gaG9tZS5qc29uO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogRW50ZXIgc21hbGwgbXFcclxuXHRcdCAqIFNldCBob21lLnZpZXdmb3JtYXRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZW50ZXJNb2JpbGUoKSB7XHJcblx0XHRcdGhvbWUudmlld2Zvcm1hdCA9ICdzbWFsbCc7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFeGl0IHNtYWxsIG1xXHJcblx0XHQgKiBTZXQgaG9tZS52aWV3Zm9ybWF0XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2V4aXRNb2JpbGUoKSB7XHJcblx0XHRcdGhvbWUudmlld2Zvcm1hdCA9ICdsYXJnZSc7XHJcblx0XHR9XHJcblxyXG5cdFx0ZnVuY3Rpb24gZ2V0VmlldygpIHsgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdFx0cmV0dXJuIGhvbWUudmlld2Zvcm1hdDsgLy90ZXN0IGNvZGVcclxuXHRcdH0gICAgICAgICAgICAgICAgICAgICAgICAgICAvL3Rlc3QgY29kZVxyXG5cclxuXHRcdGhvbWUuZW50ZXJNb2JpbGUgPSBfZW50ZXJNb2JpbGU7ICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdGhvbWUuZXhpdE1vYmlsZSA9IF9leGl0TW9iaWxlOyAgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdGhvbWUuZ2V0SnNvblN1Y2VzcyA9IF9nZXRKc29uU3VjY2VzczsgLy90ZXN0IGNvZGVcclxuXHRcdGhvbWUuYWN0aXZhdGUgPSBfYWN0aXZhdGU7ICAgICAgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdGhvbWUuZ2V0VmlldyA9IGdldFZpZXc7ICAgICAgICAgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdHJldHVybiBob21lOyAgICAgICAgICAgICAgICAgICAgICAgICAgLy90ZXN0IGNvZGVcclxuXHR9XHJcbn0oKSk7IiwiLyoqXHJcbiAqIERpcmVjdGl2ZXMgKGFuZCBhc3NvY2lhdGVkIGF0dHJpYnV0ZXMpIGFyZSBhbHdheXMgZGVjbGFyZWQgYXMgY2FtZWxDYXNlIGluIEpTIGFuZCBzbmFrZS1jYXNlIGluIEhUTUxcclxuICogQW5ndWxhcidzIGJ1aWx0LWluIDxhPiBkaXJlY3RpdmUgYXV0b21hdGljYWxseSBpbXBsZW1lbnRzIHByZXZlbnREZWZhdWx0IG9uIGxpbmtzIHRoYXQgZG9uJ3QgaGF2ZSBhbiBocmVmIGF0dHJpYnV0ZVxyXG4gKiBDb21wbGV4IEphdmFTY3JpcHQgRE9NIG1hbmlwdWxhdGlvbiBzaG91bGQgYWx3YXlzIGJlIGRvbmUgaW4gZGlyZWN0aXZlIGxpbmsgZnVuY3Rpb25zLCBhbmQgJGFwcGx5IHNob3VsZCBuZXZlciBiZSB1c2VkIGluIGEgY29udHJvbGxlciEgU2ltcGxlIERPTSBtYW5pcHVsYXRpb24gc2hvdWxkIGJlIGluIHRoZSB2aWV3LlxyXG4gKi9cclxuXHJcbi8qLS0tIFNhbXBsZSBEaXJlY3RpdmUgd2l0aCBhICR3YXRjaCAtLS0qL1xyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5kaXJlY3RpdmUoJ3NhbXBsZURpcmVjdGl2ZScsIHNhbXBsZURpcmVjdGl2ZSk7XHJcblxyXG5cdHNhbXBsZURpcmVjdGl2ZS4kaW5qZWN0ID0gWyckdGltZW91dCddO1xyXG5cclxuXHRmdW5jdGlvbiBzYW1wbGVEaXJlY3RpdmUoJHRpbWVvdXQpIHtcclxuXHRcdC8vIHJldHVybiBkaXJlY3RpdmVcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdHJlc3RyaWN0OiAnRUEnLFxyXG5cdFx0XHRyZXBsYWNlOiB0cnVlLFxyXG5cdFx0XHRzY29wZToge30sXHJcblx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvcGFnZXMvc3ViL3NhbXBsZS50cGwuaHRtbCcsXHJcblx0XHRcdHRyYW5zY2x1ZGU6IHRydWUsXHJcblx0XHRcdGNvbnRyb2xsZXI6IFNhbXBsZURpcmVjdGl2ZUN0cmwsXHJcblx0XHRcdGNvbnRyb2xsZXJBczogJ3NkJyxcclxuXHRcdFx0YmluZFRvQ29udHJvbGxlcjoge1xyXG5cdFx0XHRcdGpzb25EYXRhOiAnPSdcclxuXHRcdFx0fSxcclxuXHRcdFx0bGluazogc2FtcGxlRGlyZWN0aXZlTGlua1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIHNhbXBsZURpcmVjdGl2ZSBMSU5LIGZ1bmN0aW9uXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRzY29wZVxyXG5cdFx0ICogQHBhcmFtICRlbGVtZW50XHJcblx0XHQgKiBAcGFyYW0gJGF0dHJzXHJcblx0XHQgKiBAcGFyYW0gc2Qge2NvbnRyb2xsZXJ9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIHNhbXBsZURpcmVjdGl2ZUxpbmsoJHNjb3BlLCAkZWxlbWVudCwgJGF0dHJzLCBzZCkge1xyXG5cdFx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0XHQvLyB3YXRjaCBmb3IgYXN5bmMgZGF0YSB0byBiZWNvbWUgYXZhaWxhYmxlIGFuZCB1cGRhdGVcclxuXHRcdFx0XHQkc2NvcGUuJHdhdGNoKCdzZC5qc29uRGF0YScsIF8kd2F0Y2hKc29uRGF0YSk7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiAkd2F0Y2ggZm9yIHNkLmpzb25EYXRhIHRvIGJlY29tZSBhdmFpbGFibGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHBhcmFtIG5ld1ZhbCB7Kn1cclxuXHRcdFx0ICogQHBhcmFtIG9sZFZhbCB7Kn1cclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF8kd2F0Y2hKc29uRGF0YShuZXdWYWwsIG9sZFZhbCkge1xyXG5cdFx0XHRcdGlmIChuZXdWYWwpIHtcclxuXHRcdFx0XHRcdHNkLmpzb25EYXRhID0gbmV3VmFsO1xyXG5cclxuXHRcdFx0XHRcdCR0aW1lb3V0KGZ1bmN0aW9uKCkge1xyXG5cdFx0XHRcdFx0XHRjb25zb2xlLmxvZygnZGVtb25zdHJhdGUgJHRpbWVvdXQgaW5qZWN0aW9uIGluIGEgZGlyZWN0aXZlIGxpbmsgZnVuY3Rpb24nKTtcclxuXHRcdFx0XHRcdH0sIDEwMDApO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxuXHJcblx0U2FtcGxlRGlyZWN0aXZlQ3RybC4kaW5qZWN0ID0gW107XHJcblx0LyoqXHJcblx0ICogc2FtcGxlRGlyZWN0aXZlIENPTlRST0xMRVJcclxuXHQgKi9cclxuXHRmdW5jdGlvbiBTYW1wbGVEaXJlY3RpdmVDdHJsKCkge1xyXG5cdFx0dmFyIHNkID0gdGhpcztcclxuXHJcblx0XHQvLyBjb250cm9sbGVyIGxvZ2ljIGdvZXMgaGVyZVxyXG5cdH1cclxuXHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignU3ViQ3RybCcsIFN1YkN0cmwpO1xyXG5cclxuXHRTdWJDdHJsLiRpbmplY3QgPSBbJ1V0aWxzJywgJ1BhZ2UnLCAncmVzb2x2ZUxvY2FsRGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBTdWJDdHJsKFV0aWxzLCBQYWdlLCByZXNvbHZlTG9jYWxEYXRhKSB7XHJcblx0XHQvLyBjb250cm9sbGVyQXMgVmlld01vZGVsXHJcblx0XHR2YXIgc3ViID0gdGhpcztcclxuXHJcblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXHJcblx0XHRzdWIudGl0bGUgPSAnU3VicGFnZSc7XHJcblx0XHRzdWIuZ2xvYmFsID0gVXRpbHM7XHJcblx0XHRzdWIuanNvbiA9IHJlc29sdmVMb2NhbERhdGE7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cclxuXHRcdFx0UGFnZS5zZXRUaXRsZShzdWIudGl0bGUpO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= diff --git a/src/reStart-app/reStart-app.spec.js b/src/reStart-app/reStart-app.spec.js index c8a7817..0683227 100644 --- a/src/reStart-app/reStart-app.spec.js +++ b/src/reStart-app/reStart-app.spec.js @@ -96,6 +96,77 @@ }); }); })(); +; (function () { + describe('Module: reStart', function () { + var $controller, $rootScope; + beforeEach(function () { + module('reStart'); + inject(function ($injector) { + $rootScope = $injector.get('$rootScope'); + $controller = $injector.get('$controller'); + }); + }); + describe('Controller: HeaderCtrl', function () { + + var scope, headerVm; + beforeEach(inject(function ($controller, $rootScope) { + scope = $rootScope.$new(); + headerVm = $controller('HeaderCtrl as headerVm', { $scope: scope }); + })); + + it("Apply class to index nav if active", function () { + expect(headerVm.indexIsActive('/')).toBeTruthy(); + }) + it("Apply class to currently active nav item", function () { + expect(headerVm.navIsActive('/')).toBeTruthy(); + }) + + }); + }); +})(); +; (function () { + var $compile, $rootScope,scope,element; + beforeEach(function () { + module('reStart'); + }); + + describe('Directive: navControl', function () { + beforeEach(function () { + module('templates'); + + //disable loading directive? + angular.module("reStart").directive("loading", function (loading) { + return { + priority: 100000, + terminal: true, + link: function () { + // do nothing + } + } + }); + + inject( + ['$compile', '$rootScope', function ($c, $r) { + $compile = $c; + $rootScope = $r; + scope = $rootScope.$new(); + }] + ) + element = $compile('<div nav-control><a class="toggle-offcanvas" ng-click="nav.toggleNav()"><span></span></a></div>')($rootScope); + angular.element(document.body).append(element); + $('loading').remove(); + //trigger directive to be injected + $rootScope.$digest(); + }); + + xit("toggles nav on and off", function () { + angular.element('.toggle-offcanvas').click(); + $rootScope.$digest(); + console.log(angular.element('body')[0]) + expect(angular.element('body').hasClass('nav-closed')).toBeFalsy(); + }) + }); +})(); ; (function () { describe('Module: reStart', function () { var $rootScope; @@ -206,77 +277,6 @@ })); }); })(); -; (function () { - describe('Module: reStart', function () { - var $controller, $rootScope; - beforeEach(function () { - module('reStart'); - inject(function ($injector) { - $rootScope = $injector.get('$rootScope'); - $controller = $injector.get('$controller'); - }); - }); - describe('Controller: HeaderCtrl', function () { - - var scope, headerVm; - beforeEach(inject(function ($controller, $rootScope) { - scope = $rootScope.$new(); - headerVm = $controller('HeaderCtrl as headerVm', { $scope: scope }); - })); - - it("Apply class to index nav if active", function () { - expect(headerVm.indexIsActive('/')).toBeTruthy(); - }) - it("Apply class to currently active nav item", function () { - expect(headerVm.navIsActive('/')).toBeTruthy(); - }) - - }); - }); -})(); -; (function () { - var $compile, $rootScope,scope,element; - beforeEach(function () { - module('reStart'); - }); - - describe('Directive: navControl', function () { - beforeEach(function () { - module('templates'); - - //disable loading directive? - angular.module("reStart").directive("loading", function (loading) { - return { - priority: 100000, - terminal: true, - link: function () { - // do nothing - } - } - }); - - inject( - ['$compile', '$rootScope', function ($c, $r) { - $compile = $c; - $rootScope = $r; - scope = $rootScope.$new(); - }] - ) - element = $compile('<div nav-control><a class="toggle-offcanvas" ng-click="nav.toggleNav()"><span></span></a></div>')($rootScope); - angular.element(document.body).append(element); - $('loading').remove(); - //trigger directive to be injected - $rootScope.$digest(); - }); - - xit("toggles nav on and off", function () { - angular.element('.toggle-offcanvas').click(); - $rootScope.$digest(); - console.log(angular.element('body')[0]) - expect(angular.element('body').hasClass('nav-closed')).toBeFalsy(); - }) - }); -})(); ; (function () { describe('Module: reStart', function () { var $controller, $rootScope; @@ -408,4 +408,4 @@ }); }); })(); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNvcmUvUGFnZS5jdHJsLnNwZWMuanMiLCJjb3JlL1BhZ2UuZmFjdG9yeS5zcGVjLmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LnNwZWMuanMiLCJjb3JlL2dldC1kYXRhL0pTT05EYXRhLmZhY3Rvcnkuc3BlYy5qcyIsImNvcmUvZ2V0LWRhdGEvUmVzLmZhY3Rvcnkuc3BlYy5qcyIsImNvcmUvdWkvbG9hZGluZy5kaXIuc3BlYy5qcyIsImNvcmUvdWkvdHJ1c3RBc0hUTUwuZmlsdGVyLnNwZWMuanMiLCJtb2R1bGVzL2hlYWRlci9IZWFkZXIuY3RybC5zcGVjLmpzIiwibW9kdWxlcy9oZWFkZXIvbmF2Q29udHJvbC5kaXIuc3BlYy5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuc3BlYy5qcyIsInBhZ2VzL2hvbWUvSG9tZS5jdHJsLnNwZWMuanMiLCJwYWdlcy9zdWIvc2FtcGxlLmRpci5zcGVjLmpzIiwicGFnZXMvc3ViL1N1Yi5jdHJsLnNwZWMuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzlDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDakNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDM0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDOUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQy9CQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzlCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJyZVN0YXJ0LWFwcC5zcGVjLmpzIiwic291cmNlc0NvbnRlbnQiOlsiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgZGVzY3JpYmUoJ01vZHVsZTogcmVTdGFydCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgJGNvbnRyb2xsZXIsICRyb290U2NvcGU7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgICAgICAgICBpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgJHJvb3RTY29wZSA9ICRpbmplY3Rvci5nZXQoJyRyb290U2NvcGUnKTtcclxuICAgICAgICAgICAgICAgICRjb250cm9sbGVyID0gJGluamVjdG9yLmdldCgnJGNvbnRyb2xsZXInKTtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0NvbnRyb2xsZXI6IFBhZ2VDdHJsJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHNjb3BlLCBwYWdlVm0sIGJyb2FkY2FzdDtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKCRjb250cm9sbGVyLCAkcm9vdFNjb3BlKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZSA9ICRyb290U2NvcGUuJG5ldygpO1xyXG4gICAgICAgICAgICAgICAgcGFnZVZtID0gJGNvbnRyb2xsZXIoJ1BhZ2VDdHJsIGFzIHBhZ2VWbScsIHsgJHNjb3BlOiBzY29wZSB9KTtcclxuICAgICAgICAgICAgICAgIGJyb2FkY2FzdCA9IGZhbHNlO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdChcIkJyb2FkY2FzdHMgbG9hZGluZy1vbiBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2xvYWRpbmctb24nLCBicm9hZGNhc3RSZWNpZXZlZCk7XHJcbiAgICAgICAgICAgICAgICBwYWdlVm0ubG9hZGluZ09uKCk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoYnJvYWRjYXN0KS50b0JlVHJ1dGh5KCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KFwiQnJvYWRjYXN0cyBsb2FkaW5nLW9mZiBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2xvYWRpbmctb2ZmJywgYnJvYWRjYXN0UmVjaWV2ZWQpO1xyXG4gICAgICAgICAgICAgICAgcGFnZVZtLmxvYWRpbmdPZmYoKTtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChicm9hZGNhc3QpLnRvQmVUcnV0aHkoKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgaXQoXCJCcm9hZGNhc3RzIGVudGVyLW1vYmlsZSBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIGJyb2FkY2FzdFJlY2lldmVkKTtcclxuICAgICAgICAgICAgICAgIHBhZ2VWbS5lbnRlck1vYmlsZSgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGJyb2FkY2FzdCkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdChcIkJyb2FkY2FzdHMgZXhpdC1tb2JpbGUgZXZlbnRcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUuJG9uKCdleGl0LW1vYmlsZScsIGJyb2FkY2FzdFJlY2lldmVkKTtcclxuICAgICAgICAgICAgICAgIHBhZ2VWbS5leGl0TW9iaWxlKCk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoYnJvYWRjYXN0KS50b0JlVHJ1dGh5KCk7XHJcbiAgICAgICAgICAgIH0pXHJcblxyXG4gICAgICAgICAgICBmdW5jdGlvbiBicm9hZGNhc3RSZWNpZXZlZCgpIHtcclxuICAgICAgICAgICAgICAgIGJyb2FkY2FzdCA9IHRydWU7XHJcbiAgICAgICAgICAgIH1cclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkZmFjdG9yeTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0ZhY3Rvcnk6IFBhZ2UnLCBmdW5jdGlvbiAoKSB7XHJcblxyXG4gICAgICAgICAgICB2YXIgcGFnZTtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgcGFnZSA9ICRpbmplY3Rvci5nZXQoJ1BhZ2UnKTtcclxuICAgICAgICAgICAgfSkpO1xyXG5cclxuICAgICAgICAgICAgaXQoJ0dldHMgdGhlIHBhZ2UgdGl0bGUnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICB2YXIgdGl0bGU9cGFnZS5nZXRUaXRsZSgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHRpdGxlKS50b0VxdWFsKCdyZVN0YXJ0IEFuZ3VsYXIgfCBIb21lJyk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KCdTZXRzIHRoZSBwYWdlIHRpdGxlJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgcGFnZS5zZXRUaXRsZSgnTmV3Jyk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QocGFnZS5nZXRUaXRsZSgpKS50b0VxdWFsKCdyZVN0YXJ0IEFuZ3VsYXIgfCBOZXcnKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkZmFjdG9yeTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0ZhY3Rvcnk6IFV0aWxzJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHV0aWxzO1xyXG4gICAgICAgICAgICBiZWZvcmVFYWNoKGluamVjdChmdW5jdGlvbiAoJGluamVjdG9yKSB7XHJcbiAgICAgICAgICAgICAgICB1dGlscyA9ICRpbmplY3Rvci5nZXQoJ1V0aWxzJyk7XHJcbiAgICAgICAgICAgIH0pKTtcclxuXHJcbiAgICAgICAgICAgIGl0KCdHZXRzIHRoZSBncmVldGluZycsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgICAgIHZhciBncmVldGluZyA9IHV0aWxzLmdyZWV0aW5nO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGdyZWV0aW5nKS50b0VxdWFsKCdIZWxsbycpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdCgnR3JlZXRzIGdpdmVuIG5hbWUnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzcHlPbih1dGlscywnYWxlcnRHcmVldGluZycpXHJcbiAgICAgICAgICAgICAgICB1dGlscy5hbGVydEdyZWV0aW5nKCdOYW1lZCcpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHV0aWxzLmFsZXJ0R3JlZXRpbmcpLnRvSGF2ZUJlZW5DYWxsZWRXaXRoKFwiTmFtZWRcIilcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkcm9vdFNjb3BlO1xyXG4gICAgICAgIGJlZm9yZUVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBtb2R1bGUoJ3JlU3RhcnQnKTtcclxuICAgICAgICAgICAgaW5qZWN0KGZ1bmN0aW9uICgkaW5qZWN0b3IpIHtcclxuICAgICAgICAgICAgICAgICRyb290U2NvcGUgPSAkaW5qZWN0b3IuZ2V0KCckcm9vdFNjb3BlJyk7XHJcbiAgICAgICAgICAgIH0pO1xyXG4gICAgICAgIH0pO1xyXG4gICAgICAgIGRlc2NyaWJlKCdGYWN0b3J5OiBKU09ORGF0YScsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgICAgIHZhciBKU09ORGF0YTtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKF9KU09ORGF0YV8sXyRxXykge1xyXG4gICAgICAgICAgICAgICAgdmFyIGRlZmVycmVkID0gXyRxXy5kZWZlcigpO1xyXG4gICAgICAgICAgICAgICAgSlNPTkRhdGEgPSBfSlNPTkRhdGFfO1xyXG4gICAgICAgICAgICAgICAgcm9vdFNjb3BlID0gJHJvb3RTY29wZTtcclxuXHJcbiAgICAgICAgICAgICAgICBkZWZlcnJlZC5yZXNvbHZlKHsnbG9jYWwnOidkYXRhJ30pO1xyXG4gICAgICAgICAgICAgICAgc3B5T24oSlNPTkRhdGEsICdnZXRMb2NhbERhdGEnKS5hbmQucmV0dXJuVmFsdWUoZGVmZXJyZWQucHJvbWlzZSk7XHJcblxyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdCgnR2V0cyBsb2NhbCBkYXRhJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgdmFyIGRhdGE7XHJcbiAgICAgICAgICAgICAgICBKU09ORGF0YS5nZXRMb2NhbERhdGEoKS50aGVuKGZ1bmN0aW9uIChqc29uKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGpzb247XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIHJvb3RTY29wZS4kYXBwbHkoKVxyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGRhdGEubG9jYWwpLnRvRXF1YWwoJ2RhdGEnKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkZmFjdG9yeTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0ZhY3Rvcnk6IFJlcycsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgICAgIHZhciByZXM7XHJcbiAgICAgICAgICAgIGJlZm9yZUVhY2goaW5qZWN0KGZ1bmN0aW9uICgkaW5qZWN0b3IpIHtcclxuICAgICAgICAgICAgICAgIHJlcyA9ICRpbmplY3Rvci5nZXQoJ1JlcycpO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdCgnQ2hlY2tzIGlmIHJlc3BvbnNlIGlzIG9mIHR5cGUgb2JqZWN0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgdmFyIG9iaiA9IHsgZGF0YToge0lhbTonb2JqJ30gfVxyXG4gICAgICAgICAgICAgICAgdmFyIHJlc3BvbnNlID0gcmVzLnN1Y2Nlc3Mob2JqKTtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChyZXNwb25zZS5JYW0pLnRvRXF1YWwoJ29iaicpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdCgnVGhyb3dzIGFuIGVycm9yIGlmIGRhdGEgaXMgbm90IG9mIHR5cGUgb2JqJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHJlcy5zdWNjZXNzKS50b1Rocm93KCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KCdSZXNwb25kcyB0byBFcnJvcicsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChyZXMuZXJyb3IpLnRvVGhyb3coKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICB2YXIgJGNvbXBpbGUsICRyb290U2NvcGU7XHJcbiAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICBtb2R1bGUoJ3JlU3RhcnQnKTtcclxuICAgIH0pO1xyXG5cclxuICAgIGRlc2NyaWJlKCdEaXJlY3RpdmU6IExvYWRpbmcnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgdmFyIGVsZW1lbnQ7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgndGVtcGxhdGVzJyk7XHJcbiAgICAgICAgICAgIGluamVjdChcclxuICAgICAgICAgICAgICAgIFsnJGNvbXBpbGUnLCAnJHJvb3RTY29wZScsIGZ1bmN0aW9uICgkYywgJHIpIHtcclxuICAgICAgICAgICAgICAgICAgICAkY29tcGlsZSA9ICRjO1xyXG4gICAgICAgICAgICAgICAgICAgICRyb290U2NvcGUgPSAkcjtcclxuICAgICAgICAgICAgICAgIH1dXHJcbiAgICAgICAgICAgIClcclxuICAgICAgICAgICAgZWxlbWVudCA9ICRjb21waWxlKCc8bG9hZGluZz48L2xvYWRpbmc+JykoJHJvb3RTY29wZSk7XHJcbiAgICAgICAgICAgIGFuZ3VsYXIuZWxlbWVudChkb2N1bWVudC5ib2R5KS5hcHBlbmQoZWxlbWVudCk7XHJcbiAgICAgICAgICAgIC8vdHJpZ2dlciBkaXJlY3RpdmUgdG8gYmUgaW5qZWN0ZWRcclxuICAgICAgICAgICAgJHJvb3RTY29wZS4kZGlnZXN0KCk7XHJcbiAgICAgICAgfSk7XHJcblxyXG4gICAgICAgIGl0KFwic2hvdWxkIGxvYWQgdGhlIGxvYWRpbmcgdGVtcGxhdGVcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBleHBlY3QoZWxlbWVudC50ZXh0KCkpLnRvQmVGYWxzeSgpO1xyXG4gICAgICAgIH0pXHJcblxyXG4gICAgICAgIGFmdGVyRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGVsZW1lbnQucmVtb3ZlKCk7XHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICBtb2R1bGUoJ3JlU3RhcnQnKTtcclxuICAgIH0pO1xyXG4gICAgZGVzY3JpYmUoJ0ZpbHRlcjogVHJ1c3QgQXMgSFRNTCcsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgaXQoJ2hhcyBhIHR1c3QgYXMgaHRtbCBmaWx0ZXInLCBpbmplY3QoZnVuY3Rpb24gKCRmaWx0ZXIpIHtcclxuICAgICAgICAgICAgZXhwZWN0KCRmaWx0ZXIoJ3RydXN0QXNIVE1MJykpLm5vdC50b0JlTnVsbCgpO1xyXG4gICAgICAgIH0pKTtcclxuXHJcblxyXG4gICAgICAgIGl0KCd1c2VzIHNjZSB0byB0cnVzdCB0ZXh0IGFzIGh0bWwnLCBpbmplY3QoZnVuY3Rpb24gKCRmaWx0ZXIpIHtcclxuICAgICAgICAgICAgdmFyIHRydXN0QXNIVE1MID0gJGZpbHRlcigndHJ1c3RBc0hUTUwnKTtcclxuICAgICAgICAgICAgZXhwZWN0KHRydXN0QXNIVE1MKFwiPGRpdj5cIikpLnRvQmVUcnV0aHkoKTtcclxuICAgICAgICB9KSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkY29udHJvbGxlciwgJHJvb3RTY29wZTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgICAgIGluamVjdChmdW5jdGlvbiAoJGluamVjdG9yKSB7XHJcbiAgICAgICAgICAgICAgICAkcm9vdFNjb3BlID0gJGluamVjdG9yLmdldCgnJHJvb3RTY29wZScpO1xyXG4gICAgICAgICAgICAgICAgJGNvbnRyb2xsZXIgPSAkaW5qZWN0b3IuZ2V0KCckY29udHJvbGxlcicpO1xyXG4gICAgICAgICAgICB9KTtcclxuICAgICAgICB9KTtcclxuICAgICAgICBkZXNjcmliZSgnQ29udHJvbGxlcjogSGVhZGVyQ3RybCcsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgICAgIHZhciBzY29wZSwgaGVhZGVyVm07XHJcbiAgICAgICAgICAgIGJlZm9yZUVhY2goaW5qZWN0KGZ1bmN0aW9uICgkY29udHJvbGxlciwgJHJvb3RTY29wZSkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUgPSAkcm9vdFNjb3BlLiRuZXcoKTtcclxuICAgICAgICAgICAgICAgIGhlYWRlclZtID0gJGNvbnRyb2xsZXIoJ0hlYWRlckN0cmwgYXMgaGVhZGVyVm0nLCB7ICRzY29wZTogc2NvcGUgfSk7XHJcbiAgICAgICAgICAgIH0pKTtcclxuXHJcbiAgICAgICAgICAgIGl0KFwiQXBwbHkgY2xhc3MgdG8gaW5kZXggbmF2IGlmIGFjdGl2ZVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoaGVhZGVyVm0uaW5kZXhJc0FjdGl2ZSgnLycpKS50b0JlVHJ1dGh5KCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KFwiQXBwbHkgY2xhc3MgdG8gY3VycmVudGx5IGFjdGl2ZSBuYXYgaXRlbVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoaGVhZGVyVm0ubmF2SXNBY3RpdmUoJy8nKSkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICB9KTtcclxuICAgIH0pO1xyXG59KSgpOyIsIjsgKGZ1bmN0aW9uICgpIHtcclxuICAgIHZhciAkY29tcGlsZSwgJHJvb3RTY29wZSxzY29wZSxlbGVtZW50O1xyXG4gICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICB9KTtcclxuXHJcbiAgICBkZXNjcmliZSgnRGlyZWN0aXZlOiBuYXZDb250cm9sJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIGJlZm9yZUVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBtb2R1bGUoJ3RlbXBsYXRlcycpO1xyXG5cclxuICAgICAgICAgICAgLy9kaXNhYmxlIGxvYWRpbmcgZGlyZWN0aXZlP1xyXG4gICAgICAgICAgICBhbmd1bGFyLm1vZHVsZShcInJlU3RhcnRcIikuZGlyZWN0aXZlKFwibG9hZGluZ1wiLCBmdW5jdGlvbiAobG9hZGluZykge1xyXG4gICAgICAgICAgICAgICAgcmV0dXJuIHtcclxuICAgICAgICAgICAgICAgICAgICBwcmlvcml0eTogMTAwMDAwLFxyXG4gICAgICAgICAgICAgICAgICAgIHRlcm1pbmFsOiB0cnVlLFxyXG4gICAgICAgICAgICAgICAgICAgIGxpbms6IGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gZG8gbm90aGluZ1xyXG4gICAgICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgfSk7XHJcblxyXG4gICAgICAgICAgICBpbmplY3QoXHJcbiAgICAgICAgICAgICAgICBbJyRjb21waWxlJywgJyRyb290U2NvcGUnLCBmdW5jdGlvbiAoJGMsICRyKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgJGNvbXBpbGUgPSAkYztcclxuICAgICAgICAgICAgICAgICAgICAkcm9vdFNjb3BlID0gJHI7XHJcbiAgICAgICAgICAgICAgICAgICAgc2NvcGUgPSAkcm9vdFNjb3BlLiRuZXcoKTtcclxuICAgICAgICAgICAgICAgIH1dXHJcbiAgICAgICAgICAgIClcclxuICAgICAgICAgICAgZWxlbWVudCA9ICRjb21waWxlKCc8ZGl2IG5hdi1jb250cm9sPjxhIGNsYXNzPVwidG9nZ2xlLW9mZmNhbnZhc1wiIG5nLWNsaWNrPVwibmF2LnRvZ2dsZU5hdigpXCI+PHNwYW4+PC9zcGFuPjwvYT48L2Rpdj4nKSgkcm9vdFNjb3BlKTtcclxuICAgICAgICAgICAgYW5ndWxhci5lbGVtZW50KGRvY3VtZW50LmJvZHkpLmFwcGVuZChlbGVtZW50KTtcclxuICAgICAgICAgICAgJCgnbG9hZGluZycpLnJlbW92ZSgpO1xyXG4gICAgICAgICAgICAvL3RyaWdnZXIgZGlyZWN0aXZlIHRvIGJlIGluamVjdGVkXHJcbiAgICAgICAgICAgICRyb290U2NvcGUuJGRpZ2VzdCgpO1xyXG4gICAgICAgIH0pO1xyXG5cclxuICAgICAgICB4aXQoXCJ0b2dnbGVzIG5hdiBvbiBhbmQgb2ZmXCIsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgYW5ndWxhci5lbGVtZW50KCcudG9nZ2xlLW9mZmNhbnZhcycpLmNsaWNrKCk7XHJcbiAgICAgICAgICAgICRyb290U2NvcGUuJGRpZ2VzdCgpO1xyXG4gICAgICAgICAgICBjb25zb2xlLmxvZyhhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKVswXSlcclxuICAgICAgICAgICAgZXhwZWN0KGFuZ3VsYXIuZWxlbWVudCgnYm9keScpLmhhc0NsYXNzKCduYXYtY2xvc2VkJykpLnRvQmVGYWxzeSgpO1xyXG4gICAgICAgIH0pXHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkY29udHJvbGxlciwgJHJvb3RTY29wZTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgICAgIGluamVjdChmdW5jdGlvbiAoJGluamVjdG9yKSB7XHJcbiAgICAgICAgICAgICAgICAkcm9vdFNjb3BlID0gJGluamVjdG9yLmdldCgnJHJvb3RTY29wZScpO1xyXG4gICAgICAgICAgICAgICAgJGNvbnRyb2xsZXIgPSAkaW5qZWN0b3IuZ2V0KCckY29udHJvbGxlcicpO1xyXG4gICAgICAgICAgICB9KTtcclxuICAgICAgICB9KTtcclxuICAgICAgICBkZXNjcmliZSgnQ29udHJvbGxlcjogRXJyb3I0MDRDdHJsJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHNjb3BlLCBFcnJvcjQwNFZtLCBicm9hZGNhc3Q7XHJcbiAgICAgICAgICAgIGJlZm9yZUVhY2goaW5qZWN0KGZ1bmN0aW9uICgkY29udHJvbGxlciwgJHJvb3RTY29wZSkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUgPSAkcm9vdFNjb3BlLiRuZXcoKTtcclxuICAgICAgICAgICAgICAgIEVycm9yNDA0Vm0gPSAkY29udHJvbGxlcignRXJyb3I0MDRDdHJsIGFzIEVycm9yNDA0Vm0nLCB7ICRzY29wZTogc2NvcGUgfSk7XHJcbiAgICAgICAgICAgICAgICBicm9hZGNhc3QgPSBmYWxzZTtcclxuICAgICAgICAgICAgfSkpO1xyXG5cclxuICAgICAgICAgICAgaXQoXCJTZW5kcyBhIGxvYWRpbmctb2ZmIG1lc3NhZ2VcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUuJG9uKCdsb2FkaW5nLW9mZicsIGJyb2FkY2FzdFJlY2lldmVkKTtcclxuICAgICAgICAgICAgICAgIEVycm9yNDA0Vm0uaW5pdCgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGJyb2FkY2FzdCkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICAgICAgZnVuY3Rpb24gYnJvYWRjYXN0UmVjaWV2ZWQoKSB7XHJcbiAgICAgICAgICAgICAgICBicm9hZGNhc3QgPSB0cnVlO1xyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgIH0pO1xyXG4gICAgfSk7XHJcbn0pKCk7IiwiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgZGVzY3JpYmUoJ01vZHVsZTogcmVTdGFydCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgJGNvbnRyb2xsZXIsICRyb290U2NvcGU7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgICAgICAgICBpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgJHJvb3RTY29wZSA9ICRpbmplY3Rvci5nZXQoJyRyb290U2NvcGUnKTtcclxuICAgICAgICAgICAgICAgICRjb250cm9sbGVyID0gJGluamVjdG9yLmdldCgnJGNvbnRyb2xsZXInKTtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0NvbnRyb2xsZXI6IEhvbWVDdHJsJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHNjb3BlLCBob21lVm0sIGJyb2FkY2FzdDtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKCRjb250cm9sbGVyLCAkcm9vdFNjb3BlKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZSA9ICRyb290U2NvcGUuJG5ldygpO1xyXG4gICAgICAgICAgICAgICAgaG9tZVZtID0gJGNvbnRyb2xsZXIoJ0hvbWVDdHJsIGFzIGhvbWVWbScsIHsgJHNjb3BlOiBzY29wZSB9KTtcclxuICAgICAgICAgICAgICAgIGJyb2FkY2FzdCA9IGZhbHNlO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdChcIkJyb2FkY2FzdHMgbG9hZGluZy1vbiBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2xvYWRpbmctb24nLCBicm9hZGNhc3RSZWNpZXZlZCk7XHJcbiAgICAgICAgICAgICAgICBob21lVm0uYWN0aXZhdGUoKTtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChicm9hZGNhc3QpLnRvQmVUcnV0aHkoKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgaXQoXCJsb2FkcyBkYXRhIGludG8gY29udHJvbGxlclwiLCBmdW5jdGlvbiAoKSB7ICAgICAgICAgICAgICBcclxuICAgICAgICAgICAgICAgIGV4cGVjdChob21lVm0uZ2V0SnNvblN1Y2Vzcyh7IGRhdGE6IFwieW9cIiB9KSkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdChcIlNldHMgdmlldyBmb3JtYXQgdG8gc21hbGxcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgaG9tZVZtLmVudGVyTW9iaWxlKCk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoaG9tZVZtLmdldFZpZXcoKSkudG9FcXVhbChcInNtYWxsXCIpXHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KFwiU2V0cyB2aWV3IGZvcm1hdCB0byBsYXJnZVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBob21lVm0uZXhpdE1vYmlsZSgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGhvbWVWbS5nZXRWaWV3KCkpLnRvRXF1YWwoXCJsYXJnZVwiKVxyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICAgICAgZnVuY3Rpb24gYnJvYWRjYXN0UmVjaWV2ZWQoKSB7XHJcbiAgICAgICAgICAgICAgICBicm9hZGNhc3QgPSB0cnVlO1xyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgIH0pO1xyXG4gICAgfSk7XHJcbn0pKCk7IiwiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgdmFyICRjb21waWxlLCAkcm9vdFNjb3BlLCBzY29wZSwgZWxlbWVudDtcclxuICAgIGJlZm9yZUVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgfSk7XHJcblxyXG4gICAgZGVzY3JpYmUoJ0RpcmVjdGl2ZTogbmF2Q29udHJvbCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCd0ZW1wbGF0ZXMnKTtcclxuXHJcbiAgICAgICAgICAgIGluamVjdChcclxuICAgICAgICAgICAgICAgIFsnJGNvbXBpbGUnLCAnJHJvb3RTY29wZScsIGZ1bmN0aW9uICgkYywgJHIpIHtcclxuICAgICAgICAgICAgICAgICAgICAkY29tcGlsZSA9ICRjO1xyXG4gICAgICAgICAgICAgICAgICAgICRyb290U2NvcGUgPSAkcjtcclxuICAgICAgICAgICAgICAgICAgICBzY29wZSA9ICRyb290U2NvcGUuJG5ldygpO1xyXG4gICAgICAgICAgICAgICAgfV1cclxuICAgICAgICAgICAgKVxyXG4gICAgICAgICAgICBlbGVtZW50ID0gJGNvbXBpbGUoJzxzYW1wbGUtZGlyZWN0aXZlIGpzb24tZGF0YT1cInN1Yi5qc29uXCI+JytcIkkndmUgYmVlbiB0cmFuc2NsdWRlZCFcIisnPC9zYW1wbGUtZGlyZWN0aXZlPicpKCRyb290U2NvcGUpO1xyXG4gICAgICAgICAgICBhbmd1bGFyLmVsZW1lbnQoZG9jdW1lbnQuYm9keSkuYXBwZW5kKGVsZW1lbnQpO1xyXG4gICAgICAgICAgICAvL3RyaWdnZXIgZGlyZWN0aXZlIHRvIGJlIGluamVjdGVkXHJcbiAgICAgICAgICAgICRyb290U2NvcGUuJGRpZ2VzdCgpO1xyXG4gICAgICAgIH0pO1xyXG5cclxuICAgICAgICBpdChcImluamVjdHMgYSB0ZW1wbGF0ZSB2aWEgZGlyZWN0aXZlXCIsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgZXhwZWN0KGFuZ3VsYXIuZWxlbWVudCgnYm9keScpLmh0bWwoKSkudG9Db250YWluKFwiaW5jbHVkZWQgYnkgYSBkaXJlY3RpdmVcIik7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgaXQoXCJ0cmFuc2NsdWRlcyBkYXRhIHRvIHRoZSBpbmplY3RlZCB0ZW1wbGF0ZVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGV4cGVjdChhbmd1bGFyLmVsZW1lbnQoJy50cmFuc2NsdWRlJykuaHRtbCgpKS50b0NvbnRhaW4oXCJJJ3ZlIGJlZW4gdHJhbnNjbHVkZWQhXCIpO1xyXG4gICAgICAgIH0pO1xyXG4gICAgfSk7XHJcbn0pKCk7IiwiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgZGVzY3JpYmUoJ01vZHVsZTogcmVTdGFydCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgJGNvbnRyb2xsZXIsICRyb290U2NvcGU7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgICAgICAgICBpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgJHJvb3RTY29wZSA9ICRpbmplY3Rvci5nZXQoJyRyb290U2NvcGUnKTtcclxuICAgICAgICAgICAgICAgICRjb250cm9sbGVyID0gJGluamVjdG9yLmdldCgnJGNvbnRyb2xsZXInKTtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0NvbnRyb2xsZXI6IFN1YkN0cmwnLCBmdW5jdGlvbiAoKSB7XHJcblxyXG4gICAgICAgICAgICB2YXIgc2NvcGUsIHN1YlZtO1xyXG4gICAgICAgICAgICBiZWZvcmVFYWNoKGluamVjdChmdW5jdGlvbiAoJGNvbnRyb2xsZXIsICRyb290U2NvcGUpIHtcclxuICAgICAgICAgICAgICAgIHNjb3BlID0gJHJvb3RTY29wZS4kbmV3KCk7XHJcbiAgICAgICAgICAgICAgICBzdWJWbSA9ICRjb250cm9sbGVyKCdTdWJDdHJsIGFzIHN1YlZtJywgeyAkc2NvcGU6IHNjb3BlLCAnVXRpbHMnOicnLCAncmVzb2x2ZUxvY2FsRGF0YSc6e3N0dWZmOlwiQW5kIFRoaW5nc1wifX0pO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdChcIlNldHMgdGhlIHBhZ2UgdGl0bGVcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHN1YlZtLnRpdGxlKS50b0VxdWFsKFwiU3VicGFnZVwiKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNvcmUvUGFnZS5jdHJsLnNwZWMuanMiLCJjb3JlL1BhZ2UuZmFjdG9yeS5zcGVjLmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LnNwZWMuanMiLCJtb2R1bGVzL2hlYWRlci9IZWFkZXIuY3RybC5zcGVjLmpzIiwibW9kdWxlcy9oZWFkZXIvbmF2Q29udHJvbC5kaXIuc3BlYy5qcyIsImNvcmUvZ2V0LWRhdGEvSlNPTkRhdGEuZmFjdG9yeS5zcGVjLmpzIiwiY29yZS9nZXQtZGF0YS9SZXMuZmFjdG9yeS5zcGVjLmpzIiwiY29yZS91aS9sb2FkaW5nLmRpci5zcGVjLmpzIiwiY29yZS91aS90cnVzdEFzSFRNTC5maWx0ZXIuc3BlYy5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuc3BlYy5qcyIsInBhZ2VzL2hvbWUvSG9tZS5jdHJsLnNwZWMuanMiLCJwYWdlcy9zdWIvc2FtcGxlLmRpci5zcGVjLmpzIiwicGFnZXMvc3ViL1N1Yi5jdHJsLnNwZWMuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzlDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDekJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDM0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDMUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDakNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDM0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDOUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQy9CQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzlCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJyZVN0YXJ0LWFwcC5zcGVjLmpzIiwic291cmNlc0NvbnRlbnQiOlsiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgZGVzY3JpYmUoJ01vZHVsZTogcmVTdGFydCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgJGNvbnRyb2xsZXIsICRyb290U2NvcGU7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgICAgICAgICBpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgJHJvb3RTY29wZSA9ICRpbmplY3Rvci5nZXQoJyRyb290U2NvcGUnKTtcclxuICAgICAgICAgICAgICAgICRjb250cm9sbGVyID0gJGluamVjdG9yLmdldCgnJGNvbnRyb2xsZXInKTtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0NvbnRyb2xsZXI6IFBhZ2VDdHJsJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHNjb3BlLCBwYWdlVm0sIGJyb2FkY2FzdDtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKCRjb250cm9sbGVyLCAkcm9vdFNjb3BlKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZSA9ICRyb290U2NvcGUuJG5ldygpO1xyXG4gICAgICAgICAgICAgICAgcGFnZVZtID0gJGNvbnRyb2xsZXIoJ1BhZ2VDdHJsIGFzIHBhZ2VWbScsIHsgJHNjb3BlOiBzY29wZSB9KTtcclxuICAgICAgICAgICAgICAgIGJyb2FkY2FzdCA9IGZhbHNlO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdChcIkJyb2FkY2FzdHMgbG9hZGluZy1vbiBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2xvYWRpbmctb24nLCBicm9hZGNhc3RSZWNpZXZlZCk7XHJcbiAgICAgICAgICAgICAgICBwYWdlVm0ubG9hZGluZ09uKCk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoYnJvYWRjYXN0KS50b0JlVHJ1dGh5KCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KFwiQnJvYWRjYXN0cyBsb2FkaW5nLW9mZiBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2xvYWRpbmctb2ZmJywgYnJvYWRjYXN0UmVjaWV2ZWQpO1xyXG4gICAgICAgICAgICAgICAgcGFnZVZtLmxvYWRpbmdPZmYoKTtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChicm9hZGNhc3QpLnRvQmVUcnV0aHkoKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgaXQoXCJCcm9hZGNhc3RzIGVudGVyLW1vYmlsZSBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIGJyb2FkY2FzdFJlY2lldmVkKTtcclxuICAgICAgICAgICAgICAgIHBhZ2VWbS5lbnRlck1vYmlsZSgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGJyb2FkY2FzdCkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdChcIkJyb2FkY2FzdHMgZXhpdC1tb2JpbGUgZXZlbnRcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUuJG9uKCdleGl0LW1vYmlsZScsIGJyb2FkY2FzdFJlY2lldmVkKTtcclxuICAgICAgICAgICAgICAgIHBhZ2VWbS5leGl0TW9iaWxlKCk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoYnJvYWRjYXN0KS50b0JlVHJ1dGh5KCk7XHJcbiAgICAgICAgICAgIH0pXHJcblxyXG4gICAgICAgICAgICBmdW5jdGlvbiBicm9hZGNhc3RSZWNpZXZlZCgpIHtcclxuICAgICAgICAgICAgICAgIGJyb2FkY2FzdCA9IHRydWU7XHJcbiAgICAgICAgICAgIH1cclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkZmFjdG9yeTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0ZhY3Rvcnk6IFBhZ2UnLCBmdW5jdGlvbiAoKSB7XHJcblxyXG4gICAgICAgICAgICB2YXIgcGFnZTtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgcGFnZSA9ICRpbmplY3Rvci5nZXQoJ1BhZ2UnKTtcclxuICAgICAgICAgICAgfSkpO1xyXG5cclxuICAgICAgICAgICAgaXQoJ0dldHMgdGhlIHBhZ2UgdGl0bGUnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICB2YXIgdGl0bGU9cGFnZS5nZXRUaXRsZSgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHRpdGxlKS50b0VxdWFsKCdyZVN0YXJ0IEFuZ3VsYXIgfCBIb21lJyk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KCdTZXRzIHRoZSBwYWdlIHRpdGxlJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgcGFnZS5zZXRUaXRsZSgnTmV3Jyk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QocGFnZS5nZXRUaXRsZSgpKS50b0VxdWFsKCdyZVN0YXJ0IEFuZ3VsYXIgfCBOZXcnKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkZmFjdG9yeTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0ZhY3Rvcnk6IFV0aWxzJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHV0aWxzO1xyXG4gICAgICAgICAgICBiZWZvcmVFYWNoKGluamVjdChmdW5jdGlvbiAoJGluamVjdG9yKSB7XHJcbiAgICAgICAgICAgICAgICB1dGlscyA9ICRpbmplY3Rvci5nZXQoJ1V0aWxzJyk7XHJcbiAgICAgICAgICAgIH0pKTtcclxuXHJcbiAgICAgICAgICAgIGl0KCdHZXRzIHRoZSBncmVldGluZycsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgICAgIHZhciBncmVldGluZyA9IHV0aWxzLmdyZWV0aW5nO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGdyZWV0aW5nKS50b0VxdWFsKCdIZWxsbycpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdCgnR3JlZXRzIGdpdmVuIG5hbWUnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzcHlPbih1dGlscywnYWxlcnRHcmVldGluZycpXHJcbiAgICAgICAgICAgICAgICB1dGlscy5hbGVydEdyZWV0aW5nKCdOYW1lZCcpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHV0aWxzLmFsZXJ0R3JlZXRpbmcpLnRvSGF2ZUJlZW5DYWxsZWRXaXRoKFwiTmFtZWRcIilcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkY29udHJvbGxlciwgJHJvb3RTY29wZTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgICAgIGluamVjdChmdW5jdGlvbiAoJGluamVjdG9yKSB7XHJcbiAgICAgICAgICAgICAgICAkcm9vdFNjb3BlID0gJGluamVjdG9yLmdldCgnJHJvb3RTY29wZScpO1xyXG4gICAgICAgICAgICAgICAgJGNvbnRyb2xsZXIgPSAkaW5qZWN0b3IuZ2V0KCckY29udHJvbGxlcicpO1xyXG4gICAgICAgICAgICB9KTtcclxuICAgICAgICB9KTtcclxuICAgICAgICBkZXNjcmliZSgnQ29udHJvbGxlcjogSGVhZGVyQ3RybCcsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgICAgIHZhciBzY29wZSwgaGVhZGVyVm07XHJcbiAgICAgICAgICAgIGJlZm9yZUVhY2goaW5qZWN0KGZ1bmN0aW9uICgkY29udHJvbGxlciwgJHJvb3RTY29wZSkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUgPSAkcm9vdFNjb3BlLiRuZXcoKTtcclxuICAgICAgICAgICAgICAgIGhlYWRlclZtID0gJGNvbnRyb2xsZXIoJ0hlYWRlckN0cmwgYXMgaGVhZGVyVm0nLCB7ICRzY29wZTogc2NvcGUgfSk7XHJcbiAgICAgICAgICAgIH0pKTtcclxuXHJcbiAgICAgICAgICAgIGl0KFwiQXBwbHkgY2xhc3MgdG8gaW5kZXggbmF2IGlmIGFjdGl2ZVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoaGVhZGVyVm0uaW5kZXhJc0FjdGl2ZSgnLycpKS50b0JlVHJ1dGh5KCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KFwiQXBwbHkgY2xhc3MgdG8gY3VycmVudGx5IGFjdGl2ZSBuYXYgaXRlbVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoaGVhZGVyVm0ubmF2SXNBY3RpdmUoJy8nKSkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICB9KTtcclxuICAgIH0pO1xyXG59KSgpOyIsIjsgKGZ1bmN0aW9uICgpIHtcclxuICAgIHZhciAkY29tcGlsZSwgJHJvb3RTY29wZSxzY29wZSxlbGVtZW50O1xyXG4gICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICB9KTtcclxuXHJcbiAgICBkZXNjcmliZSgnRGlyZWN0aXZlOiBuYXZDb250cm9sJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIGJlZm9yZUVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBtb2R1bGUoJ3RlbXBsYXRlcycpO1xyXG5cclxuICAgICAgICAgICAgLy9kaXNhYmxlIGxvYWRpbmcgZGlyZWN0aXZlP1xyXG4gICAgICAgICAgICBhbmd1bGFyLm1vZHVsZShcInJlU3RhcnRcIikuZGlyZWN0aXZlKFwibG9hZGluZ1wiLCBmdW5jdGlvbiAobG9hZGluZykge1xyXG4gICAgICAgICAgICAgICAgcmV0dXJuIHtcclxuICAgICAgICAgICAgICAgICAgICBwcmlvcml0eTogMTAwMDAwLFxyXG4gICAgICAgICAgICAgICAgICAgIHRlcm1pbmFsOiB0cnVlLFxyXG4gICAgICAgICAgICAgICAgICAgIGxpbms6IGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gZG8gbm90aGluZ1xyXG4gICAgICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgfSk7XHJcblxyXG4gICAgICAgICAgICBpbmplY3QoXHJcbiAgICAgICAgICAgICAgICBbJyRjb21waWxlJywgJyRyb290U2NvcGUnLCBmdW5jdGlvbiAoJGMsICRyKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgJGNvbXBpbGUgPSAkYztcclxuICAgICAgICAgICAgICAgICAgICAkcm9vdFNjb3BlID0gJHI7XHJcbiAgICAgICAgICAgICAgICAgICAgc2NvcGUgPSAkcm9vdFNjb3BlLiRuZXcoKTtcclxuICAgICAgICAgICAgICAgIH1dXHJcbiAgICAgICAgICAgIClcclxuICAgICAgICAgICAgZWxlbWVudCA9ICRjb21waWxlKCc8ZGl2IG5hdi1jb250cm9sPjxhIGNsYXNzPVwidG9nZ2xlLW9mZmNhbnZhc1wiIG5nLWNsaWNrPVwibmF2LnRvZ2dsZU5hdigpXCI+PHNwYW4+PC9zcGFuPjwvYT48L2Rpdj4nKSgkcm9vdFNjb3BlKTtcclxuICAgICAgICAgICAgYW5ndWxhci5lbGVtZW50KGRvY3VtZW50LmJvZHkpLmFwcGVuZChlbGVtZW50KTtcclxuICAgICAgICAgICAgJCgnbG9hZGluZycpLnJlbW92ZSgpO1xyXG4gICAgICAgICAgICAvL3RyaWdnZXIgZGlyZWN0aXZlIHRvIGJlIGluamVjdGVkXHJcbiAgICAgICAgICAgICRyb290U2NvcGUuJGRpZ2VzdCgpO1xyXG4gICAgICAgIH0pO1xyXG5cclxuICAgICAgICB4aXQoXCJ0b2dnbGVzIG5hdiBvbiBhbmQgb2ZmXCIsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgYW5ndWxhci5lbGVtZW50KCcudG9nZ2xlLW9mZmNhbnZhcycpLmNsaWNrKCk7XHJcbiAgICAgICAgICAgICRyb290U2NvcGUuJGRpZ2VzdCgpO1xyXG4gICAgICAgICAgICBjb25zb2xlLmxvZyhhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKVswXSlcclxuICAgICAgICAgICAgZXhwZWN0KGFuZ3VsYXIuZWxlbWVudCgnYm9keScpLmhhc0NsYXNzKCduYXYtY2xvc2VkJykpLnRvQmVGYWxzeSgpO1xyXG4gICAgICAgIH0pXHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkcm9vdFNjb3BlO1xyXG4gICAgICAgIGJlZm9yZUVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBtb2R1bGUoJ3JlU3RhcnQnKTtcclxuICAgICAgICAgICAgaW5qZWN0KGZ1bmN0aW9uICgkaW5qZWN0b3IpIHtcclxuICAgICAgICAgICAgICAgICRyb290U2NvcGUgPSAkaW5qZWN0b3IuZ2V0KCckcm9vdFNjb3BlJyk7XHJcbiAgICAgICAgICAgIH0pO1xyXG4gICAgICAgIH0pO1xyXG4gICAgICAgIGRlc2NyaWJlKCdGYWN0b3J5OiBKU09ORGF0YScsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgICAgIHZhciBKU09ORGF0YTtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKF9KU09ORGF0YV8sXyRxXykge1xyXG4gICAgICAgICAgICAgICAgdmFyIGRlZmVycmVkID0gXyRxXy5kZWZlcigpO1xyXG4gICAgICAgICAgICAgICAgSlNPTkRhdGEgPSBfSlNPTkRhdGFfO1xyXG4gICAgICAgICAgICAgICAgcm9vdFNjb3BlID0gJHJvb3RTY29wZTtcclxuXHJcbiAgICAgICAgICAgICAgICBkZWZlcnJlZC5yZXNvbHZlKHsnbG9jYWwnOidkYXRhJ30pO1xyXG4gICAgICAgICAgICAgICAgc3B5T24oSlNPTkRhdGEsICdnZXRMb2NhbERhdGEnKS5hbmQucmV0dXJuVmFsdWUoZGVmZXJyZWQucHJvbWlzZSk7XHJcblxyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdCgnR2V0cyBsb2NhbCBkYXRhJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgdmFyIGRhdGE7XHJcbiAgICAgICAgICAgICAgICBKU09ORGF0YS5nZXRMb2NhbERhdGEoKS50aGVuKGZ1bmN0aW9uIChqc29uKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGpzb247XHJcbiAgICAgICAgICAgICAgICB9KTtcclxuICAgICAgICAgICAgICAgIHJvb3RTY29wZS4kYXBwbHkoKVxyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGRhdGEubG9jYWwpLnRvRXF1YWwoJ2RhdGEnKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkZmFjdG9yeTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0ZhY3Rvcnk6IFJlcycsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgICAgIHZhciByZXM7XHJcbiAgICAgICAgICAgIGJlZm9yZUVhY2goaW5qZWN0KGZ1bmN0aW9uICgkaW5qZWN0b3IpIHtcclxuICAgICAgICAgICAgICAgIHJlcyA9ICRpbmplY3Rvci5nZXQoJ1JlcycpO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdCgnQ2hlY2tzIGlmIHJlc3BvbnNlIGlzIG9mIHR5cGUgb2JqZWN0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgdmFyIG9iaiA9IHsgZGF0YToge0lhbTonb2JqJ30gfVxyXG4gICAgICAgICAgICAgICAgdmFyIHJlc3BvbnNlID0gcmVzLnN1Y2Nlc3Mob2JqKTtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChyZXNwb25zZS5JYW0pLnRvRXF1YWwoJ29iaicpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdCgnVGhyb3dzIGFuIGVycm9yIGlmIGRhdGEgaXMgbm90IG9mIHR5cGUgb2JqJywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHJlcy5zdWNjZXNzKS50b1Rocm93KCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KCdSZXNwb25kcyB0byBFcnJvcicsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChyZXMuZXJyb3IpLnRvVGhyb3coKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICB2YXIgJGNvbXBpbGUsICRyb290U2NvcGU7XHJcbiAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICBtb2R1bGUoJ3JlU3RhcnQnKTtcclxuICAgIH0pO1xyXG5cclxuICAgIGRlc2NyaWJlKCdEaXJlY3RpdmU6IExvYWRpbmcnLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgdmFyIGVsZW1lbnQ7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgndGVtcGxhdGVzJyk7XHJcbiAgICAgICAgICAgIGluamVjdChcclxuICAgICAgICAgICAgICAgIFsnJGNvbXBpbGUnLCAnJHJvb3RTY29wZScsIGZ1bmN0aW9uICgkYywgJHIpIHtcclxuICAgICAgICAgICAgICAgICAgICAkY29tcGlsZSA9ICRjO1xyXG4gICAgICAgICAgICAgICAgICAgICRyb290U2NvcGUgPSAkcjtcclxuICAgICAgICAgICAgICAgIH1dXHJcbiAgICAgICAgICAgIClcclxuICAgICAgICAgICAgZWxlbWVudCA9ICRjb21waWxlKCc8bG9hZGluZz48L2xvYWRpbmc+JykoJHJvb3RTY29wZSk7XHJcbiAgICAgICAgICAgIGFuZ3VsYXIuZWxlbWVudChkb2N1bWVudC5ib2R5KS5hcHBlbmQoZWxlbWVudCk7XHJcbiAgICAgICAgICAgIC8vdHJpZ2dlciBkaXJlY3RpdmUgdG8gYmUgaW5qZWN0ZWRcclxuICAgICAgICAgICAgJHJvb3RTY29wZS4kZGlnZXN0KCk7XHJcbiAgICAgICAgfSk7XHJcblxyXG4gICAgICAgIGl0KFwic2hvdWxkIGxvYWQgdGhlIGxvYWRpbmcgdGVtcGxhdGVcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICBleHBlY3QoZWxlbWVudC50ZXh0KCkpLnRvQmVGYWxzeSgpO1xyXG4gICAgICAgIH0pXHJcblxyXG4gICAgICAgIGFmdGVyRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGVsZW1lbnQucmVtb3ZlKCk7XHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICBtb2R1bGUoJ3JlU3RhcnQnKTtcclxuICAgIH0pO1xyXG4gICAgZGVzY3JpYmUoJ0ZpbHRlcjogVHJ1c3QgQXMgSFRNTCcsIGZ1bmN0aW9uICgpIHtcclxuXHJcbiAgICAgICAgaXQoJ2hhcyBhIHR1c3QgYXMgaHRtbCBmaWx0ZXInLCBpbmplY3QoZnVuY3Rpb24gKCRmaWx0ZXIpIHtcclxuICAgICAgICAgICAgZXhwZWN0KCRmaWx0ZXIoJ3RydXN0QXNIVE1MJykpLm5vdC50b0JlTnVsbCgpO1xyXG4gICAgICAgIH0pKTtcclxuXHJcblxyXG4gICAgICAgIGl0KCd1c2VzIHNjZSB0byB0cnVzdCB0ZXh0IGFzIGh0bWwnLCBpbmplY3QoZnVuY3Rpb24gKCRmaWx0ZXIpIHtcclxuICAgICAgICAgICAgdmFyIHRydXN0QXNIVE1MID0gJGZpbHRlcigndHJ1c3RBc0hUTUwnKTtcclxuICAgICAgICAgICAgZXhwZWN0KHRydXN0QXNIVE1MKFwiPGRpdj5cIikpLnRvQmVUcnV0aHkoKTtcclxuICAgICAgICB9KSk7XHJcbiAgICB9KTtcclxufSkoKTsiLCI7IChmdW5jdGlvbiAoKSB7XHJcbiAgICBkZXNjcmliZSgnTW9kdWxlOiByZVN0YXJ0JywgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIHZhciAkY29udHJvbGxlciwgJHJvb3RTY29wZTtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCdyZVN0YXJ0Jyk7XHJcbiAgICAgICAgICAgIGluamVjdChmdW5jdGlvbiAoJGluamVjdG9yKSB7XHJcbiAgICAgICAgICAgICAgICAkcm9vdFNjb3BlID0gJGluamVjdG9yLmdldCgnJHJvb3RTY29wZScpO1xyXG4gICAgICAgICAgICAgICAgJGNvbnRyb2xsZXIgPSAkaW5qZWN0b3IuZ2V0KCckY29udHJvbGxlcicpO1xyXG4gICAgICAgICAgICB9KTtcclxuICAgICAgICB9KTtcclxuICAgICAgICBkZXNjcmliZSgnQ29udHJvbGxlcjogRXJyb3I0MDRDdHJsJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHNjb3BlLCBFcnJvcjQwNFZtLCBicm9hZGNhc3Q7XHJcbiAgICAgICAgICAgIGJlZm9yZUVhY2goaW5qZWN0KGZ1bmN0aW9uICgkY29udHJvbGxlciwgJHJvb3RTY29wZSkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUgPSAkcm9vdFNjb3BlLiRuZXcoKTtcclxuICAgICAgICAgICAgICAgIEVycm9yNDA0Vm0gPSAkY29udHJvbGxlcignRXJyb3I0MDRDdHJsIGFzIEVycm9yNDA0Vm0nLCB7ICRzY29wZTogc2NvcGUgfSk7XHJcbiAgICAgICAgICAgICAgICBicm9hZGNhc3QgPSBmYWxzZTtcclxuICAgICAgICAgICAgfSkpO1xyXG5cclxuICAgICAgICAgICAgaXQoXCJTZW5kcyBhIGxvYWRpbmctb2ZmIG1lc3NhZ2VcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgc2NvcGUuJG9uKCdsb2FkaW5nLW9mZicsIGJyb2FkY2FzdFJlY2lldmVkKTtcclxuICAgICAgICAgICAgICAgIEVycm9yNDA0Vm0uaW5pdCgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGJyb2FkY2FzdCkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICAgICAgZnVuY3Rpb24gYnJvYWRjYXN0UmVjaWV2ZWQoKSB7XHJcbiAgICAgICAgICAgICAgICBicm9hZGNhc3QgPSB0cnVlO1xyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgIH0pO1xyXG4gICAgfSk7XHJcbn0pKCk7IiwiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgZGVzY3JpYmUoJ01vZHVsZTogcmVTdGFydCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgJGNvbnRyb2xsZXIsICRyb290U2NvcGU7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgICAgICAgICBpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgJHJvb3RTY29wZSA9ICRpbmplY3Rvci5nZXQoJyRyb290U2NvcGUnKTtcclxuICAgICAgICAgICAgICAgICRjb250cm9sbGVyID0gJGluamVjdG9yLmdldCgnJGNvbnRyb2xsZXInKTtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0NvbnRyb2xsZXI6IEhvbWVDdHJsJywgZnVuY3Rpb24gKCkge1xyXG5cclxuICAgICAgICAgICAgdmFyIHNjb3BlLCBob21lVm0sIGJyb2FkY2FzdDtcclxuICAgICAgICAgICAgYmVmb3JlRWFjaChpbmplY3QoZnVuY3Rpb24gKCRjb250cm9sbGVyLCAkcm9vdFNjb3BlKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZSA9ICRyb290U2NvcGUuJG5ldygpO1xyXG4gICAgICAgICAgICAgICAgaG9tZVZtID0gJGNvbnRyb2xsZXIoJ0hvbWVDdHJsIGFzIGhvbWVWbScsIHsgJHNjb3BlOiBzY29wZSB9KTtcclxuICAgICAgICAgICAgICAgIGJyb2FkY2FzdCA9IGZhbHNlO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdChcIkJyb2FkY2FzdHMgbG9hZGluZy1vbiBldmVudFwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBzY29wZS4kb24oJ2xvYWRpbmctb24nLCBicm9hZGNhc3RSZWNpZXZlZCk7XHJcbiAgICAgICAgICAgICAgICBob21lVm0uYWN0aXZhdGUoKTtcclxuICAgICAgICAgICAgICAgIGV4cGVjdChicm9hZGNhc3QpLnRvQmVUcnV0aHkoKTtcclxuICAgICAgICAgICAgfSlcclxuICAgICAgICAgICAgaXQoXCJsb2FkcyBkYXRhIGludG8gY29udHJvbGxlclwiLCBmdW5jdGlvbiAoKSB7ICAgICAgICAgICAgICBcclxuICAgICAgICAgICAgICAgIGV4cGVjdChob21lVm0uZ2V0SnNvblN1Y2Vzcyh7IGRhdGE6IFwieW9cIiB9KSkudG9CZVRydXRoeSgpO1xyXG4gICAgICAgICAgICB9KVxyXG4gICAgICAgICAgICBpdChcIlNldHMgdmlldyBmb3JtYXQgdG8gc21hbGxcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgaG9tZVZtLmVudGVyTW9iaWxlKCk7XHJcbiAgICAgICAgICAgICAgICBleHBlY3QoaG9tZVZtLmdldFZpZXcoKSkudG9FcXVhbChcInNtYWxsXCIpXHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgICAgIGl0KFwiU2V0cyB2aWV3IGZvcm1hdCB0byBsYXJnZVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgICAgICBob21lVm0uZXhpdE1vYmlsZSgpO1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KGhvbWVWbS5nZXRWaWV3KCkpLnRvRXF1YWwoXCJsYXJnZVwiKVxyXG4gICAgICAgICAgICB9KVxyXG5cclxuICAgICAgICAgICAgZnVuY3Rpb24gYnJvYWRjYXN0UmVjaWV2ZWQoKSB7XHJcbiAgICAgICAgICAgICAgICBicm9hZGNhc3QgPSB0cnVlO1xyXG4gICAgICAgICAgICB9XHJcblxyXG4gICAgICAgIH0pO1xyXG4gICAgfSk7XHJcbn0pKCk7IiwiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgdmFyICRjb21waWxlLCAkcm9vdFNjb3BlLCBzY29wZSwgZWxlbWVudDtcclxuICAgIGJlZm9yZUVhY2goZnVuY3Rpb24gKCkge1xyXG4gICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgfSk7XHJcblxyXG4gICAgZGVzY3JpYmUoJ0RpcmVjdGl2ZTogbmF2Q29udHJvbCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICBiZWZvcmVFYWNoKGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgbW9kdWxlKCd0ZW1wbGF0ZXMnKTtcclxuXHJcbiAgICAgICAgICAgIGluamVjdChcclxuICAgICAgICAgICAgICAgIFsnJGNvbXBpbGUnLCAnJHJvb3RTY29wZScsIGZ1bmN0aW9uICgkYywgJHIpIHtcclxuICAgICAgICAgICAgICAgICAgICAkY29tcGlsZSA9ICRjO1xyXG4gICAgICAgICAgICAgICAgICAgICRyb290U2NvcGUgPSAkcjtcclxuICAgICAgICAgICAgICAgICAgICBzY29wZSA9ICRyb290U2NvcGUuJG5ldygpO1xyXG4gICAgICAgICAgICAgICAgfV1cclxuICAgICAgICAgICAgKVxyXG4gICAgICAgICAgICBlbGVtZW50ID0gJGNvbXBpbGUoJzxzYW1wbGUtZGlyZWN0aXZlIGpzb24tZGF0YT1cInN1Yi5qc29uXCI+JytcIkkndmUgYmVlbiB0cmFuc2NsdWRlZCFcIisnPC9zYW1wbGUtZGlyZWN0aXZlPicpKCRyb290U2NvcGUpO1xyXG4gICAgICAgICAgICBhbmd1bGFyLmVsZW1lbnQoZG9jdW1lbnQuYm9keSkuYXBwZW5kKGVsZW1lbnQpO1xyXG4gICAgICAgICAgICAvL3RyaWdnZXIgZGlyZWN0aXZlIHRvIGJlIGluamVjdGVkXHJcbiAgICAgICAgICAgICRyb290U2NvcGUuJGRpZ2VzdCgpO1xyXG4gICAgICAgIH0pO1xyXG5cclxuICAgICAgICBpdChcImluamVjdHMgYSB0ZW1wbGF0ZSB2aWEgZGlyZWN0aXZlXCIsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICAgICAgZXhwZWN0KGFuZ3VsYXIuZWxlbWVudCgnYm9keScpLmh0bWwoKSkudG9Db250YWluKFwiaW5jbHVkZWQgYnkgYSBkaXJlY3RpdmVcIik7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgaXQoXCJ0cmFuc2NsdWRlcyBkYXRhIHRvIHRoZSBpbmplY3RlZCB0ZW1wbGF0ZVwiLCBmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIGV4cGVjdChhbmd1bGFyLmVsZW1lbnQoJy50cmFuc2NsdWRlJykuaHRtbCgpKS50b0NvbnRhaW4oXCJJJ3ZlIGJlZW4gdHJhbnNjbHVkZWQhXCIpO1xyXG4gICAgICAgIH0pO1xyXG4gICAgfSk7XHJcbn0pKCk7IiwiOyAoZnVuY3Rpb24gKCkge1xyXG4gICAgZGVzY3JpYmUoJ01vZHVsZTogcmVTdGFydCcsIGZ1bmN0aW9uICgpIHtcclxuICAgICAgICB2YXIgJGNvbnRyb2xsZXIsICRyb290U2NvcGU7XHJcbiAgICAgICAgYmVmb3JlRWFjaChmdW5jdGlvbiAoKSB7XHJcbiAgICAgICAgICAgIG1vZHVsZSgncmVTdGFydCcpO1xyXG4gICAgICAgICAgICBpbmplY3QoZnVuY3Rpb24gKCRpbmplY3Rvcikge1xyXG4gICAgICAgICAgICAgICAgJHJvb3RTY29wZSA9ICRpbmplY3Rvci5nZXQoJyRyb290U2NvcGUnKTtcclxuICAgICAgICAgICAgICAgICRjb250cm9sbGVyID0gJGluamVjdG9yLmdldCgnJGNvbnRyb2xsZXInKTtcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICAgICAgZGVzY3JpYmUoJ0NvbnRyb2xsZXI6IFN1YkN0cmwnLCBmdW5jdGlvbiAoKSB7XHJcblxyXG4gICAgICAgICAgICB2YXIgc2NvcGUsIHN1YlZtO1xyXG4gICAgICAgICAgICBiZWZvcmVFYWNoKGluamVjdChmdW5jdGlvbiAoJGNvbnRyb2xsZXIsICRyb290U2NvcGUpIHtcclxuICAgICAgICAgICAgICAgIHNjb3BlID0gJHJvb3RTY29wZS4kbmV3KCk7XHJcbiAgICAgICAgICAgICAgICBzdWJWbSA9ICRjb250cm9sbGVyKCdTdWJDdHJsIGFzIHN1YlZtJywgeyAkc2NvcGU6IHNjb3BlLCAnVXRpbHMnOicnLCAncmVzb2x2ZUxvY2FsRGF0YSc6e3N0dWZmOlwiQW5kIFRoaW5nc1wifX0pO1xyXG4gICAgICAgICAgICB9KSk7XHJcblxyXG4gICAgICAgICAgICBpdChcIlNldHMgdGhlIHBhZ2UgdGl0bGVcIiwgZnVuY3Rpb24gKCkge1xyXG4gICAgICAgICAgICAgICAgZXhwZWN0KHN1YlZtLnRpdGxlKS50b0VxdWFsKFwiU3VicGFnZVwiKTtcclxuICAgICAgICAgICAgfSlcclxuXHJcbiAgICAgICAgfSk7XHJcbiAgICB9KTtcclxufSkoKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= From 1cf2caa843f8497b63a730bb911d956f234eacd4 Mon Sep 17 00:00:00 2001 From: Damian Strong <thewhitewolf079@gmail.com> Date: Tue, 15 Dec 2015 09:58:20 -0500 Subject: [PATCH 4/4] merged --- src/reStart-app/reStart-app.js | 484 +++++++++++++++++---------------- 1 file changed, 252 insertions(+), 232 deletions(-) diff --git a/src/reStart-app/reStart-app.js b/src/reStart-app/reStart-app.js index 94dc7af..b86054b 100644 --- a/src/reStart-app/reStart-app.js +++ b/src/reStart-app/reStart-app.js @@ -140,6 +140,11 @@ $log.error(msg); } + PageCtrl.enterMobile = _enterMobile;//test code + PageCtrl.exitMobile = _exitMobile;//test code + PageCtrl.loadingOn = _loadingOn;//test code + PageCtrl.loadingOff = _loadingOff;//test code + return PageCtrl;//test code } }()); (function() { @@ -207,6 +212,215 @@ } } }()); +(function() { + 'use strict'; + + angular + .module('reStart') + .controller('HeaderCtrl', HeaderCtrl); + + HeaderCtrl.$inject = ['$location', 'JSONData']; + + function HeaderCtrl($location, JSONData) { + // controllerAs ViewModel + var header = this; + + // bindable members + header.indexIsActive = indexIsActive; + header.navIsActive = navIsActive; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // activate controller + _activate(); + } + + /** + * Controller activate + * Get JSON data + * + * @returns {*} + * @private + */ + function _activate() { + // get the data from JSON + return JSONData.getLocalData().then(_getJsonSuccess); + } + + /** + * Successful promise data + * + * @param data {json} + * @private + */ + function _getJsonSuccess(data) { + header.json = data; + return header.json; + } + + /** + * Apply class to index nav if active + * + * @param {string} path + */ + function indexIsActive(path) { + // path should be '/' + return $location.path() === path; + } + + /** + * Apply class to currently active nav item + * + * @param {string} path + */ + function navIsActive(path) { + return $location.path().substr(0, path.length) === path; + } + } + +}()); +(function () { + 'use strict'; + + angular + .module('reStart') + .directive('navControl', navControl); + + navControl.$inject = ['$window', 'resize']; + + function navControl($window, resize) { + // return directive + return { + restrict: 'EA', + link: navControlLink + }; + + /** + * navControl LINK function + * + * @param $scope + */ + function navControlLink($scope) { + // private variables + var _$body = angular.element('body'); + var _layoutCanvas = _$body.find('.layout-canvas'); + var _navOpen; + + // data model + $scope.nav = {}; + + _init(); + + /** + * INIT function executes procedural code + * + * @private + */ + function _init() { + // initialize debounced resize + var _rs = resize.init({ + scope: $scope, + resizedFn: _resized, + debounce: 100 + }); + + $scope.$on('$locationChangeStart', _$locationChangeStart); + $scope.$on('enter-mobile', _enterMobile); + $scope.$on('exit-mobile', _exitMobile); + } + + /** + * Resized window (debounced) + * + * @private + */ + function _resized() { + _layoutCanvas.css({ + minHeight: $window.innerHeight + 'px' + }); + } + + /** + * Open mobile navigation + * + * @private + */ + function _openNav() { + _$body + .removeClass('nav-closed') + .addClass('nav-open'); + + _navOpen = true; + } + + /** + * Close mobile navigation + * + * @private + */ + function _closeNav() { + _$body + .removeClass('nav-open') + .addClass('nav-closed'); + + _navOpen = false; + } + + /** + * Toggle nav open/closed + */ + function toggleNav() { + if (!_navOpen) { + _openNav(); + } else { + _closeNav(); + } + } + + /** + * When changing location, close the nav if it's open + */ + function _$locationChangeStart() { + if (_navOpen) { + _closeNav(); + } + } + + /** + * Function to execute when entering mobile media query + * Close nav and set up menu toggling functionality + * + * @private + */ + function _enterMobile(mq) { + _closeNav(); + + // bind function to toggle mobile navigation open/closed + $scope.nav.toggleNav = toggleNav; + } + + /** + * Function to execute when exiting mobile media query + * Disable menu toggling and remove body classes + * + * @private + */ + function _exitMobile(mq) { + // unbind function to toggle mobile navigation open/closed + $scope.nav.toggleNav = null; + + _$body.removeClass('nav-closed nav-open'); + } + } + } + +}()); // application config (function() { 'use strict'; @@ -328,19 +542,6 @@ (function() { 'use strict'; - // media query constants - var MQ = { - SMALL: '(max-width: 767px)', - LARGE: '(min-width: 768px)' - }; - - angular - .module('reStart') - .constant('MQ', MQ); -}()); -(function() { - 'use strict'; - angular .module('reStart') .directive('loading', loading); @@ -501,6 +702,19 @@ (function() { 'use strict'; + // media query constants + var MQ = { + SMALL: '(max-width: 767px)', + LARGE: '(min-width: 768px)' + }; + + angular + .module('reStart') + .constant('MQ', MQ); +}()); +(function() { + 'use strict'; + angular .module('reStart') .filter('trustAsHTML', trustAsHTML); @@ -513,7 +727,7 @@ }; } }()); -(function() { +(function () { 'use strict'; angular @@ -542,9 +756,13 @@ // no data to load, but loading state might be on $scope.$emit('loading-off'); } + + return { //test code + init: _init //test code + }; //test code } }()); -(function() { +(function () { 'use strict'; angular @@ -633,37 +851,17 @@ function _exitMobile() { home.viewformat = 'large'; } - } -}()); -(function() { - 'use strict'; - angular - .module('reStart') - .controller('SubCtrl', SubCtrl); - - SubCtrl.$inject = ['Utils', 'Page', 'resolveLocalData']; + function getView() { //test code + return home.viewformat; //test code + } //test code - function SubCtrl(Utils, Page, resolveLocalData) { - // controllerAs ViewModel - var sub = this; - - // bindable members - sub.title = 'Subpage'; - sub.global = Utils; - sub.json = resolveLocalData; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // set page <title> - Page.setTitle(sub.title); - } + home.enterMobile = _enterMobile; //test code + home.exitMobile = _exitMobile; //test code + home.getJsonSucess = _getJsonSuccess; //test code + home.activate = _activate; //test code + home.getView = getView; //test code + return home; //test code } }()); /** @@ -754,17 +952,18 @@ angular .module('reStart') - .controller('HeaderCtrl', HeaderCtrl); + .controller('SubCtrl', SubCtrl); - HeaderCtrl.$inject = ['$location', 'JSONData']; + SubCtrl.$inject = ['Utils', 'Page', 'resolveLocalData']; - function HeaderCtrl($location, JSONData) { + function SubCtrl(Utils, Page, resolveLocalData) { // controllerAs ViewModel - var header = this; + var sub = this; // bindable members - header.indexIsActive = indexIsActive; - header.navIsActive = navIsActive; + sub.title = 'Subpage'; + sub.global = Utils; + sub.json = resolveLocalData; _init(); @@ -774,188 +973,9 @@ * @private */ function _init() { - // activate controller - _activate(); - } - - /** - * Controller activate - * Get JSON data - * - * @returns {*} - * @private - */ - function _activate() { - // get the data from JSON - return JSONData.getLocalData().then(_getJsonSuccess); - } - - /** - * Successful promise data - * - * @param data {json} - * @private - */ - function _getJsonSuccess(data) { - header.json = data; - return header.json; - } - - /** - * Apply class to index nav if active - * - * @param {string} path - */ - function indexIsActive(path) { - // path should be '/' - return $location.path() === path; - } - - /** - * Apply class to currently active nav item - * - * @param {string} path - */ - function navIsActive(path) { - return $location.path().substr(0, path.length) === path; - } - } - -}()); -(function() { - 'use strict'; - - angular - .module('reStart') - .directive('navControl', navControl); - - navControl.$inject = ['$window', 'resize']; - - function navControl($window, resize) { - // return directive - return { - restrict: 'EA', - link: navControlLink - }; - - /** - * navControl LINK function - * - * @param $scope - */ - function navControlLink($scope) { - // private variables - var _$body = angular.element('body'); - var _layoutCanvas = _$body.find('.layout-canvas'); - var _navOpen; - - // data model - $scope.nav = {}; - - _init(); - - /** - * INIT function executes procedural code - * - * @private - */ - function _init() { - // initialize debounced resize - var _rs = resize.init({ - scope: $scope, - resizedFn: _resized, - debounce: 100 - }); - - $scope.$on('$locationChangeStart', _$locationChangeStart); - $scope.$on('enter-mobile', _enterMobile); - $scope.$on('exit-mobile', _exitMobile); - } - - /** - * Resized window (debounced) - * - * @private - */ - function _resized() { - _layoutCanvas.css({ - minHeight: $window.innerHeight + 'px' - }); - } - - /** - * Open mobile navigation - * - * @private - */ - function _openNav() { - _$body - .removeClass('nav-closed') - .addClass('nav-open'); - - _navOpen = true; - } - - /** - * Close mobile navigation - * - * @private - */ - function _closeNav() { - _$body - .removeClass('nav-open') - .addClass('nav-closed'); - - _navOpen = false; - } - - /** - * Toggle nav open/closed - */ - function toggleNav() { - if (!_navOpen) { - _openNav(); - } else { - _closeNav(); - } - } - - /** - * When changing location, close the nav if it's open - */ - function _$locationChangeStart() { - if (_navOpen) { - _closeNav(); - } - } - - /** - * Function to execute when entering mobile media query - * Close nav and set up menu toggling functionality - * - * @private - */ - function _enterMobile(mq) { - _closeNav(); - - // bind function to toggle mobile navigation open/closed - $scope.nav.toggleNav = toggleNav; - } - - /** - * Function to execute when exiting mobile media query - * Disable menu toggling and remove body classes - * - * @private - */ - function _exitMobile(mq) { - // unbind function to toggle mobile navigation open/closed - $scope.nav.toggleNav = null; - - _$body.removeClass('nav-closed nav-open'); - } + // set page <title> + Page.setTitle(sub.title); } } - }()); -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5tb2R1bGUuanMiLCJjb3JlL1BhZ2UuY3RybC5qcyIsImNvcmUvUGFnZS5mYWN0b3J5LmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LmpzIiwiY29yZS9hcHAtc2V0dXAvYXBwLmNvbmZpZy5qcyIsImNvcmUvZ2V0LWRhdGEvSlNPTkRhdGEuZmFjdG9yeS5qcyIsImNvcmUvZ2V0LWRhdGEvUmVzLmZhY3RvcnkuanMiLCJjb3JlL3VpL01RLmNvbnN0YW50LmpzIiwiY29yZS91aS9sb2FkaW5nLmRpci5qcyIsImNvcmUvdWkvdHJ1c3RBc0hUTUwuZmlsdGVyLmpzIiwicGFnZXMvZXJyb3I0MDQvRXJyb3I0MDQuY3RybC5qcyIsInBhZ2VzL2hvbWUvSG9tZS5jdHJsLmpzIiwicGFnZXMvc3ViL1N1Yi5jdHJsLmpzIiwicGFnZXMvc3ViL3NhbXBsZS5kaXIuanMiLCJtb2R1bGVzL2hlYWRlci9IZWFkZXIuY3RybC5qcyIsIm1vZHVsZXMvaGVhZGVyL25hdkNvbnRyb2wuZGlyLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDTkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hJQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDckNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzFCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2hEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQzNCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDeENBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDWkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMvSkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDZEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM5QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM5QkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2xGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJyZVN0YXJ0LWFwcC5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8vIGFwcGxpY2F0aW9uIG1vZHVsZSBzZXR0ZXJcbihmdW5jdGlvbigpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG5cdGFuZ3VsYXJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JywgWyduZ1JvdXRlJywgJ25nUmVzb3VyY2UnLCAnbmdTYW5pdGl6ZScsICdtZWRpYUNoZWNrJywgJ3Jlc2l6ZSddKTtcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5jb250cm9sbGVyKCdQYWdlQ3RybCcsIFBhZ2VDdHJsKTtcblxuXHRQYWdlQ3RybC4kaW5qZWN0ID0gWydQYWdlJywgJyRzY29wZScsICdNUScsICdtZWRpYUNoZWNrJywgJyRsb2cnXTtcblxuXHRmdW5jdGlvbiBQYWdlQ3RybChQYWdlLCAkc2NvcGUsIE1RLCBtZWRpYUNoZWNrLCAkbG9nKSB7XG5cdFx0dmFyIHBhZ2UgPSB0aGlzO1xuXG5cdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcblx0XHR2YXIgX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvciA9IGZhbHNlO1xuXHRcdC8vIFNldCB1cCBmdW5jdGlvbmFsaXR5IHRvIHJ1biBvbiBlbnRlci9leGl0IG9mIG1lZGlhIHF1ZXJ5XG5cdFx0dmFyIF9tYyA9IG1lZGlhQ2hlY2suaW5pdCh7XG5cdFx0XHRzY29wZTogJHNjb3BlLFxuXHRcdFx0bWVkaWE6IHtcblx0XHRcdFx0bXE6IE1RLlNNQUxMLFxuXHRcdFx0XHRlbnRlcjogX2VudGVyTW9iaWxlLFxuXHRcdFx0XHRleGl0OiBfZXhpdE1vYmlsZVxuXHRcdFx0fSxcblx0XHRcdGRlYm91bmNlOiAyMDBcblx0XHR9KTtcblxuXHRcdF9pbml0KCk7XG5cblx0XHQvKipcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcblx0XHRcdC8vIGFzc29jaWF0ZSBwYWdlIDx0aXRsZT5cblx0XHRcdHBhZ2UucGFnZVRpdGxlID0gUGFnZTtcblxuXHRcdFx0JHNjb3BlLiRvbignJHJvdXRlQ2hhbmdlU3RhcnQnLCBfcm91dGVDaGFuZ2VTdGFydCk7XG5cdFx0XHQkc2NvcGUuJG9uKCckcm91dGVDaGFuZ2VTdWNjZXNzJywgX3JvdXRlQ2hhbmdlU3VjY2Vzcyk7XG5cdFx0XHQkc2NvcGUuJG9uKCckcm91dGVDaGFuZ2VFcnJvcicsIF9yb3V0ZUNoYW5nZUVycm9yKTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBFbnRlciBtb2JpbGUgbWVkaWEgcXVlcnlcblx0XHQgKiAkYnJvYWRjYXN0ICdlbnRlci1tb2JpbGUnIGV2ZW50XG5cdFx0ICpcblx0XHQgKiBAcHJpdmF0ZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIF9lbnRlck1vYmlsZSgpIHtcblx0XHRcdCRzY29wZS4kYnJvYWRjYXN0KCdlbnRlci1tb2JpbGUnKTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBFeGl0IG1vYmlsZSBtZWRpYSBxdWVyeVxuXHRcdCAqICRicm9hZGNhc3QgJ2V4aXQtbW9iaWxlJyBldmVudFxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZSgpIHtcblx0XHRcdCRzY29wZS4kYnJvYWRjYXN0KCdleGl0LW1vYmlsZScpO1xuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIFR1cm4gb24gbG9hZGluZyBzdGF0ZVxuXHRcdCAqXG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfbG9hZGluZ09uKCkge1xuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2xvYWRpbmctb24nKTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBUdXJuIG9mZiBsb2FkaW5nIHN0YXRlXG5cdFx0ICpcblx0XHQgKiBAcHJpdmF0ZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIF9sb2FkaW5nT2ZmKCkge1xuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2xvYWRpbmctb2ZmJyk7XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogUm91dGUgY2hhbmdlIHN0YXJ0IGhhbmRsZXJcblx0XHQgKiBJZiBuZXh0IHJvdXRlIGhhcyByZXNvbHZlLCB0dXJuIG9uIGxvYWRpbmdcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cblx0XHQgKiBAcGFyYW0gbmV4dCB7b2JqZWN0fVxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfcm91dGVDaGFuZ2VTdGFydCgkZXZlbnQsIG5leHQsIGN1cnJlbnQpIHtcblx0XHRcdGlmIChuZXh0LiQkcm91dGUgJiYgbmV4dC4kJHJvdXRlLnJlc29sdmUpIHsgLy8gZXNsaW50LWRpc2FibGUtbGluZSBhbmd1bGFyL25vLXByaXZhdGUtY2FsbFxuXHRcdFx0XHRfbG9hZGluZ09uKCk7XG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogUm91dGUgY2hhbmdlIHN1Y2Nlc3MgaGFuZGxlclxuXHRcdCAqIE1hdGNoIGN1cnJlbnQgbWVkaWEgcXVlcnkgYW5kIHJ1biBhcHByb3ByaWF0ZSBmdW5jdGlvblxuXHRcdCAqIElmIGN1cnJlbnQgcm91dGUgaGFzIGJlZW4gcmVzb2x2ZWQsIHR1cm4gb2ZmIGxvYWRpbmdcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cblx0XHQgKiBAcGFyYW0gY3VycmVudCB7b2JqZWN0fVxuXHRcdCAqIEBwYXJhbSBwcmV2aW91cyB7b2JqZWN0fVxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX3JvdXRlQ2hhbmdlU3VjY2VzcygkZXZlbnQsIGN1cnJlbnQsIHByZXZpb3VzKSB7XG5cdFx0XHRfbWMubWF0Y2hDdXJyZW50KE1RLlNNQUxMKTtcblxuXHRcdFx0aWYgKGN1cnJlbnQuJCRyb3V0ZSAmJiBjdXJyZW50LiQkcm91dGUucmVzb2x2ZSkgeyAgIC8vIGVzbGludC1kaXNhYmxlLWxpbmUgYW5ndWxhci9uby1wcml2YXRlLWNhbGxcblx0XHRcdFx0X2xvYWRpbmdPZmYoKTtcblx0XHRcdH1cblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBSb3V0ZSBjaGFuZ2UgZXJyb3IgaGFuZGxlclxuXHRcdCAqIEhhbmRsZSByb3V0ZSByZXNvbHZlIGZhaWx1cmVzXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gJGV2ZW50IHtvYmplY3R9XG5cdFx0ICogQHBhcmFtIGN1cnJlbnQge29iamVjdH1cblx0XHQgKiBAcGFyYW0gcHJldmlvdXMge29iamVjdH1cblx0XHQgKiBAcGFyYW0gcmVqZWN0aW9uIHtvYmplY3R9XG5cdFx0ICogQHByaXZhdGVcblx0XHQgKi9cblx0XHRmdW5jdGlvbiBfcm91dGVDaGFuZ2VFcnJvcigkZXZlbnQsIGN1cnJlbnQsIHByZXZpb3VzLCByZWplY3Rpb24pIHtcblx0XHRcdHZhciBkZXN0aW5hdGlvbiA9IChjdXJyZW50ICYmIChjdXJyZW50LnRpdGxlIHx8IGN1cnJlbnQubmFtZSB8fCBjdXJyZW50LmxvYWRlZFRlbXBsYXRlVXJsKSkgfHwgJ3Vua25vd24gdGFyZ2V0Jztcblx0XHRcdHZhciBtc2cgPSAnRXJyb3Igcm91dGluZyB0byAnICsgZGVzdGluYXRpb24gKyAnLiAnICsgKHJlamVjdGlvbi5tc2cgfHwgJycpO1xuXG5cdFx0XHRpZiAoX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvcikge1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cblx0XHRcdF9oYW5kbGluZ1JvdXRlQ2hhbmdlRXJyb3IgPSB0cnVlO1xuXHRcdFx0X2xvYWRpbmdPZmYoKTtcblxuXHRcdFx0JGxvZy5lcnJvcihtc2cpO1xuXHRcdH1cblx0fVxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XG5cdCd1c2Ugc3RyaWN0JztcblxuXHRhbmd1bGFyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXG5cdFx0LmZhY3RvcnkoJ1BhZ2UnLCBQYWdlKTtcblxuXHRmdW5jdGlvbiBQYWdlKCkge1xuXHRcdC8vIHByaXZhdGUgdmFyc1xuXHRcdHZhciBzaXRlVGl0bGUgPSAncmVTdGFydCBBbmd1bGFyJztcblx0XHR2YXIgcGFnZVRpdGxlID0gJ0hvbWUnO1xuXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRnZXRUaXRsZTogZ2V0VGl0bGUsXG5cdFx0XHRzZXRUaXRsZTogc2V0VGl0bGVcblx0XHR9O1xuXG5cdFx0LyoqXG5cdFx0ICogVGl0bGUgZnVuY3Rpb25cblx0XHQgKiBTZXRzIHNpdGUgdGl0bGUgYW5kIHBhZ2UgdGl0bGVcblx0XHQgKlxuXHRcdCAqIEByZXR1cm5zIHtzdHJpbmd9IHNpdGUgdGl0bGUgKyBwYWdlIHRpdGxlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gZ2V0VGl0bGUoKSB7XG5cdFx0XHRyZXR1cm4gc2l0ZVRpdGxlICsgJyB8ICcgKyBwYWdlVGl0bGU7XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogU2V0IHBhZ2UgdGl0bGVcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSBuZXdUaXRsZSB7c3RyaW5nfVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIHNldFRpdGxlKG5ld1RpdGxlKSB7XG5cdFx0XHRwYWdlVGl0bGUgPSBuZXdUaXRsZTtcblx0XHR9XG5cdH1cbn0oKSk7IiwiLy8gXCJnbG9iYWxcIiBvYmplY3QgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5mYWN0b3J5KCdVdGlscycsIFV0aWxzKTtcblxuXHRmdW5jdGlvbiBVdGlscygpIHtcblx0XHR2YXIgZ3JlZXRpbmcgPSAnSGVsbG8nO1xuXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRncmVldGluZzogZ3JlZXRpbmcsXG5cdFx0XHRhbGVydEdyZWV0aW5nOiBhbGVydEdyZWV0aW5nXG5cdFx0fTtcblxuXHRcdC8qKlxuXHRcdCAqIEFsZXJ0IGdyZWV0aW5nXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gbmFtZSB7c3RyaW5nfVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIGFsZXJ0R3JlZXRpbmcobmFtZSkge1xuXHRcdFx0YWxlcnQoZ3JlZXRpbmcgKyAnLCAnICsgbmFtZSArICchJyk7XG5cdFx0fVxuXHR9XG59KCkpOyIsIi8vIGFwcGxpY2F0aW9uIGNvbmZpZ1xuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5jb25maWcoYXBwQ29uZmlnKTtcblxuXHRhcHBDb25maWcuJGluamVjdCA9IFsnJHJvdXRlUHJvdmlkZXInLCAnJGxvY2F0aW9uUHJvdmlkZXInXTtcblxuXHRmdW5jdGlvbiBhcHBDb25maWcoJHJvdXRlUHJvdmlkZXIsICRsb2NhdGlvblByb3ZpZGVyKSB7XG5cdFx0JHJvdXRlUHJvdmlkZXJcblx0XHRcdC53aGVuKCcvJywge1xuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL2hvbWUvSG9tZS52aWV3Lmh0bWwnLFxuXHRcdFx0XHRjb250cm9sbGVyOiAnSG9tZUN0cmwnLFxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdob21lJ1xuXHRcdFx0fSlcblx0XHRcdC53aGVuKCcvc3VicGFnZScsIHtcblx0XHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvU3ViLnZpZXcuaHRtbCcsXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdTdWJDdHJsJyxcblx0XHRcdFx0Y29udHJvbGxlckFzOiAnc3ViJyxcblx0XHRcdFx0cmVzb2x2ZToge1xuXHRcdFx0XHRcdHJlc29sdmVMb2NhbERhdGE6IHJlc29sdmVMb2NhbERhdGFcblx0XHRcdFx0fVxuXHRcdFx0fSlcblx0XHRcdC5vdGhlcndpc2Uoe1xuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LnZpZXcuaHRtbCcsXG5cdFx0XHRcdGNvbnRyb2xsZXI6ICdFcnJvcjQwNEN0cmwnLFxuXHRcdFx0XHRjb250cm9sbGVyQXM6ICdlNDA0J1xuXHRcdFx0fSk7XG5cblx0XHQkbG9jYXRpb25Qcm92aWRlclxuXHRcdFx0Lmh0bWw1TW9kZSh7XG5cdFx0XHRcdGVuYWJsZWQ6IHRydWVcblx0XHRcdH0pXG5cdFx0XHQuaGFzaFByZWZpeCgnIScpO1xuXHR9XG5cblx0cmVzb2x2ZUxvY2FsRGF0YS4kaW5qZWN0ID0gWydKU09ORGF0YSddO1xuXHQvKipcblx0ICogR2V0IGxvY2FsIGRhdGEgZm9yIHJvdXRlIHJlc29sdmVcblx0ICpcblx0ICogQHBhcmFtIEpTT05EYXRhIHtmYWN0b3J5fVxuXHQgKiBAcmV0dXJucyB7cHJvbWlzZX0gZGF0YVxuXHQgKi9cblx0ZnVuY3Rpb24gcmVzb2x2ZUxvY2FsRGF0YShKU09ORGF0YSkge1xuXHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKTtcblx0fVxufSgpKTsiLCIvLyBmZXRjaCBKU09OIGRhdGEgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xuKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5mYWN0b3J5KCdKU09ORGF0YScsIEpTT05EYXRhKTtcblxuXHRKU09ORGF0YS4kaW5qZWN0ID0gWyckaHR0cCcsICdSZXMnXTtcblxuXHRmdW5jdGlvbiBKU09ORGF0YSgkaHR0cCwgUmVzKSB7XG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRnZXRMb2NhbERhdGE6IGdldExvY2FsRGF0YVxuXHRcdH07XG5cblx0XHQvKipcblx0XHQgKiBHRVQgbG9jYWwgSlNPTiBkYXRhIGZpbGUgYW5kIHJldHVybiByZXN1bHRzXG5cdFx0ICpcblx0XHQgKiBAcmV0dXJucyB7cHJvbWlzZX1cblx0XHQgKi9cblx0XHRmdW5jdGlvbiBnZXRMb2NhbERhdGEoKSB7XG5cdFx0XHRyZXR1cm4gJGh0dHBcblx0XHRcdFx0LmdldCgnL2RhdGEvZGF0YS5qc29uJylcblx0XHRcdFx0LnRoZW4oUmVzLnN1Y2Nlc3MsIFJlcy5lcnJvcik7XG5cdFx0fVxuXHR9XG59KCkpOyIsIihmdW5jdGlvbigpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG5cdGFuZ3VsYXJcblx0XHQubW9kdWxlKCdyZVN0YXJ0Jylcblx0XHQuZmFjdG9yeSgnUmVzJywgUmVzKTtcblxuXHRmdW5jdGlvbiBSZXMoKSB7XG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xuXHRcdHJldHVybiB7XG5cdFx0XHRzdWNjZXNzOiBzdWNjZXNzLFxuXHRcdFx0ZXJyb3I6IGVycm9yXG5cdFx0fTtcblxuXHRcdC8qKlxuXHRcdCAqIFByb21pc2UgcmVzcG9uc2UgZnVuY3Rpb25cblx0XHQgKiBDaGVja3MgdHlwZW9mIGRhdGEgcmV0dXJuZWQgYW5kIHN1Y2NlZWRzIGlmIEpTIG9iamVjdCwgdGhyb3dzIGVycm9yIGlmIG5vdFxuXHRcdCAqIFVzZWZ1bCBmb3IgQVBJcyAoaWUsIHdpdGggbmdpbngpIHdoZXJlIHNlcnZlciBlcnJvciBIVE1MIHBhZ2UgbWF5IGJlIHJldHVybmVkIGluIGVycm9yXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gcmVzcG9uc2Ugeyp9IGRhdGEgZnJvbSAkaHR0cFxuXHRcdCAqIEByZXR1cm5zIHsqfSBvYmplY3QsIGFycmF5XG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gc3VjY2VzcyhyZXNwb25zZSkge1xuXHRcdFx0aWYgKGFuZ3VsYXIuaXNPYmplY3QocmVzcG9uc2UuZGF0YSkpIHtcblx0XHRcdFx0cmV0dXJuIHJlc3BvbnNlLmRhdGE7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHR0aHJvdyBuZXcgRXJyb3IoJ3JldHJpZXZlZCBkYXRhIGlzIG5vdCB0eXBlb2Ygb2JqZWN0LicpO1xuXHRcdFx0fVxuXHRcdH1cblxuXHRcdC8qKlxuXHRcdCAqIFByb21pc2UgcmVzcG9uc2UgZnVuY3Rpb24gLSBlcnJvclxuXHRcdCAqIFRocm93cyBhbiBlcnJvciB3aXRoIGVycm9yIGRhdGFcblx0XHQgKlxuXHRcdCAqIEBwYXJhbSBlcnJvciB7b2JqZWN0fVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIGVycm9yKGVycm9yKSB7XG5cdFx0XHR0aHJvdyBuZXcgRXJyb3IoJ0Vycm9yIHJldHJpZXZpbmcgZGF0YScsIGVycm9yKTtcblx0XHR9XG5cdH1cbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0Ly8gbWVkaWEgcXVlcnkgY29uc3RhbnRzXG5cdHZhciBNUSA9IHtcblx0XHRTTUFMTDogJyhtYXgtd2lkdGg6IDc2N3B4KScsXG5cdFx0TEFSR0U6ICcobWluLXdpZHRoOiA3NjhweCknXG5cdH07XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5jb25zdGFudCgnTVEnLCBNUSk7XG59KCkpOyIsIihmdW5jdGlvbigpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG5cdGFuZ3VsYXJcblx0XHQubW9kdWxlKCdyZVN0YXJ0Jylcblx0XHQuZGlyZWN0aXZlKCdsb2FkaW5nJywgbG9hZGluZyk7XG5cblx0bG9hZGluZy4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xuXG5cdGZ1bmN0aW9uIGxvYWRpbmcoJHdpbmRvdywgcmVzaXplKSB7XG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxuXHRcdHJldHVybiB7XG5cdFx0XHRyZXN0cmljdDogJ0VBJyxcblx0XHRcdHJlcGxhY2U6IHRydWUsXG5cdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL2NvcmUvdWkvbG9hZGluZy50cGwuaHRtbCcsXG5cdFx0XHR0cmFuc2NsdWRlOiB0cnVlLFxuXHRcdFx0Y29udHJvbGxlcjogbG9hZGluZ0N0cmwsXG5cdFx0XHRjb250cm9sbGVyQXM6ICdsb2FkaW5nJyxcblx0XHRcdGJpbmRUb0NvbnRyb2xsZXI6IHRydWUsXG5cdFx0XHRsaW5rOiBsb2FkaW5nTGlua1xuXHRcdH07XG5cblx0XHQvKipcblx0XHQgKiBsb2FkaW5nIExJTktcblx0XHQgKiBEaXNhYmxlcyBwYWdlIHNjcm9sbGluZyB3aGVuIGxvYWRpbmcgb3ZlcmxheSBpcyBvcGVuXG5cdFx0ICpcblx0XHQgKiBAcGFyYW0gJHNjb3BlXG5cdFx0ICogQHBhcmFtICRlbGVtZW50XG5cdFx0ICogQHBhcmFtICRhdHRyc1xuXHRcdCAqIEBwYXJhbSBsb2FkaW5nIHtjb250cm9sbGVyfVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIGxvYWRpbmdMaW5rKCRzY29wZSwgJGVsZW1lbnQsICRhdHRycywgbG9hZGluZykge1xuXHRcdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcblx0XHRcdHZhciBfJGJvZHkgPSBhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKTtcblx0XHRcdHZhciBfd2luSGVpZ2h0ID0gJHdpbmRvdy5pbm5lckhlaWdodCArICdweCc7XG5cblx0XHRcdF9pbml0KCk7XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcblx0XHRcdFx0Ly8gaW5pdGlhbGl6ZSBkZWJvdW5jZWQgcmVzaXplXG5cdFx0XHRcdHZhciBfcnMgPSByZXNpemUuaW5pdCh7XG5cdFx0XHRcdFx0c2NvcGU6ICRzY29wZSxcblx0XHRcdFx0XHRyZXNpemVkRm46IF9yZXNpemVkLFxuXHRcdFx0XHRcdGRlYm91bmNlOiAyMDBcblx0XHRcdFx0fSk7XG5cblx0XHRcdFx0Ly8gJHdhdGNoIGFjdGl2ZSBzdGF0ZVxuXHRcdFx0XHQkc2NvcGUuJHdhdGNoKCdsb2FkaW5nLmFjdGl2ZScsIF8kd2F0Y2hBY3RpdmUpO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIFdpbmRvdyByZXNpemVkXG5cdFx0XHQgKiBJZiBsb2FkaW5nLCByZWFwcGx5IGJvZHkgaGVpZ2h0XG5cdFx0XHQgKiB0byBwcmV2ZW50IHNjcm9sbGJhclxuXHRcdFx0ICpcblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF9yZXNpemVkKCkge1xuXHRcdFx0XHRfd2luSGVpZ2h0ID0gJHdpbmRvdy5pbm5lckhlaWdodCArICdweCc7XG5cblx0XHRcdFx0aWYgKGxvYWRpbmcuYWN0aXZlKSB7XG5cdFx0XHRcdFx0XyRib2R5LmNzcyh7XG5cdFx0XHRcdFx0XHRoZWlnaHQ6IF93aW5IZWlnaHQsXG5cdFx0XHRcdFx0XHRvdmVyZmxvd1k6ICdoaWRkZW4nXG5cdFx0XHRcdFx0fSk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiAkd2F0Y2ggbG9hZGluZy5hY3RpdmVcblx0XHRcdCAqXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHtib29sZWFufVxuXHRcdFx0ICogQHBhcmFtIG9sZFZhbCB7dW5kZWZpbmVkfGJvb2xlYW59XG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfJHdhdGNoQWN0aXZlKG5ld1ZhbCwgb2xkVmFsKSB7XG5cdFx0XHRcdGlmIChuZXdWYWwpIHtcblx0XHRcdFx0XHRfb3BlbigpO1xuXHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdF9jbG9zZSgpO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogT3BlbiBsb2FkaW5nXG5cdFx0XHQgKiBEaXNhYmxlIHNjcm9sbFxuXHRcdFx0ICpcblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF9vcGVuKCkge1xuXHRcdFx0XHRfJGJvZHkuY3NzKHtcblx0XHRcdFx0XHRoZWlnaHQ6IF93aW5IZWlnaHQsXG5cdFx0XHRcdFx0b3ZlcmZsb3dZOiAnaGlkZGVuJ1xuXHRcdFx0XHR9KTtcblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBDbG9zZSBsb2FkaW5nXG5cdFx0XHQgKiBFbmFibGUgc2Nyb2xsXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX2Nsb3NlKCkge1xuXHRcdFx0XHRfJGJvZHkuY3NzKHtcblx0XHRcdFx0XHRoZWlnaHQ6ICdhdXRvJyxcblx0XHRcdFx0XHRvdmVyZmxvd1k6ICdhdXRvJ1xuXHRcdFx0XHR9KTtcblx0XHRcdH1cblx0XHR9XG5cdH1cblxuXHRsb2FkaW5nQ3RybC4kaW5qZWN0ID0gWyckc2NvcGUnXTtcblx0LyoqXG5cdCAqIGxvYWRpbmcgQ09OVFJPTExFUlxuXHQgKiBVcGRhdGUgdGhlIGxvYWRpbmcgc3RhdHVzIGJhc2VkXG5cdCAqIG9uIHJvdXRlQ2hhbmdlIHN0YXRlXG5cdCAqL1xuXHRmdW5jdGlvbiBsb2FkaW5nQ3RybCgkc2NvcGUpIHtcblx0XHR2YXIgbG9hZGluZyA9IHRoaXM7XG5cblx0XHRfaW5pdCgpO1xuXG5cdFx0LyoqXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XG5cdFx0XHQvLyB0dXJuIG9uIGxvYWRpbmcgZm9yIGluaXRpYWwgcGFnZSBsb2FkXG5cdFx0XHRfbG9hZGluZ0FjdGl2ZSgpO1xuXG5cdFx0XHQkc2NvcGUuJG9uKCdsb2FkaW5nLW9uJywgX2xvYWRpbmdBY3RpdmUpO1xuXHRcdFx0JHNjb3BlLiRvbignbG9hZGluZy1vZmYnLCBfbG9hZGluZ0luYWN0aXZlKTtcblx0XHR9XG5cblx0XHQvKipcblx0XHQgKiBTZXQgbG9hZGluZyB0byBhY3RpdmVcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdBY3RpdmUoKSB7XG5cdFx0XHRsb2FkaW5nLmFjdGl2ZSA9IHRydWU7XG5cdFx0fVxuXG5cdFx0LyoqXG5cdFx0ICogU2V0IGxvYWRpbmcgdG8gaW5hY3RpdmVcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdJbmFjdGl2ZSgpIHtcblx0XHRcdGxvYWRpbmcuYWN0aXZlID0gZmFsc2U7XG5cdFx0fVxuXHR9XG5cbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5maWx0ZXIoJ3RydXN0QXNIVE1MJywgdHJ1c3RBc0hUTUwpO1xuXG5cdHRydXN0QXNIVE1MLiRpbmplY3QgPSBbJyRzY2UnXTtcblxuXHRmdW5jdGlvbiB0cnVzdEFzSFRNTCgkc2NlKSB7XG5cdFx0cmV0dXJuIGZ1bmN0aW9uKHRleHQpIHtcblx0XHRcdHJldHVybiAkc2NlLnRydXN0QXNIdG1sKHRleHQpO1xuXHRcdH07XG5cdH1cbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xuXHQndXNlIHN0cmljdCc7XG5cblx0YW5ndWxhclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxuXHRcdC5jb250cm9sbGVyKCdFcnJvcjQwNEN0cmwnLCBFcnJvcjQwNEN0cmwpO1xuXG5cdEVycm9yNDA0Q3RybC4kaW5qZWN0ID0gWyckc2NvcGUnLCAnUGFnZSddO1xuXG5cdGZ1bmN0aW9uIEVycm9yNDA0Q3RybCgkc2NvcGUsIFBhZ2UpIHtcblx0XHR2YXIgZTQwNCA9IHRoaXM7XG5cblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXG5cdFx0ZTQwNC50aXRsZSA9ICc0MDQgLSBQYWdlIE5vdCBGb3VuZCc7XG5cblx0XHRfaW5pdCgpO1xuXG5cdFx0LyoqXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcblx0XHQgKlxuXHRcdCAqIEBwcml2YXRlXG5cdFx0ICovXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XG5cdFx0XHQvLyBzZXQgcGFnZSA8dGl0bGU+XG5cdFx0XHRQYWdlLnNldFRpdGxlKGU0MDQudGl0bGUpO1xuXG5cdFx0XHQvLyBubyBkYXRhIHRvIGxvYWQsIGJ1dCBsb2FkaW5nIHN0YXRlIG1pZ2h0IGJlIG9uXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb2ZmJyk7XG5cdFx0fVxuXHR9XG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnRyb2xsZXIoJ0hvbWVDdHJsJywgSG9tZUN0cmwpO1xyXG5cclxuXHRIb21lQ3RybC4kaW5qZWN0ID0gWyckc2NvcGUnLCAnVXRpbHMnLCAnUGFnZScsICdKU09ORGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBIb21lQ3RybCgkc2NvcGUsIFV0aWxzLCBQYWdlLCBKU09ORGF0YSkge1xyXG5cdFx0Ly8gY29udHJvbGxlckFzIFZpZXdNb2RlbFxyXG5cdFx0dmFyIGhvbWUgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGJpbmRhYmxlIG1lbWJlcnNcclxuXHRcdGhvbWUudGl0bGUgPSAnSG9tZSc7XHJcblx0XHRob21lLmdsb2JhbCA9IFV0aWxzO1xyXG5cdFx0aG9tZS5uYW1lID0gJ1Zpc2l0b3InO1xyXG5cdFx0aG9tZS5hbGVydEdyZWV0aW5nID0gVXRpbHMuYWxlcnRHcmVldGluZztcclxuXHRcdGhvbWUuc3RyaW5nT2ZIVE1MID0gJzxzdHJvbmcgc3R5bGU9XCJjb2xvcjogZ3JlZW47XCI+U29tZSBncmVlbiB0ZXh0PC9zdHJvbmc+IGJvdW5kIGFzIEhUTUwgd2l0aCBhIDxhIGhyZWY9XCIjXCI+bGluazwvYT4sIHRydXN0ZWQgd2l0aCBTQ0UhJztcclxuXHRcdGhvbWUudmlld2Zvcm1hdCA9IG51bGw7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cclxuXHRcdFx0UGFnZS5zZXRUaXRsZShob21lLnRpdGxlKTtcclxuXHJcblx0XHRcdC8vIGFjdGl2YXRlIGNvbnRyb2xsZXJcclxuXHRcdFx0X2FjdGl2YXRlKCk7XHJcblxyXG5cdFx0XHQvLyBtZWRpYXF1ZXJ5IGV2ZW50c1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdlbnRlci1tb2JpbGUnLCBfZW50ZXJNb2JpbGUpO1xyXG5cdFx0XHQkc2NvcGUuJG9uKCdleGl0LW1vYmlsZScsIF9leGl0TW9iaWxlKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIENvbnRyb2xsZXIgYWN0aXZhdGVcclxuXHRcdCAqIEdldCBKU09OIGRhdGFcclxuXHRcdCAqXHJcblx0XHQgKiBAcmV0dXJucyB7Kn1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9hY3RpdmF0ZSgpIHtcclxuXHRcdFx0Ly8gc3RhcnQgbG9hZGluZ1xyXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb24nKTtcclxuXHJcblx0XHRcdC8vIGdldCB0aGUgZGF0YSBmcm9tIEpTT05cclxuXHRcdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpLnRoZW4oX2dldEpzb25TdWNjZXNzKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFN1Y2Nlc3NmdWwgcHJvbWlzZSBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGRhdGEge2pzb259XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZ2V0SnNvblN1Y2Nlc3MoZGF0YSkge1xyXG5cdFx0XHRob21lLmpzb24gPSBkYXRhO1xyXG5cclxuXHRcdFx0Ly8gc3RvcCBsb2FkaW5nXHJcblx0XHRcdCRzY29wZS4kZW1pdCgnbG9hZGluZy1vZmYnKTtcclxuXHJcblx0XHRcdHJldHVybiBob21lLmpzb247XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFbnRlciBzbWFsbCBtcVxyXG5cdFx0ICogU2V0IGhvbWUudmlld2Zvcm1hdFxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9lbnRlck1vYmlsZSgpIHtcclxuXHRcdFx0aG9tZS52aWV3Zm9ybWF0ID0gJ3NtYWxsJztcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEV4aXQgc21hbGwgbXFcclxuXHRcdCAqIFNldCBob21lLnZpZXdmb3JtYXRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZSgpIHtcclxuXHRcdFx0aG9tZS52aWV3Zm9ybWF0ID0gJ2xhcmdlJztcclxuXHRcdH1cclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignU3ViQ3RybCcsIFN1YkN0cmwpO1xyXG5cclxuXHRTdWJDdHJsLiRpbmplY3QgPSBbJ1V0aWxzJywgJ1BhZ2UnLCAncmVzb2x2ZUxvY2FsRGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBTdWJDdHJsKFV0aWxzLCBQYWdlLCByZXNvbHZlTG9jYWxEYXRhKSB7XHJcblx0XHQvLyBjb250cm9sbGVyQXMgVmlld01vZGVsXHJcblx0XHR2YXIgc3ViID0gdGhpcztcclxuXHJcblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXHJcblx0XHRzdWIudGl0bGUgPSAnU3VicGFnZSc7XHJcblx0XHRzdWIuZ2xvYmFsID0gVXRpbHM7XHJcblx0XHRzdWIuanNvbiA9IHJlc29sdmVMb2NhbERhdGE7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cclxuXHRcdFx0UGFnZS5zZXRUaXRsZShzdWIudGl0bGUpO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiLCIvKipcclxuICogRGlyZWN0aXZlcyAoYW5kIGFzc29jaWF0ZWQgYXR0cmlidXRlcykgYXJlIGFsd2F5cyBkZWNsYXJlZCBhcyBjYW1lbENhc2UgaW4gSlMgYW5kIHNuYWtlLWNhc2UgaW4gSFRNTFxyXG4gKiBBbmd1bGFyJ3MgYnVpbHQtaW4gPGE+IGRpcmVjdGl2ZSBhdXRvbWF0aWNhbGx5IGltcGxlbWVudHMgcHJldmVudERlZmF1bHQgb24gbGlua3MgdGhhdCBkb24ndCBoYXZlIGFuIGhyZWYgYXR0cmlidXRlXHJcbiAqIENvbXBsZXggSmF2YVNjcmlwdCBET00gbWFuaXB1bGF0aW9uIHNob3VsZCBhbHdheXMgYmUgZG9uZSBpbiBkaXJlY3RpdmUgbGluayBmdW5jdGlvbnMsIGFuZCAkYXBwbHkgc2hvdWxkIG5ldmVyIGJlIHVzZWQgaW4gYSBjb250cm9sbGVyISBTaW1wbGUgRE9NIG1hbmlwdWxhdGlvbiBzaG91bGQgYmUgaW4gdGhlIHZpZXcuXHJcbiAqL1xyXG5cclxuLyotLS0gU2FtcGxlIERpcmVjdGl2ZSB3aXRoIGEgJHdhdGNoIC0tLSovXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmRpcmVjdGl2ZSgnc2FtcGxlRGlyZWN0aXZlJywgc2FtcGxlRGlyZWN0aXZlKTtcclxuXHJcblx0c2FtcGxlRGlyZWN0aXZlLiRpbmplY3QgPSBbJyR0aW1lb3V0J107XHJcblxyXG5cdGZ1bmN0aW9uIHNhbXBsZURpcmVjdGl2ZSgkdGltZW91dCkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdHJlcGxhY2U6IHRydWUsXHJcblx0XHRcdHNjb3BlOiB7fSxcclxuXHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9zdWIvc2FtcGxlLnRwbC5odG1sJyxcclxuXHRcdFx0dHJhbnNjbHVkZTogdHJ1ZSxcclxuXHRcdFx0Y29udHJvbGxlcjogU2FtcGxlRGlyZWN0aXZlQ3RybCxcclxuXHRcdFx0Y29udHJvbGxlckFzOiAnc2QnLFxyXG5cdFx0XHRiaW5kVG9Db250cm9sbGVyOiB7XHJcblx0XHRcdFx0anNvbkRhdGE6ICc9J1xyXG5cdFx0XHR9LFxyXG5cdFx0XHRsaW5rOiBzYW1wbGVEaXJlY3RpdmVMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogc2FtcGxlRGlyZWN0aXZlIExJTksgZnVuY3Rpb25cclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJHNjb3BlXHJcblx0XHQgKiBAcGFyYW0gJGVsZW1lbnRcclxuXHRcdCAqIEBwYXJhbSAkYXR0cnNcclxuXHRcdCAqIEBwYXJhbSBzZCB7Y29udHJvbGxlcn1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gc2FtcGxlRGlyZWN0aXZlTGluaygkc2NvcGUsICRlbGVtZW50LCAkYXR0cnMsIHNkKSB7XHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIHdhdGNoIGZvciBhc3luYyBkYXRhIHRvIGJlY29tZSBhdmFpbGFibGUgYW5kIHVwZGF0ZVxyXG5cdFx0XHRcdCRzY29wZS4kd2F0Y2goJ3NkLmpzb25EYXRhJywgXyR3YXRjaEpzb25EYXRhKTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqICR3YXRjaCBmb3Igc2QuanNvbkRhdGEgdG8gYmVjb21lIGF2YWlsYWJsZVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcGFyYW0gbmV3VmFsIHsqfVxyXG5cdFx0XHQgKiBAcGFyYW0gb2xkVmFsIHsqfVxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gXyR3YXRjaEpzb25EYXRhKG5ld1ZhbCwgb2xkVmFsKSB7XHJcblx0XHRcdFx0aWYgKG5ld1ZhbCkge1xyXG5cdFx0XHRcdFx0c2QuanNvbkRhdGEgPSBuZXdWYWw7XHJcblxyXG5cdFx0XHRcdFx0JHRpbWVvdXQoZnVuY3Rpb24oKSB7XHJcblx0XHRcdFx0XHRcdGNvbnNvbGUubG9nKCdkZW1vbnN0cmF0ZSAkdGltZW91dCBpbmplY3Rpb24gaW4gYSBkaXJlY3RpdmUgbGluayBmdW5jdGlvbicpO1xyXG5cdFx0XHRcdFx0fSwgMTAwMCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblx0XHR9XHJcblx0fVxyXG5cclxuXHRTYW1wbGVEaXJlY3RpdmVDdHJsLiRpbmplY3QgPSBbXTtcclxuXHQvKipcclxuXHQgKiBzYW1wbGVEaXJlY3RpdmUgQ09OVFJPTExFUlxyXG5cdCAqL1xyXG5cdGZ1bmN0aW9uIFNhbXBsZURpcmVjdGl2ZUN0cmwoKSB7XHJcblx0XHR2YXIgc2QgPSB0aGlzO1xyXG5cclxuXHRcdC8vIGNvbnRyb2xsZXIgbG9naWMgZ29lcyBoZXJlXHJcblx0fVxyXG5cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdIZWFkZXJDdHJsJywgSGVhZGVyQ3RybCk7XHJcblxyXG5cdEhlYWRlckN0cmwuJGluamVjdCA9IFsnJGxvY2F0aW9uJywgJ0pTT05EYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIEhlYWRlckN0cmwoJGxvY2F0aW9uLCBKU09ORGF0YSkge1xyXG5cdFx0Ly8gY29udHJvbGxlckFzIFZpZXdNb2RlbFxyXG5cdFx0dmFyIGhlYWRlciA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gYmluZGFibGUgbWVtYmVyc1xyXG5cdFx0aGVhZGVyLmluZGV4SXNBY3RpdmUgPSBpbmRleElzQWN0aXZlO1xyXG5cdFx0aGVhZGVyLm5hdklzQWN0aXZlID0gbmF2SXNBY3RpdmU7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIGFjdGl2YXRlIGNvbnRyb2xsZXJcclxuXHRcdFx0X2FjdGl2YXRlKCk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBDb250cm9sbGVyIGFjdGl2YXRlXHJcblx0XHQgKiBHZXQgSlNPTiBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMgeyp9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfYWN0aXZhdGUoKSB7XHJcblx0XHRcdC8vIGdldCB0aGUgZGF0YSBmcm9tIEpTT05cclxuXHRcdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpLnRoZW4oX2dldEpzb25TdWNjZXNzKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFN1Y2Nlc3NmdWwgcHJvbWlzZSBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGRhdGEge2pzb259XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZ2V0SnNvblN1Y2Nlc3MoZGF0YSkge1xyXG5cdFx0XHRoZWFkZXIuanNvbiA9IGRhdGE7XHJcblx0XHRcdHJldHVybiBoZWFkZXIuanNvbjtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFwcGx5IGNsYXNzIHRvIGluZGV4IG5hdiBpZiBhY3RpdmVcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0ge3N0cmluZ30gcGF0aFxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBpbmRleElzQWN0aXZlKHBhdGgpIHtcclxuXHRcdFx0Ly8gcGF0aCBzaG91bGQgYmUgJy8nXHJcblx0XHRcdHJldHVybiAkbG9jYXRpb24ucGF0aCgpID09PSBwYXRoO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQXBwbHkgY2xhc3MgdG8gY3VycmVudGx5IGFjdGl2ZSBuYXYgaXRlbVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSB7c3RyaW5nfSBwYXRoXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIG5hdklzQWN0aXZlKHBhdGgpIHtcclxuXHRcdFx0cmV0dXJuICRsb2NhdGlvbi5wYXRoKCkuc3Vic3RyKDAsIHBhdGgubGVuZ3RoKSA9PT0gcGF0aDtcclxuXHRcdH1cclxuXHR9XHJcblxyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcblx0J3VzZSBzdHJpY3QnO1xuXG5cdGFuZ3VsYXJcblx0XHQubW9kdWxlKCdyZVN0YXJ0Jylcblx0XHQuZGlyZWN0aXZlKCduYXZDb250cm9sJywgbmF2Q29udHJvbCk7XG5cblx0bmF2Q29udHJvbC4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xuXG5cdGZ1bmN0aW9uIG5hdkNvbnRyb2woJHdpbmRvdywgcmVzaXplKSB7XG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxuXHRcdHJldHVybiB7XG5cdFx0XHRyZXN0cmljdDogJ0VBJyxcblx0XHRcdGxpbms6IG5hdkNvbnRyb2xMaW5rXG5cdFx0fTtcblxuXHRcdC8qKlxuXHRcdCAqIG5hdkNvbnRyb2wgTElOSyBmdW5jdGlvblxuXHRcdCAqXG5cdFx0ICogQHBhcmFtICRzY29wZVxuXHRcdCAqL1xuXHRcdGZ1bmN0aW9uIG5hdkNvbnRyb2xMaW5rKCRzY29wZSkge1xuXHRcdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcblx0XHRcdHZhciBfJGJvZHkgPSBhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKTtcblx0XHRcdHZhciBfbGF5b3V0Q2FudmFzID0gXyRib2R5LmZpbmQoJy5sYXlvdXQtY2FudmFzJyk7XG5cdFx0XHR2YXIgX25hdk9wZW47XG5cblx0XHRcdC8vIGRhdGEgbW9kZWxcblx0XHRcdCRzY29wZS5uYXYgPSB7fTtcblxuXHRcdFx0X2luaXQoKTtcblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxuXHRcdFx0ICpcblx0XHRcdCAqIEBwcml2YXRlXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xuXHRcdFx0XHQvLyBpbml0aWFsaXplIGRlYm91bmNlZCByZXNpemVcblx0XHRcdFx0dmFyIF9ycyA9IHJlc2l6ZS5pbml0KHtcblx0XHRcdFx0XHRzY29wZTogJHNjb3BlLFxuXHRcdFx0XHRcdHJlc2l6ZWRGbjogX3Jlc2l6ZWQsXG5cdFx0XHRcdFx0ZGVib3VuY2U6IDEwMFxuXHRcdFx0XHR9KTtcblxuXHRcdFx0XHQkc2NvcGUuJG9uKCckbG9jYXRpb25DaGFuZ2VTdGFydCcsIF8kbG9jYXRpb25DaGFuZ2VTdGFydCk7XG5cdFx0XHRcdCRzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIF9lbnRlck1vYmlsZSk7XG5cdFx0XHRcdCRzY29wZS4kb24oJ2V4aXQtbW9iaWxlJywgX2V4aXRNb2JpbGUpO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIFJlc2l6ZWQgd2luZG93IChkZWJvdW5jZWQpXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX3Jlc2l6ZWQoKSB7XG5cdFx0XHRcdF9sYXlvdXRDYW52YXMuY3NzKHtcblx0XHRcdFx0XHRtaW5IZWlnaHQ6ICR3aW5kb3cuaW5uZXJIZWlnaHQgKyAncHgnXG5cdFx0XHRcdH0pO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIE9wZW4gbW9iaWxlIG5hdmlnYXRpb25cblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfb3Blbk5hdigpIHtcblx0XHRcdFx0XyRib2R5XG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduYXYtY2xvc2VkJylcblx0XHRcdFx0XHQuYWRkQ2xhc3MoJ25hdi1vcGVuJyk7XG5cblx0XHRcdFx0X25hdk9wZW4gPSB0cnVlO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIENsb3NlIG1vYmlsZSBuYXZpZ2F0aW9uXG5cdFx0XHQgKlxuXHRcdFx0ICogQHByaXZhdGVcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gX2Nsb3NlTmF2KCkge1xuXHRcdFx0XHRfJGJvZHlcblx0XHRcdFx0XHQucmVtb3ZlQ2xhc3MoJ25hdi1vcGVuJylcblx0XHRcdFx0XHQuYWRkQ2xhc3MoJ25hdi1jbG9zZWQnKTtcblxuXHRcdFx0XHRfbmF2T3BlbiA9IGZhbHNlO1xuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIFRvZ2dsZSBuYXYgb3Blbi9jbG9zZWRcblx0XHRcdCAqL1xuXHRcdFx0ZnVuY3Rpb24gdG9nZ2xlTmF2KCkge1xuXHRcdFx0XHRpZiAoIV9uYXZPcGVuKSB7XG5cdFx0XHRcdFx0X29wZW5OYXYoKTtcblx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRfY2xvc2VOYXYoKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXG5cdFx0XHQvKipcblx0XHRcdCAqIFdoZW4gY2hhbmdpbmcgbG9jYXRpb24sIGNsb3NlIHRoZSBuYXYgaWYgaXQncyBvcGVuXG5cdFx0XHQgKi9cblx0XHRcdGZ1bmN0aW9uIF8kbG9jYXRpb25DaGFuZ2VTdGFydCgpIHtcblx0XHRcdFx0aWYgKF9uYXZPcGVuKSB7XG5cdFx0XHRcdFx0X2Nsb3NlTmF2KCk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblxuXHRcdFx0LyoqXG5cdFx0XHQgKiBGdW5jdGlvbiB0byBleGVjdXRlIHdoZW4gZW50ZXJpbmcgbW9iaWxlIG1lZGlhIHF1ZXJ5XG5cdFx0XHQgKiBDbG9zZSBuYXYgYW5kIHNldCB1cCBtZW51IHRvZ2dsaW5nIGZ1bmN0aW9uYWxpdHlcblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfZW50ZXJNb2JpbGUobXEpIHtcblx0XHRcdFx0X2Nsb3NlTmF2KCk7XG5cblx0XHRcdFx0Ly8gYmluZCBmdW5jdGlvbiB0byB0b2dnbGUgbW9iaWxlIG5hdmlnYXRpb24gb3Blbi9jbG9zZWRcblx0XHRcdFx0JHNjb3BlLm5hdi50b2dnbGVOYXYgPSB0b2dnbGVOYXY7XG5cdFx0XHR9XG5cblx0XHRcdC8qKlxuXHRcdFx0ICogRnVuY3Rpb24gdG8gZXhlY3V0ZSB3aGVuIGV4aXRpbmcgbW9iaWxlIG1lZGlhIHF1ZXJ5XG5cdFx0XHQgKiBEaXNhYmxlIG1lbnUgdG9nZ2xpbmcgYW5kIHJlbW92ZSBib2R5IGNsYXNzZXNcblx0XHRcdCAqXG5cdFx0XHQgKiBAcHJpdmF0ZVxuXHRcdFx0ICovXG5cdFx0XHRmdW5jdGlvbiBfZXhpdE1vYmlsZShtcSkge1xuXHRcdFx0XHQvLyB1bmJpbmQgZnVuY3Rpb24gdG8gdG9nZ2xlIG1vYmlsZSBuYXZpZ2F0aW9uIG9wZW4vY2xvc2VkXG5cdFx0XHRcdCRzY29wZS5uYXYudG9nZ2xlTmF2ID0gbnVsbDtcblxuXHRcdFx0XHRfJGJvZHkucmVtb3ZlQ2xhc3MoJ25hdi1jbG9zZWQgbmF2LW9wZW4nKTtcblx0XHRcdH1cblx0XHR9XG5cdH1cblxufSgpKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0= \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImFwcC5tb2R1bGUuanMiLCJjb3JlL1BhZ2UuY3RybC5qcyIsImNvcmUvUGFnZS5mYWN0b3J5LmpzIiwiY29yZS9VdGlscy5mYWN0b3J5LmpzIiwibW9kdWxlcy9oZWFkZXIvSGVhZGVyLmN0cmwuanMiLCJtb2R1bGVzL2hlYWRlci9uYXZDb250cm9sLmRpci5qcyIsImNvcmUvYXBwLXNldHVwL2FwcC5jb25maWcuanMiLCJjb3JlL2dldC1kYXRhL0pTT05EYXRhLmZhY3RvcnkuanMiLCJjb3JlL2dldC1kYXRhL1Jlcy5mYWN0b3J5LmpzIiwiY29yZS91aS9sb2FkaW5nLmRpci5qcyIsImNvcmUvdWkvTVEuY29uc3RhbnQuanMiLCJjb3JlL3VpL3RydXN0QXNIVE1MLmZpbHRlci5qcyIsInBhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LmN0cmwuanMiLCJwYWdlcy9ob21lL0hvbWUuY3RybC5qcyIsInBhZ2VzL3N1Yi9zYW1wbGUuZGlyLmpzIiwicGFnZXMvc3ViL1N1Yi5jdHJsLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDTkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUM3SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3JDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMxQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUN4RUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUN2SUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNoREE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUMzQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ3hDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQy9KQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ1pBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQ2RBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNsQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FDckdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUNsRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoicmVTdGFydC1hcHAuanMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBhcHBsaWNhdGlvbiBtb2R1bGUgc2V0dGVyXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnLCBbJ25nUm91dGUnLCAnbmdSZXNvdXJjZScsICduZ1Nhbml0aXplJywgJ21lZGlhQ2hlY2snLCAncmVzaXplJ10pO1xyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbnRyb2xsZXIoJ1BhZ2VDdHJsJywgUGFnZUN0cmwpO1xyXG5cclxuXHRQYWdlQ3RybC4kaW5qZWN0ID0gWydQYWdlJywgJyRzY29wZScsICdNUScsICdtZWRpYUNoZWNrJywgJyRsb2cnXTtcclxuXHJcblx0ZnVuY3Rpb24gUGFnZUN0cmwoUGFnZSwgJHNjb3BlLCBNUSwgbWVkaWFDaGVjaywgJGxvZykge1xyXG5cdFx0dmFyIHBhZ2UgPSB0aGlzO1xyXG5cclxuXHRcdC8vIHByaXZhdGUgdmFyaWFibGVzXHJcblx0XHR2YXIgX2hhbmRsaW5nUm91dGVDaGFuZ2VFcnJvciA9IGZhbHNlO1xyXG5cdFx0Ly8gU2V0IHVwIGZ1bmN0aW9uYWxpdHkgdG8gcnVuIG9uIGVudGVyL2V4aXQgb2YgbWVkaWEgcXVlcnlcclxuXHRcdHZhciBfbWMgPSBtZWRpYUNoZWNrLmluaXQoe1xyXG5cdFx0XHRzY29wZTogJHNjb3BlLFxyXG5cdFx0XHRtZWRpYToge1xyXG5cdFx0XHRcdG1xOiBNUS5TTUFMTCxcclxuXHRcdFx0XHRlbnRlcjogX2VudGVyTW9iaWxlLFxyXG5cdFx0XHRcdGV4aXQ6IF9leGl0TW9iaWxlXHJcblx0XHRcdH0sXHJcblx0XHRcdGRlYm91bmNlOiAyMDBcclxuXHRcdH0pO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBhc3NvY2lhdGUgcGFnZSA8dGl0bGU+XHJcblx0XHRcdHBhZ2UucGFnZVRpdGxlID0gUGFnZTtcclxuXHJcblx0XHRcdCRzY29wZS4kb24oJyRyb3V0ZUNoYW5nZVN0YXJ0JywgX3JvdXRlQ2hhbmdlU3RhcnQpO1xyXG5cdFx0XHQkc2NvcGUuJG9uKCckcm91dGVDaGFuZ2VTdWNjZXNzJywgX3JvdXRlQ2hhbmdlU3VjY2Vzcyk7XHJcblx0XHRcdCRzY29wZS4kb24oJyRyb3V0ZUNoYW5nZUVycm9yJywgX3JvdXRlQ2hhbmdlRXJyb3IpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogRW50ZXIgbW9iaWxlIG1lZGlhIHF1ZXJ5XHJcblx0XHQgKiAkYnJvYWRjYXN0ICdlbnRlci1tb2JpbGUnIGV2ZW50XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2VudGVyTW9iaWxlKCkge1xyXG5cdFx0XHQkc2NvcGUuJGJyb2FkY2FzdCgnZW50ZXItbW9iaWxlJyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFeGl0IG1vYmlsZSBtZWRpYSBxdWVyeVxyXG5cdFx0ICogJGJyb2FkY2FzdCAnZXhpdC1tb2JpbGUnIGV2ZW50XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2V4aXRNb2JpbGUoKSB7XHJcblx0XHRcdCRzY29wZS4kYnJvYWRjYXN0KCdleGl0LW1vYmlsZScpO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogVHVybiBvbiBsb2FkaW5nIHN0YXRlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2xvYWRpbmdPbigpIHtcclxuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2xvYWRpbmctb24nKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFR1cm4gb2ZmIGxvYWRpbmcgc3RhdGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfbG9hZGluZ09mZigpIHtcclxuXHRcdFx0JHNjb3BlLiRicm9hZGNhc3QoJ2xvYWRpbmctb2ZmJyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBSb3V0ZSBjaGFuZ2Ugc3RhcnQgaGFuZGxlclxyXG5cdFx0ICogSWYgbmV4dCByb3V0ZSBoYXMgcmVzb2x2ZSwgdHVybiBvbiBsb2FkaW5nXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRldmVudCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIG5leHQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfcm91dGVDaGFuZ2VTdGFydCgkZXZlbnQsIG5leHQsIGN1cnJlbnQpIHtcclxuXHRcdFx0aWYgKG5leHQuJCRyb3V0ZSAmJiBuZXh0LiQkcm91dGUucmVzb2x2ZSkgeyAvLyBlc2xpbnQtZGlzYWJsZS1saW5lIGFuZ3VsYXIvbm8tcHJpdmF0ZS1jYWxsXHJcblx0XHRcdFx0X2xvYWRpbmdPbigpO1xyXG5cdFx0XHR9XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBSb3V0ZSBjaGFuZ2Ugc3VjY2VzcyBoYW5kbGVyXHJcblx0XHQgKiBNYXRjaCBjdXJyZW50IG1lZGlhIHF1ZXJ5IGFuZCBydW4gYXBwcm9wcmlhdGUgZnVuY3Rpb25cclxuXHRcdCAqIElmIGN1cnJlbnQgcm91dGUgaGFzIGJlZW4gcmVzb2x2ZWQsIHR1cm4gb2ZmIGxvYWRpbmdcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gJGV2ZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gY3VycmVudCB7b2JqZWN0fVxyXG5cdFx0ICogQHBhcmFtIHByZXZpb3VzIHtvYmplY3R9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfcm91dGVDaGFuZ2VTdWNjZXNzKCRldmVudCwgY3VycmVudCwgcHJldmlvdXMpIHtcclxuXHRcdFx0X21jLm1hdGNoQ3VycmVudChNUS5TTUFMTCk7XHJcblxyXG5cdFx0XHRpZiAoY3VycmVudC4kJHJvdXRlICYmIGN1cnJlbnQuJCRyb3V0ZS5yZXNvbHZlKSB7ICAgLy8gZXNsaW50LWRpc2FibGUtbGluZSBhbmd1bGFyL25vLXByaXZhdGUtY2FsbFxyXG5cdFx0XHRcdF9sb2FkaW5nT2ZmKCk7XHJcblx0XHRcdH1cclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFJvdXRlIGNoYW5nZSBlcnJvciBoYW5kbGVyXHJcblx0XHQgKiBIYW5kbGUgcm91dGUgcmVzb2x2ZSBmYWlsdXJlc1xyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSAkZXZlbnQge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSBjdXJyZW50IHtvYmplY3R9XHJcblx0XHQgKiBAcGFyYW0gcHJldmlvdXMge29iamVjdH1cclxuXHRcdCAqIEBwYXJhbSByZWplY3Rpb24ge29iamVjdH1cclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9yb3V0ZUNoYW5nZUVycm9yKCRldmVudCwgY3VycmVudCwgcHJldmlvdXMsIHJlamVjdGlvbikge1xyXG5cdFx0XHR2YXIgZGVzdGluYXRpb24gPSAoY3VycmVudCAmJiAoY3VycmVudC50aXRsZSB8fCBjdXJyZW50Lm5hbWUgfHwgY3VycmVudC5sb2FkZWRUZW1wbGF0ZVVybCkpIHx8ICd1bmtub3duIHRhcmdldCc7XHJcblx0XHRcdHZhciBtc2cgPSAnRXJyb3Igcm91dGluZyB0byAnICsgZGVzdGluYXRpb24gKyAnLiAnICsgKHJlamVjdGlvbi5tc2cgfHwgJycpO1xyXG5cclxuXHRcdFx0aWYgKF9oYW5kbGluZ1JvdXRlQ2hhbmdlRXJyb3IpIHtcclxuXHRcdFx0XHRyZXR1cm47XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdF9oYW5kbGluZ1JvdXRlQ2hhbmdlRXJyb3IgPSB0cnVlO1xyXG5cdFx0XHRfbG9hZGluZ09mZigpO1xyXG5cclxuXHRcdFx0JGxvZy5lcnJvcihtc2cpO1xyXG5cdFx0fVxyXG5cdFx0UGFnZUN0cmwuZW50ZXJNb2JpbGUgPSBfZW50ZXJNb2JpbGU7Ly90ZXN0IGNvZGVcclxuXHRcdFBhZ2VDdHJsLmV4aXRNb2JpbGUgPSBfZXhpdE1vYmlsZTsvL3Rlc3QgY29kZVxyXG5cdFx0UGFnZUN0cmwubG9hZGluZ09uID0gX2xvYWRpbmdPbjsvL3Rlc3QgY29kZVxyXG5cdFx0UGFnZUN0cmwubG9hZGluZ09mZiA9IF9sb2FkaW5nT2ZmOy8vdGVzdCBjb2RlXHJcblx0XHRyZXR1cm4gUGFnZUN0cmw7Ly90ZXN0IGNvZGVcclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuZmFjdG9yeSgnUGFnZScsIFBhZ2UpO1xyXG5cclxuXHRmdW5jdGlvbiBQYWdlKCkge1xyXG5cdFx0Ly8gcHJpdmF0ZSB2YXJzXHJcblx0XHR2YXIgc2l0ZVRpdGxlID0gJ3JlU3RhcnQgQW5ndWxhcic7XHJcblx0XHR2YXIgcGFnZVRpdGxlID0gJ0hvbWUnO1xyXG5cclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdGdldFRpdGxlOiBnZXRUaXRsZSxcclxuXHRcdFx0c2V0VGl0bGU6IHNldFRpdGxlXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogVGl0bGUgZnVuY3Rpb25cclxuXHRcdCAqIFNldHMgc2l0ZSB0aXRsZSBhbmQgcGFnZSB0aXRsZVxyXG5cdFx0ICpcclxuXHRcdCAqIEByZXR1cm5zIHtzdHJpbmd9IHNpdGUgdGl0bGUgKyBwYWdlIHRpdGxlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGdldFRpdGxlKCkge1xyXG5cdFx0XHRyZXR1cm4gc2l0ZVRpdGxlICsgJyB8ICcgKyBwYWdlVGl0bGU7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTZXQgcGFnZSB0aXRsZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSBuZXdUaXRsZSB7c3RyaW5nfVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBzZXRUaXRsZShuZXdUaXRsZSkge1xyXG5cdFx0XHRwYWdlVGl0bGUgPSBuZXdUaXRsZTtcclxuXHRcdH1cclxuXHR9XHJcbn0oKSk7IiwiLy8gXCJnbG9iYWxcIiBvYmplY3QgdG8gc2hhcmUgYmV0d2VlbiBjb250cm9sbGVyc1xyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5mYWN0b3J5KCdVdGlscycsIFV0aWxzKTtcclxuXHJcblx0ZnVuY3Rpb24gVXRpbHMoKSB7XHJcblx0XHR2YXIgZ3JlZXRpbmcgPSAnSGVsbG8nO1xyXG5cclxuXHRcdC8vIGNhbGxhYmxlIG1lbWJlcnNcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdGdyZWV0aW5nOiBncmVldGluZyxcclxuXHRcdFx0YWxlcnRHcmVldGluZzogYWxlcnRHcmVldGluZ1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFsZXJ0IGdyZWV0aW5nXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIG5hbWUge3N0cmluZ31cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gYWxlcnRHcmVldGluZyhuYW1lKSB7XHJcblx0XHRcdGFsZXJ0KGdyZWV0aW5nICsgJywgJyArIG5hbWUgKyAnIScpO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiLCIoZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdIZWFkZXJDdHJsJywgSGVhZGVyQ3RybCk7XHJcblxyXG5cdEhlYWRlckN0cmwuJGluamVjdCA9IFsnJGxvY2F0aW9uJywgJ0pTT05EYXRhJ107XHJcblxyXG5cdGZ1bmN0aW9uIEhlYWRlckN0cmwoJGxvY2F0aW9uLCBKU09ORGF0YSkge1xyXG5cdFx0Ly8gY29udHJvbGxlckFzIFZpZXdNb2RlbFxyXG5cdFx0dmFyIGhlYWRlciA9IHRoaXM7XHJcblxyXG5cdFx0Ly8gYmluZGFibGUgbWVtYmVyc1xyXG5cdFx0aGVhZGVyLmluZGV4SXNBY3RpdmUgPSBpbmRleElzQWN0aXZlO1xyXG5cdFx0aGVhZGVyLm5hdklzQWN0aXZlID0gbmF2SXNBY3RpdmU7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIGFjdGl2YXRlIGNvbnRyb2xsZXJcclxuXHRcdFx0X2FjdGl2YXRlKCk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBDb250cm9sbGVyIGFjdGl2YXRlXHJcblx0XHQgKiBHZXQgSlNPTiBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMgeyp9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfYWN0aXZhdGUoKSB7XHJcblx0XHRcdC8vIGdldCB0aGUgZGF0YSBmcm9tIEpTT05cclxuXHRcdFx0cmV0dXJuIEpTT05EYXRhLmdldExvY2FsRGF0YSgpLnRoZW4oX2dldEpzb25TdWNjZXNzKTtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIFN1Y2Nlc3NmdWwgcHJvbWlzZSBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIGRhdGEge2pzb259XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZ2V0SnNvblN1Y2Nlc3MoZGF0YSkge1xyXG5cdFx0XHRoZWFkZXIuanNvbiA9IGRhdGE7XHJcblx0XHRcdHJldHVybiBoZWFkZXIuanNvbjtcclxuXHRcdH1cclxuXHJcblx0XHQvKipcclxuXHRcdCAqIEFwcGx5IGNsYXNzIHRvIGluZGV4IG5hdiBpZiBhY3RpdmVcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0ge3N0cmluZ30gcGF0aFxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBpbmRleElzQWN0aXZlKHBhdGgpIHtcclxuXHRcdFx0Ly8gcGF0aCBzaG91bGQgYmUgJy8nXHJcblx0XHRcdHJldHVybiAkbG9jYXRpb24ucGF0aCgpID09PSBwYXRoO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogQXBwbHkgY2xhc3MgdG8gY3VycmVudGx5IGFjdGl2ZSBuYXYgaXRlbVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSB7c3RyaW5nfSBwYXRoXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIG5hdklzQWN0aXZlKHBhdGgpIHtcclxuXHRcdFx0cmV0dXJuICRsb2NhdGlvbi5wYXRoKCkuc3Vic3RyKDAsIHBhdGgubGVuZ3RoKSA9PT0gcGF0aDtcclxuXHRcdH1cclxuXHR9XHJcblxyXG59KCkpOyIsIihmdW5jdGlvbiAoKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5kaXJlY3RpdmUoJ25hdkNvbnRyb2wnLCBuYXZDb250cm9sKTtcclxuXHJcblx0bmF2Q29udHJvbC4kaW5qZWN0ID0gWyckd2luZG93JywgJ3Jlc2l6ZSddO1xyXG5cclxuXHRmdW5jdGlvbiBuYXZDb250cm9sKCR3aW5kb3csIHJlc2l6ZSkge1xyXG5cdFx0Ly8gcmV0dXJuIGRpcmVjdGl2ZVxyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0cmVzdHJpY3Q6ICdFQScsXHJcblx0XHRcdGxpbms6IG5hdkNvbnRyb2xMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogbmF2Q29udHJvbCBMSU5LIGZ1bmN0aW9uXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRzY29wZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBuYXZDb250cm9sTGluaygkc2NvcGUpIHtcclxuXHRcdFx0Ly8gcHJpdmF0ZSB2YXJpYWJsZXNcclxuXHRcdFx0dmFyIF8kYm9keSA9IGFuZ3VsYXIuZWxlbWVudCgnYm9keScpO1xyXG5cdFx0XHR2YXIgX2xheW91dENhbnZhcyA9IF8kYm9keS5maW5kKCcubGF5b3V0LWNhbnZhcycpO1xyXG5cdFx0XHR2YXIgX25hdk9wZW47XHJcblxyXG5cdFx0XHQvLyBkYXRhIG1vZGVsXHJcblx0XHRcdCRzY29wZS5uYXYgPSB7fTtcclxuXHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIGluaXRpYWxpemUgZGVib3VuY2VkIHJlc2l6ZVxyXG5cdFx0XHRcdHZhciBfcnMgPSByZXNpemUuaW5pdCh7XHJcblx0XHRcdFx0XHRzY29wZTogJHNjb3BlLFxyXG5cdFx0XHRcdFx0cmVzaXplZEZuOiBfcmVzaXplZCxcclxuXHRcdFx0XHRcdGRlYm91bmNlOiAxMDBcclxuXHRcdFx0XHR9KTtcclxuXHJcblx0XHRcdFx0JHNjb3BlLiRvbignJGxvY2F0aW9uQ2hhbmdlU3RhcnQnLCBfJGxvY2F0aW9uQ2hhbmdlU3RhcnQpO1xyXG5cdFx0XHRcdCRzY29wZS4kb24oJ2VudGVyLW1vYmlsZScsIF9lbnRlck1vYmlsZSk7XHJcblx0XHRcdFx0JHNjb3BlLiRvbignZXhpdC1tb2JpbGUnLCBfZXhpdE1vYmlsZSk7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBSZXNpemVkIHdpbmRvdyAoZGVib3VuY2VkKVxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX3Jlc2l6ZWQoKSB7XHJcblx0XHRcdFx0X2xheW91dENhbnZhcy5jc3Moe1xyXG5cdFx0XHRcdFx0bWluSGVpZ2h0OiAkd2luZG93LmlubmVySGVpZ2h0ICsgJ3B4J1xyXG5cdFx0XHRcdH0pO1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogT3BlbiBtb2JpbGUgbmF2aWdhdGlvblxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX29wZW5OYXYoKSB7XHJcblx0XHRcdFx0XyRib2R5XHJcblx0XHRcdFx0XHQucmVtb3ZlQ2xhc3MoJ25hdi1jbG9zZWQnKVxyXG5cdFx0XHRcdFx0LmFkZENsYXNzKCduYXYtb3BlbicpO1xyXG5cclxuXHRcdFx0XHRfbmF2T3BlbiA9IHRydWU7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBDbG9zZSBtb2JpbGUgbmF2aWdhdGlvblxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX2Nsb3NlTmF2KCkge1xyXG5cdFx0XHRcdF8kYm9keVxyXG5cdFx0XHRcdFx0LnJlbW92ZUNsYXNzKCduYXYtb3BlbicpXHJcblx0XHRcdFx0XHQuYWRkQ2xhc3MoJ25hdi1jbG9zZWQnKTtcclxuXHJcblx0XHRcdFx0X25hdk9wZW4gPSBmYWxzZTtcclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIFRvZ2dsZSBuYXYgb3Blbi9jbG9zZWRcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIHRvZ2dsZU5hdigpIHtcclxuXHRcdFx0XHRpZiAoIV9uYXZPcGVuKSB7XHJcblx0XHRcdFx0XHRfb3Blbk5hdigpO1xyXG5cdFx0XHRcdH0gZWxzZSB7XHJcblx0XHRcdFx0XHRfY2xvc2VOYXYoKTtcclxuXHRcdFx0XHR9XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBXaGVuIGNoYW5naW5nIGxvY2F0aW9uLCBjbG9zZSB0aGUgbmF2IGlmIGl0J3Mgb3BlblxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gXyRsb2NhdGlvbkNoYW5nZVN0YXJ0KCkge1xyXG5cdFx0XHRcdGlmIChfbmF2T3Blbikge1xyXG5cdFx0XHRcdFx0X2Nsb3NlTmF2KCk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogRnVuY3Rpb24gdG8gZXhlY3V0ZSB3aGVuIGVudGVyaW5nIG1vYmlsZSBtZWRpYSBxdWVyeVxyXG5cdFx0XHQgKiBDbG9zZSBuYXYgYW5kIHNldCB1cCBtZW51IHRvZ2dsaW5nIGZ1bmN0aW9uYWxpdHlcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9lbnRlck1vYmlsZShtcSkge1xyXG5cdFx0XHRcdF9jbG9zZU5hdigpO1xyXG5cclxuXHRcdFx0XHQvLyBiaW5kIGZ1bmN0aW9uIHRvIHRvZ2dsZSBtb2JpbGUgbmF2aWdhdGlvbiBvcGVuL2Nsb3NlZFxyXG5cdFx0XHRcdCRzY29wZS5uYXYudG9nZ2xlTmF2ID0gdG9nZ2xlTmF2O1xyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogRnVuY3Rpb24gdG8gZXhlY3V0ZSB3aGVuIGV4aXRpbmcgbW9iaWxlIG1lZGlhIHF1ZXJ5XHJcblx0XHRcdCAqIERpc2FibGUgbWVudSB0b2dnbGluZyBhbmQgcmVtb3ZlIGJvZHkgY2xhc3Nlc1xyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX2V4aXRNb2JpbGUobXEpIHtcclxuXHRcdFx0XHQvLyB1bmJpbmQgZnVuY3Rpb24gdG8gdG9nZ2xlIG1vYmlsZSBuYXZpZ2F0aW9uIG9wZW4vY2xvc2VkXHJcblx0XHRcdFx0JHNjb3BlLm5hdi50b2dnbGVOYXYgPSBudWxsO1xyXG5cclxuXHRcdFx0XHRfJGJvZHkucmVtb3ZlQ2xhc3MoJ25hdi1jbG9zZWQgbmF2LW9wZW4nKTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxuXHJcbn0oKSk7IiwiLy8gYXBwbGljYXRpb24gY29uZmlnXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmNvbmZpZyhhcHBDb25maWcpO1xyXG5cclxuXHRhcHBDb25maWcuJGluamVjdCA9IFsnJHJvdXRlUHJvdmlkZXInLCAnJGxvY2F0aW9uUHJvdmlkZXInXTtcclxuXHJcblx0ZnVuY3Rpb24gYXBwQ29uZmlnKCRyb3V0ZVByb3ZpZGVyLCAkbG9jYXRpb25Qcm92aWRlcikge1xyXG5cdFx0JHJvdXRlUHJvdmlkZXJcclxuXHRcdFx0LndoZW4oJy8nLCB7XHJcblx0XHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9wYWdlcy9ob21lL0hvbWUudmlldy5odG1sJyxcclxuXHRcdFx0XHRjb250cm9sbGVyOiAnSG9tZUN0cmwnLFxyXG5cdFx0XHRcdGNvbnRyb2xsZXJBczogJ2hvbWUnXHJcblx0XHRcdH0pXHJcblx0XHRcdC53aGVuKCcvc3VicGFnZScsIHtcclxuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL3N1Yi9TdWIudmlldy5odG1sJyxcclxuXHRcdFx0XHRjb250cm9sbGVyOiAnU3ViQ3RybCcsXHJcblx0XHRcdFx0Y29udHJvbGxlckFzOiAnc3ViJyxcclxuXHRcdFx0XHRyZXNvbHZlOiB7XHJcblx0XHRcdFx0XHRyZXNvbHZlTG9jYWxEYXRhOiByZXNvbHZlTG9jYWxEYXRhXHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9KVxyXG5cdFx0XHQub3RoZXJ3aXNlKHtcclxuXHRcdFx0XHR0ZW1wbGF0ZVVybDogJ3JlU3RhcnQtYXBwL3BhZ2VzL2Vycm9yNDA0L0Vycm9yNDA0LnZpZXcuaHRtbCcsXHJcblx0XHRcdFx0Y29udHJvbGxlcjogJ0Vycm9yNDA0Q3RybCcsXHJcblx0XHRcdFx0Y29udHJvbGxlckFzOiAnZTQwNCdcclxuXHRcdFx0fSk7XHJcblxyXG5cdFx0JGxvY2F0aW9uUHJvdmlkZXJcclxuXHRcdFx0Lmh0bWw1TW9kZSh7XHJcblx0XHRcdFx0ZW5hYmxlZDogdHJ1ZVxyXG5cdFx0XHR9KVxyXG5cdFx0XHQuaGFzaFByZWZpeCgnIScpO1xyXG5cdH1cclxuXHJcblx0cmVzb2x2ZUxvY2FsRGF0YS4kaW5qZWN0ID0gWydKU09ORGF0YSddO1xyXG5cdC8qKlxyXG5cdCAqIEdldCBsb2NhbCBkYXRhIGZvciByb3V0ZSByZXNvbHZlXHJcblx0ICpcclxuXHQgKiBAcGFyYW0gSlNPTkRhdGEge2ZhY3Rvcnl9XHJcblx0ICogQHJldHVybnMge3Byb21pc2V9IGRhdGFcclxuXHQgKi9cclxuXHRmdW5jdGlvbiByZXNvbHZlTG9jYWxEYXRhKEpTT05EYXRhKSB7XHJcblx0XHRyZXR1cm4gSlNPTkRhdGEuZ2V0TG9jYWxEYXRhKCk7XHJcblx0fVxyXG59KCkpOyIsIi8vIGZldGNoIEpTT04gZGF0YSB0byBzaGFyZSBiZXR3ZWVuIGNvbnRyb2xsZXJzXHJcbihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmZhY3RvcnkoJ0pTT05EYXRhJywgSlNPTkRhdGEpO1xyXG5cclxuXHRKU09ORGF0YS4kaW5qZWN0ID0gWyckaHR0cCcsICdSZXMnXTtcclxuXHJcblx0ZnVuY3Rpb24gSlNPTkRhdGEoJGh0dHAsIFJlcykge1xyXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0Z2V0TG9jYWxEYXRhOiBnZXRMb2NhbERhdGFcclxuXHRcdH07XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBHRVQgbG9jYWwgSlNPTiBkYXRhIGZpbGUgYW5kIHJldHVybiByZXN1bHRzXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMge3Byb21pc2V9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIGdldExvY2FsRGF0YSgpIHtcclxuXHRcdFx0cmV0dXJuICRodHRwXHJcblx0XHRcdFx0LmdldCgnL2RhdGEvZGF0YS5qc29uJylcclxuXHRcdFx0XHQudGhlbihSZXMuc3VjY2VzcywgUmVzLmVycm9yKTtcclxuXHRcdH1cclxuXHR9XHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuZmFjdG9yeSgnUmVzJywgUmVzKTtcclxuXHJcblx0ZnVuY3Rpb24gUmVzKCkge1xyXG5cdFx0Ly8gY2FsbGFibGUgbWVtYmVyc1xyXG5cdFx0cmV0dXJuIHtcclxuXHRcdFx0c3VjY2Vzczogc3VjY2VzcyxcclxuXHRcdFx0ZXJyb3I6IGVycm9yXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogUHJvbWlzZSByZXNwb25zZSBmdW5jdGlvblxyXG5cdFx0ICogQ2hlY2tzIHR5cGVvZiBkYXRhIHJldHVybmVkIGFuZCBzdWNjZWVkcyBpZiBKUyBvYmplY3QsIHRocm93cyBlcnJvciBpZiBub3RcclxuXHRcdCAqIFVzZWZ1bCBmb3IgQVBJcyAoaWUsIHdpdGggbmdpbngpIHdoZXJlIHNlcnZlciBlcnJvciBIVE1MIHBhZ2UgbWF5IGJlIHJldHVybmVkIGluIGVycm9yXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtIHJlc3BvbnNlIHsqfSBkYXRhIGZyb20gJGh0dHBcclxuXHRcdCAqIEByZXR1cm5zIHsqfSBvYmplY3QsIGFycmF5XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIHN1Y2Nlc3MocmVzcG9uc2UpIHtcclxuXHRcdFx0aWYgKGFuZ3VsYXIuaXNPYmplY3QocmVzcG9uc2UuZGF0YSkpIHtcclxuXHRcdFx0XHRyZXR1cm4gcmVzcG9uc2UuZGF0YTtcclxuXHRcdFx0fSBlbHNlIHtcclxuXHRcdFx0XHR0aHJvdyBuZXcgRXJyb3IoJ3JldHJpZXZlZCBkYXRhIGlzIG5vdCB0eXBlb2Ygb2JqZWN0LicpO1xyXG5cdFx0XHR9XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBQcm9taXNlIHJlc3BvbnNlIGZ1bmN0aW9uIC0gZXJyb3JcclxuXHRcdCAqIFRocm93cyBhbiBlcnJvciB3aXRoIGVycm9yIGRhdGFcclxuXHRcdCAqXHJcblx0XHQgKiBAcGFyYW0gZXJyb3Ige29iamVjdH1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gZXJyb3IoZXJyb3IpIHtcclxuXHRcdFx0dGhyb3cgbmV3IEVycm9yKCdFcnJvciByZXRyaWV2aW5nIGRhdGEnLCBlcnJvcik7XHJcblx0XHR9XHJcblx0fVxyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmRpcmVjdGl2ZSgnbG9hZGluZycsIGxvYWRpbmcpO1xyXG5cclxuXHRsb2FkaW5nLiRpbmplY3QgPSBbJyR3aW5kb3cnLCAncmVzaXplJ107XHJcblxyXG5cdGZ1bmN0aW9uIGxvYWRpbmcoJHdpbmRvdywgcmVzaXplKSB7XHJcblx0XHQvLyByZXR1cm4gZGlyZWN0aXZlXHJcblx0XHRyZXR1cm4ge1xyXG5cdFx0XHRyZXN0cmljdDogJ0VBJyxcclxuXHRcdFx0cmVwbGFjZTogdHJ1ZSxcclxuXHRcdFx0dGVtcGxhdGVVcmw6ICdyZVN0YXJ0LWFwcC9jb3JlL3VpL2xvYWRpbmcudHBsLmh0bWwnLFxyXG5cdFx0XHR0cmFuc2NsdWRlOiB0cnVlLFxyXG5cdFx0XHRjb250cm9sbGVyOiBsb2FkaW5nQ3RybCxcclxuXHRcdFx0Y29udHJvbGxlckFzOiAnbG9hZGluZycsXHJcblx0XHRcdGJpbmRUb0NvbnRyb2xsZXI6IHRydWUsXHJcblx0XHRcdGxpbms6IGxvYWRpbmdMaW5rXHJcblx0XHR9O1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogbG9hZGluZyBMSU5LXHJcblx0XHQgKiBEaXNhYmxlcyBwYWdlIHNjcm9sbGluZyB3aGVuIGxvYWRpbmcgb3ZlcmxheSBpcyBvcGVuXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRzY29wZVxyXG5cdFx0ICogQHBhcmFtICRlbGVtZW50XHJcblx0XHQgKiBAcGFyYW0gJGF0dHJzXHJcblx0XHQgKiBAcGFyYW0gbG9hZGluZyB7Y29udHJvbGxlcn1cclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gbG9hZGluZ0xpbmsoJHNjb3BlLCAkZWxlbWVudCwgJGF0dHJzLCBsb2FkaW5nKSB7XHJcblx0XHRcdC8vIHByaXZhdGUgdmFyaWFibGVzXHJcblx0XHRcdHZhciBfJGJvZHkgPSBhbmd1bGFyLmVsZW1lbnQoJ2JvZHknKTtcclxuXHRcdFx0dmFyIF93aW5IZWlnaHQgPSAkd2luZG93LmlubmVySGVpZ2h0ICsgJ3B4JztcclxuXHJcblx0XHRcdF9pbml0KCk7XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHRcdC8vIGluaXRpYWxpemUgZGVib3VuY2VkIHJlc2l6ZVxyXG5cdFx0XHRcdHZhciBfcnMgPSByZXNpemUuaW5pdCh7XHJcblx0XHRcdFx0XHRzY29wZTogJHNjb3BlLFxyXG5cdFx0XHRcdFx0cmVzaXplZEZuOiBfcmVzaXplZCxcclxuXHRcdFx0XHRcdGRlYm91bmNlOiAyMDBcclxuXHRcdFx0XHR9KTtcclxuXHJcblx0XHRcdFx0Ly8gJHdhdGNoIGFjdGl2ZSBzdGF0ZVxyXG5cdFx0XHRcdCRzY29wZS4kd2F0Y2goJ2xvYWRpbmcuYWN0aXZlJywgXyR3YXRjaEFjdGl2ZSk7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBXaW5kb3cgcmVzaXplZFxyXG5cdFx0XHQgKiBJZiBsb2FkaW5nLCByZWFwcGx5IGJvZHkgaGVpZ2h0XHJcblx0XHRcdCAqIHRvIHByZXZlbnQgc2Nyb2xsYmFyXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfcmVzaXplZCgpIHtcclxuXHRcdFx0XHRfd2luSGVpZ2h0ID0gJHdpbmRvdy5pbm5lckhlaWdodCArICdweCc7XHJcblxyXG5cdFx0XHRcdGlmIChsb2FkaW5nLmFjdGl2ZSkge1xyXG5cdFx0XHRcdFx0XyRib2R5LmNzcyh7XHJcblx0XHRcdFx0XHRcdGhlaWdodDogX3dpbkhlaWdodCxcclxuXHRcdFx0XHRcdFx0b3ZlcmZsb3dZOiAnaGlkZGVuJ1xyXG5cdFx0XHRcdFx0fSk7XHJcblx0XHRcdFx0fVxyXG5cdFx0XHR9XHJcblxyXG5cdFx0XHQvKipcclxuXHRcdFx0ICogJHdhdGNoIGxvYWRpbmcuYWN0aXZlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwYXJhbSBuZXdWYWwge2Jvb2xlYW59XHJcblx0XHRcdCAqIEBwYXJhbSBvbGRWYWwge3VuZGVmaW5lZHxib29sZWFufVxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gXyR3YXRjaEFjdGl2ZShuZXdWYWwsIG9sZFZhbCkge1xyXG5cdFx0XHRcdGlmIChuZXdWYWwpIHtcclxuXHRcdFx0XHRcdF9vcGVuKCk7XHJcblx0XHRcdFx0fSBlbHNlIHtcclxuXHRcdFx0XHRcdF9jbG9zZSgpO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIE9wZW4gbG9hZGluZ1xyXG5cdFx0XHQgKiBEaXNhYmxlIHNjcm9sbFxyXG5cdFx0XHQgKlxyXG5cdFx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0XHQgKi9cclxuXHRcdFx0ZnVuY3Rpb24gX29wZW4oKSB7XHJcblx0XHRcdFx0XyRib2R5LmNzcyh7XHJcblx0XHRcdFx0XHRoZWlnaHQ6IF93aW5IZWlnaHQsXHJcblx0XHRcdFx0XHRvdmVyZmxvd1k6ICdoaWRkZW4nXHJcblx0XHRcdFx0fSk7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiBDbG9zZSBsb2FkaW5nXHJcblx0XHRcdCAqIEVuYWJsZSBzY3JvbGxcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF9jbG9zZSgpIHtcclxuXHRcdFx0XHRfJGJvZHkuY3NzKHtcclxuXHRcdFx0XHRcdGhlaWdodDogJ2F1dG8nLFxyXG5cdFx0XHRcdFx0b3ZlcmZsb3dZOiAnYXV0bydcclxuXHRcdFx0XHR9KTtcclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxuXHJcblx0bG9hZGluZ0N0cmwuJGluamVjdCA9IFsnJHNjb3BlJ107XHJcblx0LyoqXHJcblx0ICogbG9hZGluZyBDT05UUk9MTEVSXHJcblx0ICogVXBkYXRlIHRoZSBsb2FkaW5nIHN0YXR1cyBiYXNlZFxyXG5cdCAqIG9uIHJvdXRlQ2hhbmdlIHN0YXRlXHJcblx0ICovXHJcblx0ZnVuY3Rpb24gbG9hZGluZ0N0cmwoJHNjb3BlKSB7XHJcblx0XHR2YXIgbG9hZGluZyA9IHRoaXM7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHR1cm4gb24gbG9hZGluZyBmb3IgaW5pdGlhbCBwYWdlIGxvYWRcclxuXHRcdFx0X2xvYWRpbmdBY3RpdmUoKTtcclxuXHJcblx0XHRcdCRzY29wZS4kb24oJ2xvYWRpbmctb24nLCBfbG9hZGluZ0FjdGl2ZSk7XHJcblx0XHRcdCRzY29wZS4kb24oJ2xvYWRpbmctb2ZmJywgX2xvYWRpbmdJbmFjdGl2ZSk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTZXQgbG9hZGluZyB0byBhY3RpdmVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfbG9hZGluZ0FjdGl2ZSgpIHtcclxuXHRcdFx0bG9hZGluZy5hY3RpdmUgPSB0cnVlO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogU2V0IGxvYWRpbmcgdG8gaW5hY3RpdmVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfbG9hZGluZ0luYWN0aXZlKCkge1xyXG5cdFx0XHRsb2FkaW5nLmFjdGl2ZSA9IGZhbHNlO1xyXG5cdFx0fVxyXG5cdH1cclxuXHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0Ly8gbWVkaWEgcXVlcnkgY29uc3RhbnRzXHJcblx0dmFyIE1RID0ge1xyXG5cdFx0U01BTEw6ICcobWF4LXdpZHRoOiA3NjdweCknLFxyXG5cdFx0TEFSR0U6ICcobWluLXdpZHRoOiA3NjhweCknXHJcblx0fTtcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29uc3RhbnQoJ01RJywgTVEpO1xyXG59KCkpOyIsIihmdW5jdGlvbigpIHtcclxuXHQndXNlIHN0cmljdCc7XHJcblxyXG5cdGFuZ3VsYXJcclxuXHRcdC5tb2R1bGUoJ3JlU3RhcnQnKVxyXG5cdFx0LmZpbHRlcigndHJ1c3RBc0hUTUwnLCB0cnVzdEFzSFRNTCk7XHJcblxyXG5cdHRydXN0QXNIVE1MLiRpbmplY3QgPSBbJyRzY2UnXTtcclxuXHJcblx0ZnVuY3Rpb24gdHJ1c3RBc0hUTUwoJHNjZSkge1xyXG5cdFx0cmV0dXJuIGZ1bmN0aW9uKHRleHQpIHtcclxuXHRcdFx0cmV0dXJuICRzY2UudHJ1c3RBc0h0bWwodGV4dCk7XHJcblx0XHR9O1xyXG5cdH1cclxufSgpKTsiLCIoZnVuY3Rpb24gKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignRXJyb3I0MDRDdHJsJywgRXJyb3I0MDRDdHJsKTtcclxuXHJcblx0RXJyb3I0MDRDdHJsLiRpbmplY3QgPSBbJyRzY29wZScsICdQYWdlJ107XHJcblxyXG5cdGZ1bmN0aW9uIEVycm9yNDA0Q3RybCgkc2NvcGUsIFBhZ2UpIHtcclxuXHRcdHZhciBlNDA0ID0gdGhpcztcclxuXHJcblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXHJcblx0XHRlNDA0LnRpdGxlID0gJzQwNCAtIFBhZ2UgTm90IEZvdW5kJztcclxuXHJcblx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogSU5JVCBmdW5jdGlvbiBleGVjdXRlcyBwcm9jZWR1cmFsIGNvZGVcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0Ly8gc2V0IHBhZ2UgPHRpdGxlPlxyXG5cdFx0XHRQYWdlLnNldFRpdGxlKGU0MDQudGl0bGUpO1xyXG5cclxuXHRcdFx0Ly8gbm8gZGF0YSB0byBsb2FkLCBidXQgbG9hZGluZyBzdGF0ZSBtaWdodCBiZSBvblxyXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb2ZmJyk7XHJcblx0XHR9XHJcblxyXG5cdFx0cmV0dXJuIHsgICAgICAgIC8vdGVzdCBjb2RlXHJcblx0XHRcdGluaXQ6IF9pbml0IC8vdGVzdCBjb2RlXHJcblx0XHR9OyAgICAgICAgICAgICAgIC8vdGVzdCBjb2RlXHJcblx0fVxyXG59KCkpOyIsIihmdW5jdGlvbiAoKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5jb250cm9sbGVyKCdIb21lQ3RybCcsIEhvbWVDdHJsKTtcclxuXHJcblx0SG9tZUN0cmwuJGluamVjdCA9IFsnJHNjb3BlJywgJ1V0aWxzJywgJ1BhZ2UnLCAnSlNPTkRhdGEnXTtcclxuXHJcblx0ZnVuY3Rpb24gSG9tZUN0cmwoJHNjb3BlLCBVdGlscywgUGFnZSwgSlNPTkRhdGEpIHtcclxuXHRcdC8vIGNvbnRyb2xsZXJBcyBWaWV3TW9kZWxcclxuXHRcdHZhciBob21lID0gdGhpcztcclxuXHJcblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXHJcblx0XHRob21lLnRpdGxlID0gJ0hvbWUnO1xyXG5cdFx0aG9tZS5nbG9iYWwgPSBVdGlscztcclxuXHRcdGhvbWUubmFtZSA9ICdWaXNpdG9yJztcclxuXHRcdGhvbWUuYWxlcnRHcmVldGluZyA9IFV0aWxzLmFsZXJ0R3JlZXRpbmc7XHJcblx0XHRob21lLnN0cmluZ09mSFRNTCA9ICc8c3Ryb25nIHN0eWxlPVwiY29sb3I6IGdyZWVuO1wiPlNvbWUgZ3JlZW4gdGV4dDwvc3Ryb25nPiBib3VuZCBhcyBIVE1MIHdpdGggYSA8YSBocmVmPVwiI1wiPmxpbms8L2E+LCB0cnVzdGVkIHdpdGggU0NFISc7XHJcblx0XHRob21lLnZpZXdmb3JtYXQgPSBudWxsO1xyXG5cclxuXHRcdF9pbml0KCk7XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBJTklUIGZ1bmN0aW9uIGV4ZWN1dGVzIHByb2NlZHVyYWwgY29kZVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwcml2YXRlXHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIF9pbml0KCkge1xyXG5cdFx0XHQvLyBzZXQgcGFnZSA8dGl0bGU+XHJcblx0XHRcdFBhZ2Uuc2V0VGl0bGUoaG9tZS50aXRsZSk7XHJcblxyXG5cdFx0XHQvLyBhY3RpdmF0ZSBjb250cm9sbGVyXHJcblx0XHRcdF9hY3RpdmF0ZSgpO1xyXG5cclxuXHRcdFx0Ly8gbWVkaWFxdWVyeSBldmVudHNcclxuXHRcdFx0JHNjb3BlLiRvbignZW50ZXItbW9iaWxlJywgX2VudGVyTW9iaWxlKTtcclxuXHRcdFx0JHNjb3BlLiRvbignZXhpdC1tb2JpbGUnLCBfZXhpdE1vYmlsZSk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBDb250cm9sbGVyIGFjdGl2YXRlXHJcblx0XHQgKiBHZXQgSlNPTiBkYXRhXHJcblx0XHQgKlxyXG5cdFx0ICogQHJldHVybnMgeyp9XHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfYWN0aXZhdGUoKSB7XHJcblx0XHRcdC8vIHN0YXJ0IGxvYWRpbmdcclxuXHRcdFx0JHNjb3BlLiRlbWl0KCdsb2FkaW5nLW9uJyk7XHJcblxyXG5cdFx0XHQvLyBnZXQgdGhlIGRhdGEgZnJvbSBKU09OXHJcblx0XHRcdHJldHVybiBKU09ORGF0YS5nZXRMb2NhbERhdGEoKS50aGVuKF9nZXRKc29uU3VjY2Vzcyk7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBTdWNjZXNzZnVsIHByb21pc2UgZGF0YVxyXG5cdFx0ICpcclxuXHRcdCAqIEBwYXJhbSBkYXRhIHtqc29ufVxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2dldEpzb25TdWNjZXNzKGRhdGEpIHtcclxuXHRcdFx0aG9tZS5qc29uID0gZGF0YTtcclxuXHJcblx0XHRcdC8vIHN0b3AgbG9hZGluZ1xyXG5cdFx0XHQkc2NvcGUuJGVtaXQoJ2xvYWRpbmctb2ZmJyk7XHJcblxyXG5cdFx0XHRyZXR1cm4gaG9tZS5qc29uO1xyXG5cdFx0fVxyXG5cclxuXHRcdC8qKlxyXG5cdFx0ICogRW50ZXIgc21hbGwgbXFcclxuXHRcdCAqIFNldCBob21lLnZpZXdmb3JtYXRcclxuXHRcdCAqXHJcblx0XHQgKiBAcHJpdmF0ZVxyXG5cdFx0ICovXHJcblx0XHRmdW5jdGlvbiBfZW50ZXJNb2JpbGUoKSB7XHJcblx0XHRcdGhvbWUudmlld2Zvcm1hdCA9ICdzbWFsbCc7XHJcblx0XHR9XHJcblxyXG5cdFx0LyoqXHJcblx0XHQgKiBFeGl0IHNtYWxsIG1xXHJcblx0XHQgKiBTZXQgaG9tZS52aWV3Zm9ybWF0XHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2V4aXRNb2JpbGUoKSB7XHJcblx0XHRcdGhvbWUudmlld2Zvcm1hdCA9ICdsYXJnZSc7XHJcblx0XHR9XHJcblxyXG5cdFx0ZnVuY3Rpb24gZ2V0VmlldygpIHsgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdFx0cmV0dXJuIGhvbWUudmlld2Zvcm1hdDsgLy90ZXN0IGNvZGVcclxuXHRcdH0gICAgICAgICAgICAgICAgICAgICAgICAgICAvL3Rlc3QgY29kZVxyXG5cclxuXHRcdGhvbWUuZW50ZXJNb2JpbGUgPSBfZW50ZXJNb2JpbGU7ICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdGhvbWUuZXhpdE1vYmlsZSA9IF9leGl0TW9iaWxlOyAgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdGhvbWUuZ2V0SnNvblN1Y2VzcyA9IF9nZXRKc29uU3VjY2VzczsgLy90ZXN0IGNvZGVcclxuXHRcdGhvbWUuYWN0aXZhdGUgPSBfYWN0aXZhdGU7ICAgICAgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdGhvbWUuZ2V0VmlldyA9IGdldFZpZXc7ICAgICAgICAgICAgICAgLy90ZXN0IGNvZGVcclxuXHRcdHJldHVybiBob21lOyAgICAgICAgICAgICAgICAgICAgICAgICAgLy90ZXN0IGNvZGVcclxuXHR9XHJcbn0oKSk7IiwiLyoqXHJcbiAqIERpcmVjdGl2ZXMgKGFuZCBhc3NvY2lhdGVkIGF0dHJpYnV0ZXMpIGFyZSBhbHdheXMgZGVjbGFyZWQgYXMgY2FtZWxDYXNlIGluIEpTIGFuZCBzbmFrZS1jYXNlIGluIEhUTUxcclxuICogQW5ndWxhcidzIGJ1aWx0LWluIDxhPiBkaXJlY3RpdmUgYXV0b21hdGljYWxseSBpbXBsZW1lbnRzIHByZXZlbnREZWZhdWx0IG9uIGxpbmtzIHRoYXQgZG9uJ3QgaGF2ZSBhbiBocmVmIGF0dHJpYnV0ZVxyXG4gKiBDb21wbGV4IEphdmFTY3JpcHQgRE9NIG1hbmlwdWxhdGlvbiBzaG91bGQgYWx3YXlzIGJlIGRvbmUgaW4gZGlyZWN0aXZlIGxpbmsgZnVuY3Rpb25zLCBhbmQgJGFwcGx5IHNob3VsZCBuZXZlciBiZSB1c2VkIGluIGEgY29udHJvbGxlciEgU2ltcGxlIERPTSBtYW5pcHVsYXRpb24gc2hvdWxkIGJlIGluIHRoZSB2aWV3LlxyXG4gKi9cclxuXHJcbi8qLS0tIFNhbXBsZSBEaXJlY3RpdmUgd2l0aCBhICR3YXRjaCAtLS0qL1xyXG4oZnVuY3Rpb24oKSB7XHJcblx0J3VzZSBzdHJpY3QnO1xyXG5cclxuXHRhbmd1bGFyXHJcblx0XHQubW9kdWxlKCdyZVN0YXJ0JylcclxuXHRcdC5kaXJlY3RpdmUoJ3NhbXBsZURpcmVjdGl2ZScsIHNhbXBsZURpcmVjdGl2ZSk7XHJcblxyXG5cdHNhbXBsZURpcmVjdGl2ZS4kaW5qZWN0ID0gWyckdGltZW91dCddO1xyXG5cclxuXHRmdW5jdGlvbiBzYW1wbGVEaXJlY3RpdmUoJHRpbWVvdXQpIHtcclxuXHRcdC8vIHJldHVybiBkaXJlY3RpdmVcclxuXHRcdHJldHVybiB7XHJcblx0XHRcdHJlc3RyaWN0OiAnRUEnLFxyXG5cdFx0XHRyZXBsYWNlOiB0cnVlLFxyXG5cdFx0XHRzY29wZToge30sXHJcblx0XHRcdHRlbXBsYXRlVXJsOiAncmVTdGFydC1hcHAvcGFnZXMvc3ViL3NhbXBsZS50cGwuaHRtbCcsXHJcblx0XHRcdHRyYW5zY2x1ZGU6IHRydWUsXHJcblx0XHRcdGNvbnRyb2xsZXI6IFNhbXBsZURpcmVjdGl2ZUN0cmwsXHJcblx0XHRcdGNvbnRyb2xsZXJBczogJ3NkJyxcclxuXHRcdFx0YmluZFRvQ29udHJvbGxlcjoge1xyXG5cdFx0XHRcdGpzb25EYXRhOiAnPSdcclxuXHRcdFx0fSxcclxuXHRcdFx0bGluazogc2FtcGxlRGlyZWN0aXZlTGlua1xyXG5cdFx0fTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIHNhbXBsZURpcmVjdGl2ZSBMSU5LIGZ1bmN0aW9uXHJcblx0XHQgKlxyXG5cdFx0ICogQHBhcmFtICRzY29wZVxyXG5cdFx0ICogQHBhcmFtICRlbGVtZW50XHJcblx0XHQgKiBAcGFyYW0gJGF0dHJzXHJcblx0XHQgKiBAcGFyYW0gc2Qge2NvbnRyb2xsZXJ9XHJcblx0XHQgKi9cclxuXHRcdGZ1bmN0aW9uIHNhbXBsZURpcmVjdGl2ZUxpbmsoJHNjb3BlLCAkZWxlbWVudCwgJGF0dHJzLCBzZCkge1xyXG5cdFx0XHRfaW5pdCgpO1xyXG5cclxuXHRcdFx0LyoqXHJcblx0XHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHRcdCAqXHJcblx0XHRcdCAqIEBwcml2YXRlXHJcblx0XHRcdCAqL1xyXG5cdFx0XHRmdW5jdGlvbiBfaW5pdCgpIHtcclxuXHRcdFx0XHQvLyB3YXRjaCBmb3IgYXN5bmMgZGF0YSB0byBiZWNvbWUgYXZhaWxhYmxlIGFuZCB1cGRhdGVcclxuXHRcdFx0XHQkc2NvcGUuJHdhdGNoKCdzZC5qc29uRGF0YScsIF8kd2F0Y2hKc29uRGF0YSk7XHJcblx0XHRcdH1cclxuXHJcblx0XHRcdC8qKlxyXG5cdFx0XHQgKiAkd2F0Y2ggZm9yIHNkLmpzb25EYXRhIHRvIGJlY29tZSBhdmFpbGFibGVcclxuXHRcdFx0ICpcclxuXHRcdFx0ICogQHBhcmFtIG5ld1ZhbCB7Kn1cclxuXHRcdFx0ICogQHBhcmFtIG9sZFZhbCB7Kn1cclxuXHRcdFx0ICogQHByaXZhdGVcclxuXHRcdFx0ICovXHJcblx0XHRcdGZ1bmN0aW9uIF8kd2F0Y2hKc29uRGF0YShuZXdWYWwsIG9sZFZhbCkge1xyXG5cdFx0XHRcdGlmIChuZXdWYWwpIHtcclxuXHRcdFx0XHRcdHNkLmpzb25EYXRhID0gbmV3VmFsO1xyXG5cclxuXHRcdFx0XHRcdCR0aW1lb3V0KGZ1bmN0aW9uKCkge1xyXG5cdFx0XHRcdFx0XHRjb25zb2xlLmxvZygnZGVtb25zdHJhdGUgJHRpbWVvdXQgaW5qZWN0aW9uIGluIGEgZGlyZWN0aXZlIGxpbmsgZnVuY3Rpb24nKTtcclxuXHRcdFx0XHRcdH0sIDEwMDApO1xyXG5cdFx0XHRcdH1cclxuXHRcdFx0fVxyXG5cdFx0fVxyXG5cdH1cclxuXHJcblx0U2FtcGxlRGlyZWN0aXZlQ3RybC4kaW5qZWN0ID0gW107XHJcblx0LyoqXHJcblx0ICogc2FtcGxlRGlyZWN0aXZlIENPTlRST0xMRVJcclxuXHQgKi9cclxuXHRmdW5jdGlvbiBTYW1wbGVEaXJlY3RpdmVDdHJsKCkge1xyXG5cdFx0dmFyIHNkID0gdGhpcztcclxuXHJcblx0XHQvLyBjb250cm9sbGVyIGxvZ2ljIGdvZXMgaGVyZVxyXG5cdH1cclxuXHJcbn0oKSk7IiwiKGZ1bmN0aW9uKCkge1xyXG5cdCd1c2Ugc3RyaWN0JztcclxuXHJcblx0YW5ndWxhclxyXG5cdFx0Lm1vZHVsZSgncmVTdGFydCcpXHJcblx0XHQuY29udHJvbGxlcignU3ViQ3RybCcsIFN1YkN0cmwpO1xyXG5cclxuXHRTdWJDdHJsLiRpbmplY3QgPSBbJ1V0aWxzJywgJ1BhZ2UnLCAncmVzb2x2ZUxvY2FsRGF0YSddO1xyXG5cclxuXHRmdW5jdGlvbiBTdWJDdHJsKFV0aWxzLCBQYWdlLCByZXNvbHZlTG9jYWxEYXRhKSB7XHJcblx0XHQvLyBjb250cm9sbGVyQXMgVmlld01vZGVsXHJcblx0XHR2YXIgc3ViID0gdGhpcztcclxuXHJcblx0XHQvLyBiaW5kYWJsZSBtZW1iZXJzXHJcblx0XHRzdWIudGl0bGUgPSAnU3VicGFnZSc7XHJcblx0XHRzdWIuZ2xvYmFsID0gVXRpbHM7XHJcblx0XHRzdWIuanNvbiA9IHJlc29sdmVMb2NhbERhdGE7XHJcblxyXG5cdFx0X2luaXQoKTtcclxuXHJcblx0XHQvKipcclxuXHRcdCAqIElOSVQgZnVuY3Rpb24gZXhlY3V0ZXMgcHJvY2VkdXJhbCBjb2RlXHJcblx0XHQgKlxyXG5cdFx0ICogQHByaXZhdGVcclxuXHRcdCAqL1xyXG5cdFx0ZnVuY3Rpb24gX2luaXQoKSB7XHJcblx0XHRcdC8vIHNldCBwYWdlIDx0aXRsZT5cclxuXHRcdFx0UGFnZS5zZXRUaXRsZShzdWIudGl0bGUpO1xyXG5cdFx0fVxyXG5cdH1cclxufSgpKTsiXSwic291cmNlUm9vdCI6Ii9zb3VyY2UvIn0=