Commit 8c9c2896d36940a738fb5eb5b4590850d2d29152

Michael Schmidt 2020-06-27T20:46:04

Command Line: Some refactoring (#2290)

diff --git a/plugins/command-line/prism-command-line.js b/plugins/command-line/prism-command-line.js
index 0d1392f..907fd36 100644
--- a/plugins/command-line/prism-command-line.js
+++ b/plugins/command-line/prism-command-line.js
@@ -1,140 +1,182 @@
-(function() {
+(function () {
 
-if (typeof self === 'undefined' || !self.Prism || !self.document) {
-	return;
-}
-
-var clsReg = /(?:^|\s)command-line(?:\s|$)/;
-
-Prism.hooks.add('before-highlight', function (env) {
-	var vars = env.vars = env.vars || {};
-	var commandLine = vars['command-line'] = vars['command-line'] || {};
-
-	if (commandLine.complete || !env.code) {
-		commandLine.complete = true;
+	if (typeof self === 'undefined' || !self.Prism || !self.document) {
 		return;
 	}
 
-	// Works only for <code> wrapped inside <pre> (not inline).
-	var pre = env.element.parentNode;
-	if (!pre || !/pre/i.test(pre.nodeName) || // Abort only if neither the <pre> nor the <code> have the class
-		(!clsReg.test(pre.className) && !clsReg.test(env.element.className))) {
-		commandLine.complete = true;
-		return;
+	var CLASS_PATTERN = /(?:^|\s)command-line(?:\s|$)/;
+	var PROMPT_CLASS = 'command-line-prompt';
+
+	/** @type {(str: string, prefix: string) => boolean} */
+	var startsWith = "".startsWith
+		? function (s, p) { return s.startsWith(p); }
+		: function (s, p) { return s.indexOf(p) === 0; };
+
+	/**
+	 * Repeats the given string some number of times.
+	 *
+	 * This is just a polyfill for `String.prototype.repeat`.
+	 *
+	 * @param {string} str
+	 * @param {number} times
+	 * @returns {string}
+	 */
+	function repeat(str, times) {
+		var s = "";
+		for (var i = 0; i < times; i++) {
+			s += str;
+		}
+		return s;
 	}
 
-	// The element might be highlighted multiple times, so we just remove the previous prompt
-	var existingPrompt = env.element.querySelector('.command-line-prompt');
-	if (existingPrompt) {
-		existingPrompt.remove();
+	/**
+	 * Returns the command line info object from the given hook environment.
+	 *
+	 * @param {any} env
+	 * @returns {CommandLineInfo}
+	 *
+	 * @typedef CommandLineInfo
+	 * @property {boolean} [complete]
+	 * @property {number} [numberOfLines]
+	 * @property {string[]} [outputLines]
+	 */
+	function getCommandLineInfo(env) {
+		var vars = env.vars = env.vars || {};
+		return vars['command-line'] = vars['command-line'] || {};
 	}
 
-	var codeLines = env.code.split('\n');
-	commandLine.numberOfLines = codeLines.length;
-	var outputLines = commandLine.outputLines = [];
-
-	var outputSections = pre.getAttribute('data-output');
-	var outputFilter = pre.getAttribute('data-filter-output');
-	if (outputSections || outputSections === '') { // The user specified the output lines. -- cwells
-		outputSections = outputSections.split(',');
-		for (var i = 0; i < outputSections.length; i++) { // Parse the output sections into start/end ranges. -- cwells
-			var range = outputSections[i].split('-');
-			var outputStart = parseInt(range[0], 10);
-			var outputEnd = (range.length === 2 ? parseInt(range[1], 10) : outputStart);
-
-			if (!isNaN(outputStart) && !isNaN(outputEnd)) {
-				if (outputStart < 1) {
-					outputStart = 1;
-				}
-				if (outputEnd > codeLines.length) {
-					outputEnd = codeLines.length;
+
+	Prism.hooks.add('before-highlight', function (env) {
+		var commandLine = getCommandLineInfo(env);
+
+		if (commandLine.complete || !env.code) {
+			commandLine.complete = true;
+			return;
+		}
+
+		// Works only for <code> wrapped inside <pre> (not inline).
+		var pre = env.element.parentElement;
+		if (!pre || !/pre/i.test(pre.nodeName) || // Abort only if neither the <pre> nor the <code> have the class
+			(!CLASS_PATTERN.test(pre.className) && !CLASS_PATTERN.test(env.element.className))) {
+			commandLine.complete = true;
+			return;
+		}
+
+		// The element might be highlighted multiple times, so we just remove the previous prompt
+		var existingPrompt = env.element.querySelector('.' + PROMPT_CLASS);
+		if (existingPrompt) {
+			existingPrompt.remove();
+		}
+
+		var codeLines = env.code.split('\n');
+		commandLine.numberOfLines = codeLines.length;
+		/** @type {string[]} */
+		var outputLines = commandLine.outputLines = [];
+
+		var outputSections = pre.getAttribute('data-output');
+		var outputFilter = pre.getAttribute('data-filter-output');
+		if (outputSections !== null) { // The user specified the output lines. -- cwells
+			outputSections.split(',').forEach(function (section) {
+				var range = section.split('-');
+				var outputStart = parseInt(range[0], 10);
+				var outputEnd = range.length === 2 ? parseInt(range[1], 10) : outputStart;
+
+				if (!isNaN(outputStart) && !isNaN(outputEnd)) {
+					if (outputStart < 1) {
+						outputStart = 1;
+					}
+					if (outputEnd > codeLines.length) {
+						outputEnd = codeLines.length;
+					}
+					// Convert start and end to 0-based to simplify the arrays. -- cwells
+					outputStart--;
+					outputEnd--;
+					// Save the output line in an array and clear it in the code so it's not highlighted. -- cwells
+					for (var j = outputStart; j <= outputEnd; j++) {
+						outputLines[j] = codeLines[j];
+						codeLines[j] = '';
+					}
 				}
-				// Convert start and end to 0-based to simplify the arrays. -- cwells
-				outputStart--;
-				outputEnd--;
-				// Save the output line in an array and clear it in the code so it's not highlighted. -- cwells
-				for (var j = outputStart; j <= outputEnd; j++) {
-					outputLines[j] = codeLines[j];
-					codeLines[j] = '';
+			});
+		} else if (outputFilter) { // Treat lines beginning with this string as output. -- cwells
+			for (var i = 0; i < codeLines.length; i++) {
+				if (startsWith(codeLines[i], outputFilter)) { // This line is output. -- cwells
+					outputLines[i] = codeLines[i].slice(outputFilter.length);
+					codeLines[i] = '';
 				}
 			}
 		}
-	} else if (outputFilter) { // Treat lines beginning with this string as output. -- cwells
-		for (var i = 0; i < codeLines.length; i++) {
-			if (codeLines[i].indexOf(outputFilter) === 0) { // This line is output. -- cwells
-				outputLines[i] = codeLines[i].slice(outputFilter.length);
-				codeLines[i] = '';
-			}
+
+		env.code = codeLines.join('\n');
+	});
+
+	Prism.hooks.add('before-insert', function (env) {
+		var commandLine = getCommandLineInfo(env);
+
+		if (commandLine.complete) {
+			return;
 		}
-	}
 
-	env.code = codeLines.join('\n');
-});
+		// Reinsert the output lines into the highlighted code. -- cwells
+		var codeLines = env.highlightedCode.split('\n');
+		var outputLines = commandLine.outputLines || [];
+		for (var i = 0, l = outputLines.length; i < l; i++) {
+			if (outputLines.hasOwnProperty(i)) {
+				codeLines[i] = outputLines[i];
+			}
+		}
+		env.highlightedCode = codeLines.join('\n');
+	});
 
-Prism.hooks.add('before-insert', function (env) {
-	var vars = env.vars = env.vars || {};
-	var commandLine = vars['command-line'] = vars['command-line'] || {};
-	if (commandLine.complete) {
-		return;
-	}
+	Prism.hooks.add('complete', function (env) {
+		var commandLine = getCommandLineInfo(env);
 
-	// Reinsert the output lines into the highlighted code. -- cwells
-	var codeLines = env.highlightedCode.split('\n');
-	for (var i = 0, l = (commandLine.outputLines || []).length; i < l; i++) {
-		if (commandLine.outputLines.hasOwnProperty(i)) {
-			codeLines[i] = commandLine.outputLines[i];
+		if (commandLine.complete) {
+			return;
 		}
-	}
-	env.highlightedCode = codeLines.join('\n');
-});
 
-Prism.hooks.add('complete', function (env) {
-	var vars = env.vars = env.vars || {};
-	var commandLine = vars['command-line'] = vars['command-line'] || {};
-	if (commandLine.complete) {
-		return;
-	}
+		var pre = env.element.parentElement;
+		if (CLASS_PATTERN.test(env.element.className)) { // Remove the class "command-line" from the <code>
+			env.element.className = env.element.className.replace(CLASS_PATTERN, ' ');
+		}
+		if (!CLASS_PATTERN.test(pre.className)) { // Add the class "command-line" to the <pre>
+			pre.className += ' command-line';
+		}
 
-	var pre = env.element.parentNode;
-	if (clsReg.test(env.element.className)) { // Remove the class "command-line" from the <code>
-		env.element.className = env.element.className.replace(clsReg, ' ');
-	}
-	if (!clsReg.test(pre.className)) { // Add the class "command-line" to the <pre>
-		pre.className += ' command-line';
-	}
+		function getAttribute(key, defaultValue) {
+			return (pre.getAttribute(key) || defaultValue).replace(/"/g, '&quot');
+		}
 
-	var getAttribute = function(key, defaultValue) {
-		return (pre.getAttribute(key) || defaultValue).replace(/"/g, '&quot');
-	};
-
-	// Create the "rows" that will become the command-line prompts. -- cwells
-	var promptLines = new Array((commandLine.numberOfLines || 0) + 1);
-	var promptText = getAttribute('data-prompt', '');
-	if (promptText !== '') {
-		promptLines = promptLines.join('<span data-prompt="' + promptText + '"></span>');
-	} else {
-		var user = getAttribute('data-user', 'user');
-		var host = getAttribute('data-host', 'localhost');
-		promptLines = promptLines.join('<span data-user="' + user + '" data-host="' + host + '"></span>');
-	}
+		// Create the "rows" that will become the command-line prompts. -- cwells
+		var promptLines;
+		var rowCount = commandLine.numberOfLines || 0;
+		var promptText = getAttribute('data-prompt', '');
+		if (promptText !== '') {
+			promptLines = repeat('<span data-prompt="' + promptText + '"></span>', rowCount);
+		} else {
+			var user = getAttribute('data-user', 'user');
+			var host = getAttribute('data-host', 'localhost');
+			promptLines = repeat('<span data-user="' + user + '" data-host="' + host + '"></span>', rowCount);
+		}
 
-	// Create the wrapper element. -- cwells
-	var prompt = document.createElement('span');
-	prompt.className = 'command-line-prompt';
-	prompt.innerHTML = promptLines;
-
-	// Remove the prompt from the output lines. -- cwells
-	for (var i = 0, l = (commandLine.outputLines || []).length; i < l; i++) {
-		if (commandLine.outputLines.hasOwnProperty(i)) {
-			var node = prompt.children[i];
-			node.removeAttribute('data-user');
-			node.removeAttribute('data-host');
-			node.removeAttribute('data-prompt');
+		// Create the wrapper element. -- cwells
+		var prompt = document.createElement('span');
+		prompt.className = PROMPT_CLASS;
+		prompt.innerHTML = promptLines;
+
+		// Remove the prompt from the output lines. -- cwells
+		var outputLines = commandLine.outputLines || [];
+		for (var i = 0, l = outputLines.length; i < l; i++) {
+			if (outputLines.hasOwnProperty(i)) {
+				var node = prompt.children[i];
+				node.removeAttribute('data-user');
+				node.removeAttribute('data-host');
+				node.removeAttribute('data-prompt');
+			}
 		}
-	}
 
-	env.element.insertBefore(prompt, env.element.firstChild);
-	commandLine.complete = true;
-});
+		env.element.insertBefore(prompt, env.element.firstChild);
+		commandLine.complete = true;
+	});
 
 }());
diff --git a/plugins/command-line/prism-command-line.min.js b/plugins/command-line/prism-command-line.min.js
index b0f6cd1..8a3d347 100644
--- a/plugins/command-line/prism-command-line.min.js
+++ b/plugins/command-line/prism-command-line.min.js
@@ -1 +1 @@
-!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var f=/(?:^|\s)command-line(?:\s|$)/;Prism.hooks.add("before-highlight",function(e){var t=e.vars=e.vars||{},a=t["command-line"]=t["command-line"]||{};if(!a.complete&&e.code){var n=e.element.parentNode;if(n&&/pre/i.test(n.nodeName)&&(f.test(n.className)||f.test(e.element.className))){var r=e.element.querySelector(".command-line-prompt");r&&r.remove();var s=e.code.split("\n");a.numberOfLines=s.length;var o=a.outputLines=[],i=n.getAttribute("data-output"),l=n.getAttribute("data-filter-output");if(i||""===i){i=i.split(",");for(var m=0;m<i.length;m++){var d=i[m].split("-"),p=parseInt(d[0],10),c=2===d.length?parseInt(d[1],10):p;if(!isNaN(p)&&!isNaN(c)){p<1&&(p=1),c>s.length&&(c=s.length),c--;for(var u=--p;u<=c;u++)o[u]=s[u],s[u]=""}}}else if(l)for(m=0;m<s.length;m++)0===s[m].indexOf(l)&&(o[m]=s[m].slice(l.length),s[m]="");e.code=s.join("\n")}else a.complete=!0}else a.complete=!0}),Prism.hooks.add("before-insert",function(e){var t=e.vars=e.vars||{},a=t["command-line"]=t["command-line"]||{};if(!a.complete){for(var n=e.highlightedCode.split("\n"),r=0,s=(a.outputLines||[]).length;r<s;r++)a.outputLines.hasOwnProperty(r)&&(n[r]=a.outputLines[r]);e.highlightedCode=n.join("\n")}}),Prism.hooks.add("complete",function(e){var t=e.vars=e.vars||{},a=t["command-line"]=t["command-line"]||{};if(!a.complete){var n=e.element.parentNode;f.test(e.element.className)&&(e.element.className=e.element.className.replace(f," ")),f.test(n.className)||(n.className+=" command-line");var r=function(e,t){return(n.getAttribute(e)||t).replace(/"/g,"&quot")},s=new Array((a.numberOfLines||0)+1),o=r("data-prompt","");if(""!==o)s=s.join('<span data-prompt="'+o+'"></span>');else{var i=r("data-user","user"),l=r("data-host","localhost");s=s.join('<span data-user="'+i+'" data-host="'+l+'"></span>')}var m=document.createElement("span");m.className="command-line-prompt",m.innerHTML=s;for(var d=0,p=(a.outputLines||[]).length;d<p;d++)if(a.outputLines.hasOwnProperty(d)){var c=m.children[d];c.removeAttribute("data-user"),c.removeAttribute("data-host"),c.removeAttribute("data-prompt")}e.element.insertBefore(m,e.element.firstChild),a.complete=!0}})}}();
\ No newline at end of file
+!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var f=/(?:^|\s)command-line(?:\s|$)/,p="command-line-prompt",m="".startsWith?function(e,t){return e.startsWith(t)}:function(e,t){return 0===e.indexOf(t)};Prism.hooks.add("before-highlight",function(e){var t=h(e);if(!t.complete&&e.code){var n=e.element.parentElement;if(n&&/pre/i.test(n.nodeName)&&(f.test(n.className)||f.test(e.element.className))){var a=e.element.querySelector("."+p);a&&a.remove();var s=e.code.split("\n");t.numberOfLines=s.length;var o=t.outputLines=[],r=n.getAttribute("data-output"),i=n.getAttribute("data-filter-output");if(null!==r)r.split(",").forEach(function(e){var t=e.split("-"),n=parseInt(t[0],10),a=2===t.length?parseInt(t[1],10):n;if(!isNaN(n)&&!isNaN(a)){n<1&&(n=1),a>s.length&&(a=s.length),a--;for(var r=--n;r<=a;r++)o[r]=s[r],s[r]=""}});else if(i)for(var l=0;l<s.length;l++)m(s[l],i)&&(o[l]=s[l].slice(i.length),s[l]="");e.code=s.join("\n")}else t.complete=!0}else t.complete=!0}),Prism.hooks.add("before-insert",function(e){var t=h(e);if(!t.complete){for(var n=e.highlightedCode.split("\n"),a=t.outputLines||[],r=0,s=a.length;r<s;r++)a.hasOwnProperty(r)&&(n[r]=a[r]);e.highlightedCode=n.join("\n")}}),Prism.hooks.add("complete",function(e){var t=h(e);if(!t.complete){var n,a=e.element.parentElement;f.test(e.element.className)&&(e.element.className=e.element.className.replace(f," ")),f.test(a.className)||(a.className+=" command-line");var r=t.numberOfLines||0,s=u("data-prompt","");if(""!==s)n=d('<span data-prompt="'+s+'"></span>',r);else n=d('<span data-user="'+u("data-user","user")+'" data-host="'+u("data-host","localhost")+'"></span>',r);var o=document.createElement("span");o.className=p,o.innerHTML=n;for(var i=t.outputLines||[],l=0,m=i.length;l<m;l++)if(i.hasOwnProperty(l)){var c=o.children[l];c.removeAttribute("data-user"),c.removeAttribute("data-host"),c.removeAttribute("data-prompt")}e.element.insertBefore(o,e.element.firstChild),t.complete=!0}function u(e,t){return(a.getAttribute(e)||t).replace(/"/g,"&quot")}})}function d(e,t){for(var n="",a=0;a<t;a++)n+=e;return n}function h(e){var t=e.vars=e.vars||{};return t["command-line"]=t["command-line"]||{}}}();
\ No newline at end of file