├── .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 | [![NPM](https://nodei.co/npm/reveal-run-in-terminal.png)](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 | ![](https://github.com/dluxemburg/reveal-run-in-terminal/blob/master/demo.gif?raw=true&v=2) 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 | --------------------------------------------------------------------------------