├── .gitignore
├── demo.gif
├── demo
├── app.js
├── code
│ ├── node-error-example.js
│ ├── node-example.js
│ ├── python-example.py
│ └── ruby-argv-example.rb
└── index.html
├── index.js
├── package-lock.json
├── package.json
├── readme.md
├── src
├── highligher.js
├── reveal-run-in-terminal.js
├── run-command.js
└── slide.js
└── static
└── plugin
├── hl-9.0.0.js
├── reveal-run-in-terminal-hljs-worker.js
├── reveal-run-in-terminal.css
└── reveal-run-in-terminal.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stanleynguyen/reveal-run-in-terminal/f1b5c77285182c06c9fda32d8fad0aa2c629c50f/demo.gif
--------------------------------------------------------------------------------
/demo/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const path = require('path');
3 | const revealRunInTerminal = require('../index.js');
4 |
5 | let app = express();
6 |
7 | app.use(revealRunInTerminal({
8 | publicPath: __dirname,
9 | commandRegex: /node|ruby/,
10 | log: true
11 | }));
12 |
13 | let revealJsPath = path.resolve(__dirname, '../node_modules/reveal.js');
14 | app.use(express.static(revealJsPath));
15 |
16 | app.listen(5000);
17 |
--------------------------------------------------------------------------------
/demo/code/node-error-example.js:
--------------------------------------------------------------------------------
1 | throw new Error('something has gone terribly wrong!');
2 |
--------------------------------------------------------------------------------
/demo/code/node-example.js:
--------------------------------------------------------------------------------
1 | console.log('console.log');
2 | console.error('console.error');
3 | setTimeout(() => process.stdout.write('stdout (250ms)'), 250);
4 | setTimeout(() => process.stderr.write('stderr (750ms)'), 750);
5 |
--------------------------------------------------------------------------------
/demo/code/python-example.py:
--------------------------------------------------------------------------------
1 | print 'this should never print'
2 |
--------------------------------------------------------------------------------
/demo/code/ruby-argv-example.rb:
--------------------------------------------------------------------------------
1 | ARGV.each_with_index do |arg, i|
2 | puts "argument ##{i + 1}) #{arg}"
3 | end
4 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | reveal-run-in-terminal
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
29 | reveal-run-in-terminal
30 |
31 |
32 |
33 | Node (With Time!)
34 |
35 |
36 |
37 | Node (With An Error!)
38 |
39 |
40 |
45 | Ruby (With Arguments!)
46 |
47 |
48 |
52 | Python (Isn't Whitelisted!)
53 |
54 |
55 |
56 | Outside Public Directory (Not Allowed!)
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const child_process = require('child_process');
2 | const express = require('express');
3 | const path = require('path');
4 |
5 | const ARGS_REGEX = /(?:[^\s']+|'[^']*')+/g;
6 | const HEADERS = {
7 | 'Content-Type': 'text/event-stream',
8 | 'Connection': 'keep-alive'
9 | };
10 |
11 | module.exports = (options) => {
12 | options = options || {};
13 |
14 | let app = express();
15 | let commandRegex = options.commandRegex || /\S*/;
16 | let publicPath = path.resolve(options.publicPath || '.');
17 |
18 | app.use(express.static(publicPath));
19 | app.use(express.static(path.join(__dirname, 'static')));
20 |
21 | app.get('/reveal-run-in-terminal', (req, res) => {
22 | let errors = [];
23 |
24 | if (!options.allowRemote && req.ip !== '::1' && req.ip !== '127.0.0.1') {
25 | errors.push(`command sent to reveal-run-in-terminal from non-localhost (IP was ${req.query.ip})`);
26 | }
27 |
28 | let bin = req.query.bin;
29 | if (!commandRegex.test(bin)) {
30 | errors.push(`command sent to reveal-run-in-terminal didn't match required format (was '${bin}')`);
31 | }
32 |
33 | let src = path.join(publicPath, req.query.src);
34 | if (!src.startsWith(publicPath)) {
35 | errors.push(`command sent to reveal-run-in-terminal specified a file outside of the allowed public path (was '${req.query.src}'')`);
36 | }
37 |
38 | res.writeHead(200, HEADERS);
39 |
40 | if (errors.length !== 0) {
41 | let payload = JSON.stringify({messages: errors});
42 | errors.forEach(err => console.error(`ERROR: ${err}`));
43 | res.end(`event: error\ndata: ${payload}\n\n`);
44 | return;
45 | }
46 |
47 | let args = ((req.query.args || '').match(ARGS_REGEX) || []);
48 | args = args.map(a => a.replace(/^'(.*)'$/, '$1'));
49 | args.unshift(src);
50 |
51 | let ps = child_process.spawn(bin, args);
52 |
53 | ['stdout', 'stderr'].forEach(source => {
54 | ps[source].on('data', (data) => {
55 | res.write(`data: ${JSON.stringify(data.toString())}\n\n`);
56 | });
57 | });
58 |
59 | ps.on('exit', exit => {
60 | if (options.log) {
61 | console.log(`${ps.pid}: ${ps.spawnargs.join(' ')} (${exit})`);
62 | }
63 | res.write(`event: done\ndata: ${exit}\n\n`);
64 | res.end();
65 | });
66 | });
67 |
68 | return app;
69 | };
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reveal-run-in-terminal",
3 | "version": "1.0.5",
4 | "description": "Show and execute code in your presentation",
5 | "main": "index.js",
6 | "dependencies": {
7 | "express": "^4.14.0"
8 | },
9 | "devDependencies": {
10 | "browserify": "^13.1.0",
11 | "np": "^6.5.0",
12 | "reveal.js": "^3.3.0"
13 | },
14 | "scripts": {
15 | "start": "node demo/app.js",
16 | "build": "browserify --debug src/reveal-run-in-terminal.js > static/plugin/reveal-run-in-terminal.js",
17 | "release": "np",
18 | "test": "echo 'no test yet'"
19 | },
20 | "author": {
21 | "name": "Daniel Luxemburg",
22 | "email": "daniel.luxemburg@gmail.com",
23 | "url": "http://dluxemburg.com"
24 | },
25 | "contributors": [
26 | {
27 | "name": "Stanley Nguyen",
28 | "email": "hung.ngn.the@gmail.com",
29 | "url": "https://stanleynguyen/me"
30 | }
31 | ],
32 | "license": "ISC",
33 | "repository": {
34 | "type": "git",
35 | "url": "git://github.com/stanleynguyen/reveal-run-in-terminal.git"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # reveal-run-in-terminal
2 |
3 | [](https://nodei.co/npm/reveal-run-in-terminal/)
4 |
5 | Add executable code examples to you [reveal.js](https://github.com/hakimel/reveal.js/#revealjs) presentation.
6 |
7 | Tabbing between Keynote and a terminal looks terrible and it is impossible to type with people watching anyway.
8 |
9 | Looks like this:
10 |
11 | 
12 |
13 | _**IMPORTANT NOTE**_: This, um, exposes a URL that can be used to execute user-provided commands on your machine. There are a few measures taken to restrict this to its intended use, but it's almost certainly still exploitable somehow. Be careful!
14 |
15 | ## Usage
16 |
17 | ### Run the Server
18 |
19 | The plugin requires that your presentation be served by [Express](https://expressjs.com/). A minimal version looks like this:
20 |
21 | ```javascript
22 | const express = require('express');
23 | const revealRunInTerminal = require('reveal-run-in-terminal');
24 |
25 | let app = express();
26 |
27 | app.use(revealRunInTerminal());
28 | app.use(express.static('node_modules/reveal.js'));
29 |
30 | app.listen(5000);
31 | ```
32 |
33 | Options for `revealRunInTerminal`:
34 |
35 | - **`publicPath`** (_default_: `'.'`): Directory to serve files and load executed code from.
36 | - **`commandRegex`** (_default_: `/\S*/`): Regex that executable must match. This is a safety measure to make sure you don't run anything you didn't intend to.
37 | - **`allowRemote`** (_default_: `false`): Allow command-execution requests from non-localhost sources. Probably don't ever do this.
38 | - **`log`** (_default_: `false`): Whether to log executed commands (along with PID and exit code) to the server console.
39 |
40 | The server handles exposing the plugin's client-side JS and CSS dependencies. It's up to you make sure reveal.js files are exposed (the above is a good approach). You can keep your own source files (including reveal.js ones if you're vendoring them) in the public path reveal-run-in-terminal uses, but you do not have to.
41 |
42 | ### Include the CSS
43 |
44 | ```html
45 |
46 | ```
47 |
48 | ### Include the JS
49 |
50 | You should use reveal.js's plugin system, like this:
51 |
52 | ```javascript
53 | Reveal.initialize({
54 | // some options
55 | dependencies: [
56 | {
57 | src: 'plugin/reveal-run-in-terminal.js',
58 | callback: function() {
59 | RunInTerminal.init();
60 | },
61 | async: true,
62 | },
63 | // more plugins
64 | ],
65 | });
66 | ```
67 |
68 | Nothing will happen until `RunInTerminal#init` is called. You should also include the highlight plugin if you want code to be syntax highlighted.
69 |
70 | `RunInTerminal#init` options:
71 |
72 | - **`defaultBin`**: Default value for the `data-run-in-terminal-bin` attribute of individual slides (the executable used to run each code example).
73 |
74 | ### Add Some Slides
75 |
76 | `
77 |
78 | ```html
79 |
83 | Here Is A Great Example
84 |
85 | ```
86 |
87 | The `section` elements for reveal-run-in-terminal slides use these attributes:
88 |
89 | - **`data-run-in-terminal`** (_required_): Path to the code to display and run.
90 | - **`data-run-in-terminal-bin`** (_required unless `defaultBin` was passed to `TerminalSlides#init`_): The executable used to run the code example.
91 | - **`data-run-in-terminal-args`**: Additional space-separated arguments to pass to the command to be run. Use single quotes for values including spaces.
92 |
93 | The slide above will initially display code from `{publicPath}/code/some-great-example.js` and an empty simulated terminal prompt. Two [fragments](https://github.com/hakimel/reveal.js/#fragments) are added by the plugin:
94 |
95 | - The first displays the command that will be run (`node code/some-great-example.js` in this case).
96 | - The second adds the `stdout` and `stderr` from that command as executed by the server.
97 |
98 | So, the process goes:
99 |
100 | - Advance to slide (empty prompt)
101 | - Advance to command fragment (prompt with command)
102 | - Advance to command execution (output incrementally added after command)
103 | - Advance to next silde
104 |
105 | ## Developing
106 |
107 | ### Demo Server
108 |
109 | `npm start` runs it on port 5000.
110 |
111 | ### Client Code
112 |
113 | `npm run build` browserifies it.
114 |
115 | ### Notes
116 |
117 | - `/reveal-run-in-terminal` is implemented as a `GET` request in order to use the `EventSource` API on the client to stream process output. Yes, socket.io something something but this avoids additional dependencies and is pretty simple.
118 | - It would be cool to do this for Node specifically using the `vm` module instead of spawning a process but I couldn't figure out how to capture `stderr`/`stdout`/process termination in a way that reliably mimicked what running a script would do.
119 | - Maybe it would be better to use `#!` syntax at the top of files to specify how to run them instead of requiring that option per-slide? I didn't want to require the code files to be executable or have to manipulate them before putting them on the page.
120 |
121 | ### Goals
122 |
123 | - Record command output so that live presentations can be given with static assets.
124 | - Colorize `stdout` vs `stderr`.
125 | - Display process exit code somehow.
126 | - Better integration with other plugins (is it possible to use this and server notes? multiplexing?).
127 | - Source highlighting.
128 | - Source diffing.
129 |
--------------------------------------------------------------------------------
/src/highligher.js:
--------------------------------------------------------------------------------
1 | module.exports = class {
2 | static highlight(code) {
3 | this._instance = this._instance || new this();
4 | return this._instance.highlight(code);
5 | }
6 |
7 | constructor() {
8 | this.worker = new Worker('/plugin/reveal-run-in-terminal-hljs-worker.js');
9 | this.pending = {};
10 | this.worker.onmessage = (event) => {
11 | this.pending[event.data.callbackId].resolve(event.data.code.value);
12 | delete this.pending[event.data.callbackId];
13 | };
14 | }
15 |
16 | highlight(code) {
17 | let callbackId = (Date.now() + Math.random()).toString(16);
18 | return new Promise((resolve, reject) => {
19 | this.pending[callbackId] = {resolve, reject};
20 | this.worker.postMessage({callbackId, code});
21 | });
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/reveal-run-in-terminal.js:
--------------------------------------------------------------------------------
1 | const Slide = require('./slide');
2 |
3 | window.RunInTerminal = class {
4 | static init(options) {
5 | let runInTerminal = new this(options);
6 | runInTerminal.load();
7 |
8 | Reveal.addEventListener('fragmentshown', function(event) {
9 | if (!event.fragment.dataset.terminalFragment) return;
10 | let slide = runInTerminal.forSection(event.fragment.parentElement);
11 |
12 | if (event.fragment.dataset.terminalFragment === 'showCommand') {
13 | slide.renderCommand();
14 | slide.scrollToBottom();
15 | } else if (event.fragment.dataset.terminalFragment === 'execute') {
16 | slide.executeCommand();
17 | }
18 | });
19 |
20 | Reveal.addEventListener('fragmenthidden', function(event) {
21 | if (!event.fragment.dataset.terminalFragment) return;
22 | let slide = runInTerminal.forSection(event.fragment.parentElement);
23 |
24 | if (event.fragment.dataset.terminalFragment === 'showCommand') {
25 | slide.renderPrompt();
26 | } else if (event.fragment.dataset.terminalFragment === 'execute') {
27 | slide.renderCommand();
28 | }
29 | });
30 |
31 | Reveal.addEventListener('slidechanged', function(event) {
32 | let slide = runInTerminal.forSection(event.currentSlide);
33 | if (slide && slide.clearOnShow) slide.renderPrompt();
34 | runInTerminal.reload({except: [slide]});
35 | });
36 |
37 | return runInTerminal;
38 | }
39 |
40 | constructor(options) { this.options = options || {}; }
41 |
42 | load() {
43 | let sections = document.querySelectorAll('section[data-run-in-terminal]');
44 | this.slides = [].map.call(sections, section => {
45 | return new Slide(section, this.options);
46 | });
47 | }
48 |
49 | reload(options = {except: []}) {
50 | this.slides
51 | .filter(s => options.except.indexOf(s) !== -1)
52 | .forEach(s => s.load());
53 | }
54 |
55 | forSection(section) {
56 | return this.slides.filter((s) => s.section === section)[0];
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/src/run-command.js:
--------------------------------------------------------------------------------
1 | const querystring = require('querystring');
2 |
3 | module.exports = (params, fn) => {
4 | let qs = querystring.stringify(params);
5 | return new Promise((resolve, reject) => {
6 | let source = new EventSource(`/reveal-run-in-terminal?${qs}`);
7 | source.addEventListener('message', e => fn(JSON.parse(e.data)));
8 | source.addEventListener('done', () => resolve(source.close()));
9 | source.addEventListener('error', e => {
10 | if (e.data) {
11 | let messages = JSON.parse(e.data).messages;
12 | messages.forEach(err => console.error(err));
13 | reject(new Error(`${messages.join(', ')}`));
14 | } else {
15 | reject(e);
16 | }
17 |
18 | source.close();
19 | });
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/src/slide.js:
--------------------------------------------------------------------------------
1 | const runCommand = require('./run-command');
2 | const Highligher = require('./highligher');
3 |
4 | module.exports = class {
5 | constructor(section, options) {
6 | this.options = options;
7 | this.section = section;
8 |
9 | this.hide();
10 | this.addElement('container');
11 |
12 | this.addElement('title', {tagName: 'span', parent: this.container});
13 | this.title.innerText = this.src;
14 |
15 | ['code', 'term'].forEach(name => this.addElement(name, {
16 | tagName: 'pre',
17 | classes: ['hljs'],
18 | parent: this.container
19 | }));
20 |
21 | ['showCommand', 'execute'].forEach(name => this.addElement(name, {
22 | classes: ['fragment'],
23 | dataset: {terminalFragment: name}
24 | }));
25 |
26 | this.load();
27 | }
28 |
29 | load() {
30 | this.hide();
31 | return fetch(this.src)
32 | .then(response => response.text())
33 | .then(code => Highligher.highlight(code))
34 | .then(html => html.replace(/\n/g, '\n'))
35 | .then(html => this.code.innerHTML = html)
36 | .then(() => this.container.scrollTop = 0)
37 | .then(() => this.show());
38 | }
39 |
40 | addElement(name, options) {
41 | options = options || {};
42 |
43 | this[name] = document.createElement(options.tagName || 'div');
44 | (options.classes || []).concat([name]).forEach(clazz => {
45 | this[name].classList.add(clazz)
46 | });
47 | Object.assign(this[name].dataset, options.dataset || {});
48 |
49 | (options.parent || this.section).appendChild(this[name]);
50 | return this[name];
51 | }
52 |
53 | scrollToBottom() {
54 | let interval = setInterval(() => {
55 | let top = this.container.scrollTop;
56 | this.container.scrollTop += 2;
57 | if (top === this.container.scrollTop) {
58 | clearInterval(interval);
59 | }
60 | }, 1);
61 | }
62 |
63 | hide() { this.section.style.display = 'none'; }
64 |
65 | show() { this.section.style.display = 'block'; }
66 |
67 | renderPrompt() { this.term.innerText = `> █`; }
68 |
69 | renderCommand() { this.term.innerText = `> ${this.command}█`; }
70 |
71 | executeCommand() {
72 | this.term.innerText = `> ${this.command}\n`;
73 | runCommand(this.params, output => {
74 | this.term.innerText = `${this.term.innerText.trim()}\n${output}`;
75 | this.scrollToBottom();
76 | }).then(() => {
77 | this.term.innerText = `${this.term.innerText.trim().replace(/█/g, '')}\n> █`;
78 | this.scrollToBottom();
79 | }).catch(err => this.term.innerText = err.message);
80 | }
81 |
82 | property(prop) { return this.section.dataset[prop]; }
83 |
84 | get clearOnShow() {
85 | return !this.showCommand.classList.contains('visible');
86 | }
87 |
88 | get command() {
89 | let command = `${this.bin} ${this.src}`
90 | if (this.args) command = `${command} ${this.args}`;
91 | return command;
92 | }
93 |
94 | get params() {
95 | let params = {bin: this.bin, src: this.src};
96 | if (this.args) params.args = this.args;
97 | return params;
98 | }
99 |
100 | get bin() {
101 | return this.property('runInTerminalBin') || this.options.defaultBin;
102 | }
103 |
104 | get src() { return this.property('runInTerminal'); }
105 |
106 | get args() { return this.property('runInTerminalArgs'); }
107 | };
108 |
--------------------------------------------------------------------------------
/static/plugin/reveal-run-in-terminal-hljs-worker.js:
--------------------------------------------------------------------------------
1 | self.window = {};
2 | importScripts('/plugin/hl-9.0.0.js');
3 |
4 | onmessage = event => {
5 | postMessage({
6 | code: self.hljs.highlightAuto(event.data.code),
7 | callbackId: event.data.callbackId,
8 | });
9 | };
10 |
--------------------------------------------------------------------------------
/static/plugin/reveal-run-in-terminal.css:
--------------------------------------------------------------------------------
1 | section[data-run-in-terminal] {
2 | height: 100%;
3 | top: 0;
4 | }
5 |
6 | section[data-run-in-terminal] .container {
7 | height: 100%;
8 | overflow-y: scroll;
9 | }
10 |
11 | section[data-run-in-terminal] .title {
12 | float: left;
13 | font-size: 0.5em;
14 | color: gray;
15 | font-family: monospace;
16 | text-indent: 3em;
17 | }
18 |
19 | section[data-run-in-terminal] pre {
20 | width: 100%;
21 | white-space: pre-wrap;
22 | background: none;
23 | box-shadow: none;
24 | margin: 0px;
25 | }
26 |
27 | section[data-run-in-terminal] pre.code {
28 | padding: 0.5em 0 1em;
29 | counter-reset: line;
30 | padding-left: 3em;
31 | width: 90%;
32 | }
33 |
34 | section[data-run-in-terminal] pre.code span.line:before {
35 | counter-increment: line;
36 | content: counter(line);
37 | display: inline-block;
38 | border-right: 1px solid gray;
39 | padding: 0 .25em 0 0;
40 | text-align: right;
41 | min-width: 1.5em;
42 | color: gray;
43 | position: absolute;
44 | left: 0;
45 | }
46 |
47 | section[data-run-in-terminal] pre.term {
48 | padding-top: 1em;
49 | border-top: 1px solid gray;
50 | }
51 |
52 | section.no-run[data-run-in-terminal] pre.term { display: none; }
53 |
--------------------------------------------------------------------------------
/static/plugin/reveal-run-in-terminal.js:
--------------------------------------------------------------------------------
1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && len > maxKeys) {
52 | len = maxKeys;
53 | }
54 |
55 | for (var i = 0; i < len; ++i) {
56 | var x = qs[i].replace(regexp, '%20'),
57 | idx = x.indexOf(eq),
58 | kstr, vstr, k, v;
59 |
60 | if (idx >= 0) {
61 | kstr = x.substr(0, idx);
62 | vstr = x.substr(idx + 1);
63 | } else {
64 | kstr = x;
65 | vstr = '';
66 | }
67 |
68 | k = decodeURIComponent(kstr);
69 | v = decodeURIComponent(vstr);
70 |
71 | if (!hasOwnProperty(obj, k)) {
72 | obj[k] = v;
73 | } else if (isArray(obj[k])) {
74 | obj[k].push(v);
75 | } else {
76 | obj[k] = [obj[k], v];
77 | }
78 | }
79 |
80 | return obj;
81 | };
82 |
83 | var isArray = Array.isArray || function (xs) {
84 | return Object.prototype.toString.call(xs) === '[object Array]';
85 | };
86 |
87 | },{}],2:[function(require,module,exports){
88 | // Copyright Joyent, Inc. and other Node contributors.
89 | //
90 | // Permission is hereby granted, free of charge, to any person obtaining a
91 | // copy of this software and associated documentation files (the
92 | // "Software"), to deal in the Software without restriction, including
93 | // without limitation the rights to use, copy, modify, merge, publish,
94 | // distribute, sublicense, and/or sell copies of the Software, and to permit
95 | // persons to whom the Software is furnished to do so, subject to the
96 | // following conditions:
97 | //
98 | // The above copyright notice and this permission notice shall be included
99 | // in all copies or substantial portions of the Software.
100 | //
101 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
102 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
103 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
104 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
105 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
106 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
107 | // USE OR OTHER DEALINGS IN THE SOFTWARE.
108 |
109 | 'use strict';
110 |
111 | var stringifyPrimitive = function(v) {
112 | switch (typeof v) {
113 | case 'string':
114 | return v;
115 |
116 | case 'boolean':
117 | return v ? 'true' : 'false';
118 |
119 | case 'number':
120 | return isFinite(v) ? v : '';
121 |
122 | default:
123 | return '';
124 | }
125 | };
126 |
127 | module.exports = function(obj, sep, eq, name) {
128 | sep = sep || '&';
129 | eq = eq || '=';
130 | if (obj === null) {
131 | obj = undefined;
132 | }
133 |
134 | if (typeof obj === 'object') {
135 | return map(objectKeys(obj), function(k) {
136 | var ks = encodeURIComponent(stringifyPrimitive(k)) + eq;
137 | if (isArray(obj[k])) {
138 | return map(obj[k], function(v) {
139 | return ks + encodeURIComponent(stringifyPrimitive(v));
140 | }).join(sep);
141 | } else {
142 | return ks + encodeURIComponent(stringifyPrimitive(obj[k]));
143 | }
144 | }).join(sep);
145 |
146 | }
147 |
148 | if (!name) return '';
149 | return encodeURIComponent(stringifyPrimitive(name)) + eq +
150 | encodeURIComponent(stringifyPrimitive(obj));
151 | };
152 |
153 | var isArray = Array.isArray || function (xs) {
154 | return Object.prototype.toString.call(xs) === '[object Array]';
155 | };
156 |
157 | function map (xs, f) {
158 | if (xs.map) return xs.map(f);
159 | var res = [];
160 | for (var i = 0; i < xs.length; i++) {
161 | res.push(f(xs[i], i));
162 | }
163 | return res;
164 | }
165 |
166 | var objectKeys = Object.keys || function (obj) {
167 | var res = [];
168 | for (var key in obj) {
169 | if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key);
170 | }
171 | return res;
172 | };
173 |
174 | },{}],3:[function(require,module,exports){
175 | 'use strict';
176 |
177 | exports.decode = exports.parse = require('./decode');
178 | exports.encode = exports.stringify = require('./encode');
179 |
180 | },{"./decode":1,"./encode":2}],4:[function(require,module,exports){
181 | module.exports = class {
182 | static highlight(code) {
183 | this._instance = this._instance || new this();
184 | return this._instance.highlight(code);
185 | }
186 |
187 | constructor() {
188 | this.worker = new Worker('/plugin/reveal-run-in-terminal-hljs-worker.js');
189 | this.pending = {};
190 | this.worker.onmessage = (event) => {
191 | this.pending[event.data.callbackId].resolve(event.data.code.value);
192 | delete this.pending[event.data.callbackId];
193 | };
194 | }
195 |
196 | highlight(code) {
197 | let callbackId = (Date.now() + Math.random()).toString(16);
198 | return new Promise((resolve, reject) => {
199 | this.pending[callbackId] = {resolve, reject};
200 | this.worker.postMessage({callbackId, code});
201 | });
202 | }
203 | }
204 |
205 | },{}],5:[function(require,module,exports){
206 | const Slide = require('./slide');
207 |
208 | window.RunInTerminal = class {
209 | static init(options) {
210 | let runInTerminal = new this(options);
211 | runInTerminal.load();
212 |
213 | Reveal.addEventListener('fragmentshown', function(event) {
214 | if (!event.fragment.dataset.terminalFragment) return;
215 | let slide = runInTerminal.forSection(event.fragment.parentElement);
216 |
217 | if (event.fragment.dataset.terminalFragment === 'showCommand') {
218 | slide.renderCommand();
219 | slide.scrollToBottom();
220 | } else if (event.fragment.dataset.terminalFragment === 'execute') {
221 | slide.executeCommand();
222 | }
223 | });
224 |
225 | Reveal.addEventListener('fragmenthidden', function(event) {
226 | if (!event.fragment.dataset.terminalFragment) return;
227 | let slide = runInTerminal.forSection(event.fragment.parentElement);
228 |
229 | if (event.fragment.dataset.terminalFragment === 'showCommand') {
230 | slide.renderPrompt();
231 | } else if (event.fragment.dataset.terminalFragment === 'execute') {
232 | slide.renderCommand();
233 | }
234 | });
235 |
236 | Reveal.addEventListener('slidechanged', function(event) {
237 | let slide = runInTerminal.forSection(event.currentSlide);
238 | if (slide && slide.clearOnShow) slide.renderPrompt();
239 | runInTerminal.reload({except: [slide]});
240 | });
241 |
242 | return runInTerminal;
243 | }
244 |
245 | constructor(options) { this.options = options || {}; }
246 |
247 | load() {
248 | let sections = document.querySelectorAll('section[data-run-in-terminal]');
249 | this.slides = [].map.call(sections, section => {
250 | return new Slide(section, this.options);
251 | });
252 | }
253 |
254 | reload(options = {except: []}) {
255 | this.slides
256 | .filter(s => options.except.indexOf(s) !== -1)
257 | .forEach(s => s.load());
258 | }
259 |
260 | forSection(section) {
261 | return this.slides.filter((s) => s.section === section)[0];
262 | }
263 | };
264 |
265 | },{"./slide":7}],6:[function(require,module,exports){
266 | const querystring = require('querystring');
267 |
268 | module.exports = (params, fn) => {
269 | let qs = querystring.stringify(params);
270 | return new Promise((resolve, reject) => {
271 | let source = new EventSource(`/reveal-run-in-terminal?${qs}`);
272 | source.addEventListener('message', e => fn(JSON.parse(e.data)));
273 | source.addEventListener('done', () => resolve(source.close()));
274 | source.addEventListener('error', e => {
275 | if (e.data) {
276 | let messages = JSON.parse(e.data).messages;
277 | messages.forEach(err => console.error(err));
278 | reject(new Error(`${messages.join(', ')}`));
279 | } else {
280 | reject(e);
281 | }
282 |
283 | source.close();
284 | });
285 | });
286 | };
287 |
288 | },{"querystring":3}],7:[function(require,module,exports){
289 | const runCommand = require('./run-command');
290 | const Highligher = require('./highligher');
291 |
292 | module.exports = class {
293 | constructor(section, options) {
294 | this.options = options;
295 | this.section = section;
296 |
297 | this.hide();
298 | this.addElement('container');
299 |
300 | this.addElement('title', {tagName: 'span', parent: this.container});
301 | this.title.innerText = this.src;
302 |
303 | ['code', 'term'].forEach(name => this.addElement(name, {
304 | tagName: 'pre',
305 | classes: ['hljs'],
306 | parent: this.container
307 | }));
308 |
309 | ['showCommand', 'execute'].forEach(name => this.addElement(name, {
310 | classes: ['fragment'],
311 | dataset: {terminalFragment: name}
312 | }));
313 |
314 | this.load();
315 | }
316 |
317 | load() {
318 | this.hide();
319 | return fetch(this.src)
320 | .then(response => response.text())
321 | .then(code => Highligher.highlight(code))
322 | .then(html => html.replace(/\n/g, '\n'))
323 | .then(html => this.code.innerHTML = html)
324 | .then(() => this.container.scrollTop = 0)
325 | .then(() => this.show());
326 | }
327 |
328 | addElement(name, options) {
329 | options = options || {};
330 |
331 | this[name] = document.createElement(options.tagName || 'div');
332 | (options.classes || []).concat([name]).forEach(clazz => {
333 | this[name].classList.add(clazz)
334 | });
335 | Object.assign(this[name].dataset, options.dataset || {});
336 |
337 | (options.parent || this.section).appendChild(this[name]);
338 | return this[name];
339 | }
340 |
341 | scrollToBottom() {
342 | let interval = setInterval(() => {
343 | let top = this.container.scrollTop;
344 | this.container.scrollTop += 2;
345 | if (top === this.container.scrollTop) {
346 | clearInterval(interval);
347 | }
348 | }, 1);
349 | }
350 |
351 | hide() { this.section.style.display = 'none'; }
352 |
353 | show() { this.section.style.display = 'block'; }
354 |
355 | renderPrompt() { this.term.innerText = `> █`; }
356 |
357 | renderCommand() { this.term.innerText = `> ${this.command}█`; }
358 |
359 | executeCommand() {
360 | this.term.innerText = `> ${this.command}\n`;
361 | runCommand(this.params, output => {
362 | this.term.innerText = `${this.term.innerText.trim()}\n${output}`;
363 | this.scrollToBottom();
364 | }).then(() => {
365 | this.term.innerText = `${this.term.innerText.trim().replace(/█/g, '')}\n> █`;
366 | this.scrollToBottom();
367 | }).catch(err => this.term.innerText = err.message);
368 | }
369 |
370 | property(prop) { return this.section.dataset[prop]; }
371 |
372 | get clearOnShow() {
373 | return !this.showCommand.classList.contains('visible');
374 | }
375 |
376 | get command() {
377 | let command = `${this.bin} ${this.src}`
378 | if (this.args) command = `${command} ${this.args}`;
379 | return command;
380 | }
381 |
382 | get params() {
383 | let params = {bin: this.bin, src: this.src};
384 | if (this.args) params.args = this.args;
385 | return params;
386 | }
387 |
388 | get bin() {
389 | return this.property('runInTerminalBin') || this.options.defaultBin;
390 | }
391 |
392 | get src() { return this.property('runInTerminal'); }
393 |
394 | get args() { return this.property('runInTerminalArgs'); }
395 | };
396 |
397 | },{"./highligher":4,"./run-command":6}]},{},[5])
398 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJub2RlX21vZHVsZXMvcXVlcnlzdHJpbmctZXMzL2RlY29kZS5qcyIsIm5vZGVfbW9kdWxlcy9xdWVyeXN0cmluZy1lczMvZW5jb2RlLmpzIiwibm9kZV9tb2R1bGVzL3F1ZXJ5c3RyaW5nLWVzMy9pbmRleC5qcyIsInNyYy9oaWdobGlnaGVyLmpzIiwic3JjL3JldmVhbC1ydW4taW4tdGVybWluYWwuanMiLCJzcmMvcnVuLWNvbW1hbmQuanMiLCJzcmMvc2xpZGUuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNwRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNyRkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNKQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDdkJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMURBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3JCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiLy8gQ29weXJpZ2h0IEpveWVudCwgSW5jLiBhbmQgb3RoZXIgTm9kZSBjb250cmlidXRvcnMuXG4vL1xuLy8gUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGFcbi8vIGNvcHkgb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGVcbi8vIFwiU29mdHdhcmVcIiksIHRvIGRlYWwgaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZ1xuLy8gd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMgdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLFxuLy8gZGlzdHJpYnV0ZSwgc3VibGljZW5zZSwgYW5kL29yIHNlbGwgY29waWVzIG9mIHRoZSBTb2Z0d2FyZSwgYW5kIHRvIHBlcm1pdFxuLy8gcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcyBmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlXG4vLyBmb2xsb3dpbmcgY29uZGl0aW9uczpcbi8vXG4vLyBUaGUgYWJvdmUgY29weXJpZ2h0IG5vdGljZSBhbmQgdGhpcyBwZXJtaXNzaW9uIG5vdGljZSBzaGFsbCBiZSBpbmNsdWRlZFxuLy8gaW4gYWxsIGNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuXG4vL1xuLy8gVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEIFwiQVMgSVNcIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTU1xuLy8gT1IgSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRlxuLy8gTUVSQ0hBTlRBQklMSVRZLCBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBTkQgTk9OSU5GUklOR0VNRU5ULiBJTlxuLy8gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sXG4vLyBEQU1BR0VTIE9SIE9USEVSIExJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1Jcbi8vIE9USEVSV0lTRSwgQVJJU0lORyBGUk9NLCBPVVQgT0YgT1IgSU4gQ09OTkVDVElPTiBXSVRIIFRIRSBTT0ZUV0FSRSBPUiBUSEVcbi8vIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUgU09GVFdBUkUuXG5cbid1c2Ugc3RyaWN0JztcblxuLy8gSWYgb2JqLmhhc093blByb3BlcnR5IGhhcyBiZWVuIG92ZXJyaWRkZW4sIHRoZW4gY2FsbGluZ1xuLy8gb2JqLmhhc093blByb3BlcnR5KHByb3ApIHdpbGwgYnJlYWsuXG4vLyBTZWU6IGh0dHBzOi8vZ2l0aHViLmNvbS9qb3llbnQvbm9kZS9pc3N1ZXMvMTcwN1xuZnVuY3Rpb24gaGFzT3duUHJvcGVydHkob2JqLCBwcm9wKSB7XG4gIHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqLCBwcm9wKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbihxcywgc2VwLCBlcSwgb3B0aW9ucykge1xuICBzZXAgPSBzZXAgfHwgJyYnO1xuICBlcSA9IGVxIHx8ICc9JztcbiAgdmFyIG9iaiA9IHt9O1xuXG4gIGlmICh0eXBlb2YgcXMgIT09ICdzdHJpbmcnIHx8IHFzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiBvYmo7XG4gIH1cblxuICB2YXIgcmVnZXhwID0gL1xcKy9nO1xuICBxcyA9IHFzLnNwbGl0KHNlcCk7XG5cbiAgdmFyIG1heEtleXMgPSAxMDAwO1xuICBpZiAob3B0aW9ucyAmJiB0eXBlb2Ygb3B0aW9ucy5tYXhLZXlzID09PSAnbnVtYmVyJykge1xuICAgIG1heEtleXMgPSBvcHRpb25zLm1heEtleXM7XG4gIH1cblxuICB2YXIgbGVuID0gcXMubGVuZ3RoO1xuICAvLyBtYXhLZXlzIDw9IDAgbWVhbnMgdGhhdCB3ZSBzaG91bGQgbm90IGxpbWl0IGtleXMgY291bnRcbiAgaWYgKG1heEtleXMgPiAwICYmIGxlbiA+IG1heEtleXMpIHtcbiAgICBsZW4gPSBtYXhLZXlzO1xuICB9XG5cbiAgZm9yICh2YXIgaSA9IDA7IGkgPCBsZW47ICsraSkge1xuICAgIHZhciB4ID0gcXNbaV0ucmVwbGFjZShyZWdleHAsICclMjAnKSxcbiAgICAgICAgaWR4ID0geC5pbmRleE9mKGVxKSxcbiAgICAgICAga3N0ciwgdnN0ciwgaywgdjtcblxuICAgIGlmIChpZHggPj0gMCkge1xuICAgICAga3N0ciA9IHguc3Vic3RyKDAsIGlkeCk7XG4gICAgICB2c3RyID0geC5zdWJzdHIoaWR4ICsgMSk7XG4gICAgfSBlbHNlIHtcbiAgICAgIGtzdHIgPSB4O1xuICAgICAgdnN0ciA9ICcnO1xuICAgIH1cblxuICAgIGsgPSBkZWNvZGVVUklDb21wb25lbnQoa3N0cik7XG4gICAgdiA9IGRlY29kZVVSSUNvbXBvbmVudCh2c3RyKTtcblxuICAgIGlmICghaGFzT3duUHJvcGVydHkob2JqLCBrKSkge1xuICAgICAgb2JqW2tdID0gdjtcbiAgICB9IGVsc2UgaWYgKGlzQXJyYXkob2JqW2tdKSkge1xuICAgICAgb2JqW2tdLnB1c2godik7XG4gICAgfSBlbHNlIHtcbiAgICAgIG9ialtrXSA9IFtvYmpba10sIHZdO1xuICAgIH1cbiAgfVxuXG4gIHJldHVybiBvYmo7XG59O1xuXG52YXIgaXNBcnJheSA9IEFycmF5LmlzQXJyYXkgfHwgZnVuY3Rpb24gKHhzKSB7XG4gIHJldHVybiBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nLmNhbGwoeHMpID09PSAnW29iamVjdCBBcnJheV0nO1xufTtcbiIsIi8vIENvcHlyaWdodCBKb3llbnQsIEluYy4gYW5kIG90aGVyIE5vZGUgY29udHJpYnV0b3JzLlxuLy9cbi8vIFBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhXG4vLyBjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlXG4vLyBcIlNvZnR3YXJlXCIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmdcbi8vIHdpdGhvdXQgbGltaXRhdGlvbiB0aGUgcmlnaHRzIHRvIHVzZSwgY29weSwgbW9kaWZ5LCBtZXJnZSwgcHVibGlzaCxcbi8vIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsIGNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXRcbi8vIHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZVxuLy8gZm9sbG93aW5nIGNvbmRpdGlvbnM6XG4vL1xuLy8gVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWRcbi8vIGluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLlxuLy9cbi8vIFRIRSBTT0ZUV0FSRSBJUyBQUk9WSURFRCBcIkFTIElTXCIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1Ncbi8vIE9SIElNUExJRUQsIElOQ0xVRElORyBCVVQgTk9UIExJTUlURUQgVE8gVEhFIFdBUlJBTlRJRVMgT0Zcbi8vIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU5cbi8vIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLFxuLy8gREFNQUdFUyBPUiBPVEhFUiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQU4gQUNUSU9OIE9GIENPTlRSQUNULCBUT1JUIE9SXG4vLyBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFXG4vLyBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLlxuXG4ndXNlIHN0cmljdCc7XG5cbnZhciBzdHJpbmdpZnlQcmltaXRpdmUgPSBmdW5jdGlvbih2KSB7XG4gIHN3aXRjaCAodHlwZW9mIHYpIHtcbiAgICBjYXNlICdzdHJpbmcnOlxuICAgICAgcmV0dXJuIHY7XG5cbiAgICBjYXNlICdib29sZWFuJzpcbiAgICAgIHJldHVybiB2ID8gJ3RydWUnIDogJ2ZhbHNlJztcblxuICAgIGNhc2UgJ251bWJlcic6XG4gICAgICByZXR1cm4gaXNGaW5pdGUodikgPyB2IDogJyc7XG5cbiAgICBkZWZhdWx0OlxuICAgICAgcmV0dXJuICcnO1xuICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uKG9iaiwgc2VwLCBlcSwgbmFtZSkge1xuICBzZXAgPSBzZXAgfHwgJyYnO1xuICBlcSA9IGVxIHx8ICc9JztcbiAgaWYgKG9iaiA9PT0gbnVsbCkge1xuICAgIG9iaiA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGlmICh0eXBlb2Ygb2JqID09PSAnb2JqZWN0Jykge1xuICAgIHJldHVybiBtYXAob2JqZWN0S2V5cyhvYmopLCBmdW5jdGlvbihrKSB7XG4gICAgICB2YXIga3MgPSBlbmNvZGVVUklDb21wb25lbnQoc3RyaW5naWZ5UHJpbWl0aXZlKGspKSArIGVxO1xuICAgICAgaWYgKGlzQXJyYXkob2JqW2tdKSkge1xuICAgICAgICByZXR1cm4gbWFwKG9ialtrXSwgZnVuY3Rpb24odikge1xuICAgICAgICAgIHJldHVybiBrcyArIGVuY29kZVVSSUNvbXBvbmVudChzdHJpbmdpZnlQcmltaXRpdmUodikpO1xuICAgICAgICB9KS5qb2luKHNlcCk7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4ga3MgKyBlbmNvZGVVUklDb21wb25lbnQoc3RyaW5naWZ5UHJpbWl0aXZlKG9ialtrXSkpO1xuICAgICAgfVxuICAgIH0pLmpvaW4oc2VwKTtcblxuICB9XG5cbiAgaWYgKCFuYW1lKSByZXR1cm4gJyc7XG4gIHJldHVybiBlbmNvZGVVUklDb21wb25lbnQoc3RyaW5naWZ5UHJpbWl0aXZlKG5hbWUpKSArIGVxICtcbiAgICAgICAgIGVuY29kZVVSSUNvbXBvbmVudChzdHJpbmdpZnlQcmltaXRpdmUob2JqKSk7XG59O1xuXG52YXIgaXNBcnJheSA9IEFycmF5LmlzQXJyYXkgfHwgZnVuY3Rpb24gKHhzKSB7XG4gIHJldHVybiBPYmplY3QucHJvdG90eXBlLnRvU3RyaW5nLmNhbGwoeHMpID09PSAnW29iamVjdCBBcnJheV0nO1xufTtcblxuZnVuY3Rpb24gbWFwICh4cywgZikge1xuICBpZiAoeHMubWFwKSByZXR1cm4geHMubWFwKGYpO1xuICB2YXIgcmVzID0gW107XG4gIGZvciAodmFyIGkgPSAwOyBpIDwgeHMubGVuZ3RoOyBpKyspIHtcbiAgICByZXMucHVzaChmKHhzW2ldLCBpKSk7XG4gIH1cbiAgcmV0dXJuIHJlcztcbn1cblxudmFyIG9iamVjdEtleXMgPSBPYmplY3Qua2V5cyB8fCBmdW5jdGlvbiAob2JqKSB7XG4gIHZhciByZXMgPSBbXTtcbiAgZm9yICh2YXIga2V5IGluIG9iaikge1xuICAgIGlmIChPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwob2JqLCBrZXkpKSByZXMucHVzaChrZXkpO1xuICB9XG4gIHJldHVybiByZXM7XG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5leHBvcnRzLmRlY29kZSA9IGV4cG9ydHMucGFyc2UgPSByZXF1aXJlKCcuL2RlY29kZScpO1xuZXhwb3J0cy5lbmNvZGUgPSBleHBvcnRzLnN0cmluZ2lmeSA9IHJlcXVpcmUoJy4vZW5jb2RlJyk7XG4iLCJtb2R1bGUuZXhwb3J0cyA9IGNsYXNzIHtcbiAgc3RhdGljIGhpZ2hsaWdodChjb2RlKSB7XG4gICAgdGhpcy5faW5zdGFuY2UgPSB0aGlzLl9pbnN0YW5jZSB8fCBuZXcgdGhpcygpO1xuICAgIHJldHVybiB0aGlzLl9pbnN0YW5jZS5oaWdobGlnaHQoY29kZSk7XG4gIH1cblxuICBjb25zdHJ1Y3RvcigpIHtcbiAgICB0aGlzLndvcmtlciA9IG5ldyBXb3JrZXIoJy9wbHVnaW4vcmV2ZWFsLXJ1bi1pbi10ZXJtaW5hbC1obGpzLXdvcmtlci5qcycpO1xuICAgIHRoaXMucGVuZGluZyA9IHt9O1xuICAgIHRoaXMud29ya2VyLm9ubWVzc2FnZSA9IChldmVudCkgPT4ge1xuICAgICAgdGhpcy5wZW5kaW5nW2V2ZW50LmRhdGEuY2FsbGJhY2tJZF0ucmVzb2x2ZShldmVudC5kYXRhLmNvZGUudmFsdWUpO1xuICAgICAgZGVsZXRlIHRoaXMucGVuZGluZ1tldmVudC5kYXRhLmNhbGxiYWNrSWRdO1xuICAgIH07XG4gIH1cblxuICBoaWdobGlnaHQoY29kZSkge1xuICAgIGxldCBjYWxsYmFja0lkID0gKERhdGUubm93KCkgKyBNYXRoLnJhbmRvbSgpKS50b1N0cmluZygxNik7XG4gICAgcmV0dXJuIG5ldyBQcm9taXNlKChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgIHRoaXMucGVuZGluZ1tjYWxsYmFja0lkXSA9IHtyZXNvbHZlLCByZWplY3R9O1xuICAgICAgdGhpcy53b3JrZXIucG9zdE1lc3NhZ2Uoe2NhbGxiYWNrSWQsIGNvZGV9KTtcbiAgICB9KTtcbiAgfVxufVxuIiwiY29uc3QgU2xpZGUgPSByZXF1aXJlKCcuL3NsaWRlJyk7XG5cbndpbmRvdy5SdW5JblRlcm1pbmFsID0gY2xhc3Mge1xuICBzdGF0aWMgaW5pdChvcHRpb25zKSB7XG4gICAgbGV0IHJ1bkluVGVybWluYWwgPSBuZXcgdGhpcyhvcHRpb25zKTtcbiAgICBydW5JblRlcm1pbmFsLmxvYWQoKTtcblxuICAgIFJldmVhbC5hZGRFdmVudExpc3RlbmVyKCdmcmFnbWVudHNob3duJywgZnVuY3Rpb24oZXZlbnQpIHtcbiAgICAgIGlmICghZXZlbnQuZnJhZ21lbnQuZGF0YXNldC50ZXJtaW5hbEZyYWdtZW50KSByZXR1cm47XG4gICAgICBsZXQgc2xpZGUgPSBydW5JblRlcm1pbmFsLmZvclNlY3Rpb24oZXZlbnQuZnJhZ21lbnQucGFyZW50RWxlbWVudCk7XG5cbiAgICAgIGlmIChldmVudC5mcmFnbWVudC5kYXRhc2V0LnRlcm1pbmFsRnJhZ21lbnQgPT09ICdzaG93Q29tbWFuZCcpIHtcbiAgICAgICAgc2xpZGUucmVuZGVyQ29tbWFuZCgpO1xuICAgICAgICBzbGlkZS5zY3JvbGxUb0JvdHRvbSgpO1xuICAgICAgfSBlbHNlIGlmIChldmVudC5mcmFnbWVudC5kYXRhc2V0LnRlcm1pbmFsRnJhZ21lbnQgPT09ICdleGVjdXRlJykge1xuICAgICAgICBzbGlkZS5leGVjdXRlQ29tbWFuZCgpO1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgUmV2ZWFsLmFkZEV2ZW50TGlzdGVuZXIoJ2ZyYWdtZW50aGlkZGVuJywgZnVuY3Rpb24oZXZlbnQpIHtcbiAgICAgIGlmICghZXZlbnQuZnJhZ21lbnQuZGF0YXNldC50ZXJtaW5hbEZyYWdtZW50KSByZXR1cm47XG4gICAgICBsZXQgc2xpZGUgPSBydW5JblRlcm1pbmFsLmZvclNlY3Rpb24oZXZlbnQuZnJhZ21lbnQucGFyZW50RWxlbWVudCk7XG5cbiAgICAgIGlmIChldmVudC5mcmFnbWVudC5kYXRhc2V0LnRlcm1pbmFsRnJhZ21lbnQgPT09ICdzaG93Q29tbWFuZCcpIHtcbiAgICAgICAgc2xpZGUucmVuZGVyUHJvbXB0KCk7XG4gICAgICB9IGVsc2UgaWYgKGV2ZW50LmZyYWdtZW50LmRhdGFzZXQudGVybWluYWxGcmFnbWVudCA9PT0gJ2V4ZWN1dGUnKSB7XG4gICAgICAgIHNsaWRlLnJlbmRlckNvbW1hbmQoKTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIFJldmVhbC5hZGRFdmVudExpc3RlbmVyKCdzbGlkZWNoYW5nZWQnLCBmdW5jdGlvbihldmVudCkge1xuICAgICAgbGV0IHNsaWRlID0gcnVuSW5UZXJtaW5hbC5mb3JTZWN0aW9uKGV2ZW50LmN1cnJlbnRTbGlkZSk7XG4gICAgICBpZiAoc2xpZGUgJiYgc2xpZGUuY2xlYXJPblNob3cpIHNsaWRlLnJlbmRlclByb21wdCgpO1xuICAgICAgcnVuSW5UZXJtaW5hbC5yZWxvYWQoe2V4Y2VwdDogW3NsaWRlXX0pO1xuICAgIH0pO1xuXG4gICAgcmV0dXJuIHJ1bkluVGVybWluYWw7XG4gIH1cblxuICBjb25zdHJ1Y3RvcihvcHRpb25zKSB7IHRoaXMub3B0aW9ucyA9IG9wdGlvbnMgfHwge307IH1cblxuICBsb2FkKCkge1xuICAgIGxldCBzZWN0aW9ucyA9IGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3JBbGwoJ3NlY3Rpb25bZGF0YS1ydW4taW4tdGVybWluYWxdJyk7XG4gICAgdGhpcy5zbGlkZXMgPSBbXS5tYXAuY2FsbChzZWN0aW9ucywgc2VjdGlvbiA9PiB7XG4gICAgICByZXR1cm4gbmV3IFNsaWRlKHNlY3Rpb24sIHRoaXMub3B0aW9ucyk7XG4gICAgfSk7XG4gIH1cblxuICByZWxvYWQob3B0aW9ucyA9IHtleGNlcHQ6IFtdfSkge1xuICAgIHRoaXMuc2xpZGVzXG4gICAgICAuZmlsdGVyKHMgPT4gb3B0aW9ucy5leGNlcHQuaW5kZXhPZihzKSAhPT0gLTEpXG4gICAgICAuZm9yRWFjaChzID0+IHMubG9hZCgpKTtcbiAgfVxuXG4gIGZvclNlY3Rpb24oc2VjdGlvbikge1xuICAgIHJldHVybiB0aGlzLnNsaWRlcy5maWx0ZXIoKHMpID0+IHMuc2VjdGlvbiA9PT0gc2VjdGlvbilbMF07XG4gIH1cbn07XG4iLCJjb25zdCBxdWVyeXN0cmluZyA9IHJlcXVpcmUoJ3F1ZXJ5c3RyaW5nJyk7XG5cbm1vZHVsZS5leHBvcnRzID0gKHBhcmFtcywgZm4pID0+IHtcbiAgbGV0IHFzID0gcXVlcnlzdHJpbmcuc3RyaW5naWZ5KHBhcmFtcyk7XG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgbGV0IHNvdXJjZSA9IG5ldyBFdmVudFNvdXJjZShgL3JldmVhbC1ydW4taW4tdGVybWluYWw/JHtxc31gKTtcbiAgICBzb3VyY2UuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIGUgPT4gZm4oSlNPTi5wYXJzZShlLmRhdGEpKSk7XG4gICAgc291cmNlLmFkZEV2ZW50TGlzdGVuZXIoJ2RvbmUnLCAoKSA9PiByZXNvbHZlKHNvdXJjZS5jbG9zZSgpKSk7XG4gICAgc291cmNlLmFkZEV2ZW50TGlzdGVuZXIoJ2Vycm9yJywgZSA9PiB7XG4gICAgICBpZiAoZS5kYXRhKSB7XG4gICAgICAgIGxldCBtZXNzYWdlcyA9IEpTT04ucGFyc2UoZS5kYXRhKS5tZXNzYWdlcztcbiAgICAgICAgbWVzc2FnZXMuZm9yRWFjaChlcnIgPT4gY29uc29sZS5lcnJvcihlcnIpKTtcbiAgICAgICAgcmVqZWN0KG5ldyBFcnJvcihgJHttZXNzYWdlcy5qb2luKCcsICcpfWApKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJlamVjdChlKTtcbiAgICAgIH1cblxuICAgICAgc291cmNlLmNsb3NlKCk7XG4gICAgfSk7XG4gIH0pO1xufTtcbiIsImNvbnN0IHJ1bkNvbW1hbmQgPSByZXF1aXJlKCcuL3J1bi1jb21tYW5kJyk7XG5jb25zdCBIaWdobGlnaGVyID0gcmVxdWlyZSgnLi9oaWdobGlnaGVyJyk7XG5cbm1vZHVsZS5leHBvcnRzID0gY2xhc3Mge1xuICBjb25zdHJ1Y3RvcihzZWN0aW9uLCBvcHRpb25zKSB7XG4gICAgdGhpcy5vcHRpb25zID0gb3B0aW9ucztcbiAgICB0aGlzLnNlY3Rpb24gPSBzZWN0aW9uO1xuXG4gICAgdGhpcy5oaWRlKCk7XG4gICAgdGhpcy5hZGRFbGVtZW50KCdjb250YWluZXInKTtcblxuICAgIHRoaXMuYWRkRWxlbWVudCgndGl0bGUnLCB7dGFnTmFtZTogJ3NwYW4nLCBwYXJlbnQ6IHRoaXMuY29udGFpbmVyfSk7XG4gICAgdGhpcy50aXRsZS5pbm5lclRleHQgPSB0aGlzLnNyYztcblxuICAgIFsnY29kZScsICd0ZXJtJ10uZm9yRWFjaChuYW1lID0+IHRoaXMuYWRkRWxlbWVudChuYW1lLCB7XG4gICAgICB0YWdOYW1lOiAncHJlJyxcbiAgICAgIGNsYXNzZXM6IFsnaGxqcyddLFxuICAgICAgcGFyZW50OiB0aGlzLmNvbnRhaW5lclxuICAgIH0pKTtcblxuICAgIFsnc2hvd0NvbW1hbmQnLCAnZXhlY3V0ZSddLmZvckVhY2gobmFtZSA9PiB0aGlzLmFkZEVsZW1lbnQobmFtZSwge1xuICAgICAgY2xhc3NlczogWydmcmFnbWVudCddLFxuICAgICAgZGF0YXNldDoge3Rlcm1pbmFsRnJhZ21lbnQ6IG5hbWV9XG4gICAgfSkpO1xuXG4gICAgdGhpcy5sb2FkKCk7XG4gIH1cblxuICBsb2FkKCkge1xuICAgIHRoaXMuaGlkZSgpO1xuICAgIHJldHVybiBmZXRjaCh0aGlzLnNyYylcbiAgICAgIC50aGVuKHJlc3BvbnNlID0+IHJlc3BvbnNlLnRleHQoKSlcbiAgICAgIC50aGVuKGNvZGUgPT4gSGlnaGxpZ2hlci5oaWdobGlnaHQoY29kZSkpXG4gICAgICAudGhlbihodG1sID0+IGh0bWwucmVwbGFjZSgvXFxuL2csICc8c3BhbiBjbGFzcz1cImxpbmVcIj48L3NwYW4+XFxuJykpXG4gICAgICAudGhlbihodG1sID0+IHRoaXMuY29kZS5pbm5lckhUTUwgPSBodG1sKVxuICAgICAgLnRoZW4oKCkgPT4gdGhpcy5jb250YWluZXIuc2Nyb2xsVG9wID0gMClcbiAgICAgIC50aGVuKCgpID0+IHRoaXMuc2hvdygpKTtcbiAgfVxuXG4gIGFkZEVsZW1lbnQobmFtZSwgb3B0aW9ucykge1xuICAgIG9wdGlvbnMgPSBvcHRpb25zIHx8IHt9O1xuXG4gICAgdGhpc1tuYW1lXSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQob3B0aW9ucy50YWdOYW1lIHx8ICdkaXYnKTtcbiAgICAob3B0aW9ucy5jbGFzc2VzIHx8IFtdKS5jb25jYXQoW25hbWVdKS5mb3JFYWNoKGNsYXp6ID0+IHtcbiAgICAgIHRoaXNbbmFtZV0uY2xhc3NMaXN0LmFkZChjbGF6eilcbiAgICB9KTtcbiAgICBPYmplY3QuYXNzaWduKHRoaXNbbmFtZV0uZGF0YXNldCwgb3B0aW9ucy5kYXRhc2V0IHx8IHt9KTtcblxuICAgIChvcHRpb25zLnBhcmVudCB8fCB0aGlzLnNlY3Rpb24pLmFwcGVuZENoaWxkKHRoaXNbbmFtZV0pO1xuICAgIHJldHVybiB0aGlzW25hbWVdO1xuICB9XG5cbiAgc2Nyb2xsVG9Cb3R0b20oKSB7XG4gICAgbGV0IGludGVydmFsID0gc2V0SW50ZXJ2YWwoKCkgPT4ge1xuICAgICAgbGV0IHRvcCA9IHRoaXMuY29udGFpbmVyLnNjcm9sbFRvcDtcbiAgICAgIHRoaXMuY29udGFpbmVyLnNjcm9sbFRvcCArPSAyO1xuICAgICAgaWYgKHRvcCA9PT0gdGhpcy5jb250YWluZXIuc2Nyb2xsVG9wKSB7XG4gICAgICAgIGNsZWFySW50ZXJ2YWwoaW50ZXJ2YWwpO1xuICAgICAgfVxuICAgIH0sIDEpO1xuICB9XG5cbiAgaGlkZSgpIHsgdGhpcy5zZWN0aW9uLnN0eWxlLmRpc3BsYXkgPSAnbm9uZSc7IH1cblxuICBzaG93KCkgeyB0aGlzLnNlY3Rpb24uc3R5bGUuZGlzcGxheSA9ICdibG9jayc7IH1cblxuICByZW5kZXJQcm9tcHQoKSB7IHRoaXMudGVybS5pbm5lclRleHQgPSBgPiDilohgOyB9XG5cbiAgcmVuZGVyQ29tbWFuZCgpIHsgdGhpcy50ZXJtLmlubmVyVGV4dCA9IGA+ICR7dGhpcy5jb21tYW5kfeKWiGA7IH1cblxuICBleGVjdXRlQ29tbWFuZCgpIHtcbiAgICB0aGlzLnRlcm0uaW5uZXJUZXh0ID0gYD4gJHt0aGlzLmNvbW1hbmR9XFxuYDtcbiAgICBydW5Db21tYW5kKHRoaXMucGFyYW1zLCBvdXRwdXQgPT4ge1xuICAgICAgdGhpcy50ZXJtLmlubmVyVGV4dCA9IGAke3RoaXMudGVybS5pbm5lclRleHQudHJpbSgpfVxcbiR7b3V0cHV0fWA7XG4gICAgICB0aGlzLnNjcm9sbFRvQm90dG9tKCk7XG4gICAgfSkudGhlbigoKSA9PiB7XG4gICAgICB0aGlzLnRlcm0uaW5uZXJUZXh0ID0gYCR7dGhpcy50ZXJtLmlubmVyVGV4dC50cmltKCkucmVwbGFjZSgv4paIL2csICcnKX1cXG4+IOKWiGA7XG4gICAgICB0aGlzLnNjcm9sbFRvQm90dG9tKCk7XG4gICAgfSkuY2F0Y2goZXJyID0+IHRoaXMudGVybS5pbm5lclRleHQgPSBlcnIubWVzc2FnZSk7XG4gIH1cblxuICBwcm9wZXJ0eShwcm9wKSB7IHJldHVybiB0aGlzLnNlY3Rpb24uZGF0YXNldFtwcm9wXTsgfVxuXG4gIGdldCBjbGVhck9uU2hvdygpIHtcbiAgICByZXR1cm4gIXRoaXMuc2hvd0NvbW1hbmQuY2xhc3NMaXN0LmNvbnRhaW5zKCd2aXNpYmxlJyk7XG4gIH1cblxuICBnZXQgY29tbWFuZCgpIHtcbiAgICBsZXQgY29tbWFuZCA9IGAke3RoaXMuYmlufSAke3RoaXMuc3JjfWBcbiAgICBpZiAodGhpcy5hcmdzKSBjb21tYW5kID0gYCR7Y29tbWFuZH0gJHt0aGlzLmFyZ3N9YDtcbiAgICByZXR1cm4gY29tbWFuZDtcbiAgfVxuXG4gIGdldCBwYXJhbXMoKSB7XG4gICAgbGV0IHBhcmFtcyA9IHtiaW46IHRoaXMuYmluLCBzcmM6IHRoaXMuc3JjfTtcbiAgICBpZiAodGhpcy5hcmdzKSBwYXJhbXMuYXJncyA9IHRoaXMuYXJncztcbiAgICByZXR1cm4gcGFyYW1zO1xuICB9XG5cbiAgZ2V0IGJpbigpIHtcbiAgICByZXR1cm4gdGhpcy5wcm9wZXJ0eSgncnVuSW5UZXJtaW5hbEJpbicpIHx8IHRoaXMub3B0aW9ucy5kZWZhdWx0QmluO1xuICB9XG5cbiAgZ2V0IHNyYygpIHsgcmV0dXJuIHRoaXMucHJvcGVydHkoJ3J1bkluVGVybWluYWwnKTsgfVxuXG4gIGdldCBhcmdzKCkgeyByZXR1cm4gdGhpcy5wcm9wZXJ0eSgncnVuSW5UZXJtaW5hbEFyZ3MnKTsgfVxufTtcbiJdfQ==
399 |
--------------------------------------------------------------------------------