Commit 094d5463f6bab69d334231b403f440ad74a76026

Chris Wells 2018-03-26T02:14:54

Command Line: Allow specifying output prefix using data-filter-output attribute. (#856)

diff --git a/plugins/command-line/index.html b/plugins/command-line/index.html
index 146b5d7..0de8e3a 100644
--- a/plugins/command-line/index.html
+++ b/plugins/command-line/index.html
@@ -30,7 +30,7 @@
 
 	<p>Add class <strong>command-line</strong> to your <code class="language-markup">&lt;pre></code>. For a server command line, specify the user and host names using the <code class="language-markup">data-user</code> and <code class="language-markup">data-host</code> attributes. The resulting prompt displays a <strong>#</strong> for the root user and <strong>$</strong> for all other users. For any other command line, such as a Windows prompt, you may specify the entire prompt using the <code class="language-markup">data-prompt</code> attribute.</p>
 
-	<p>Optional: You may specify the lines to be presented as output (no prompt) through the <code class="language-markup">data-output</code> attribute on the <code class="language-markup">&lt;pre></code> element in the following simple format:</p>
+	<p>Optional: You may specify the lines to be presented as output (no prompt and no highlighting) through the <code class="language-markup">data-output</code> attribute on the <code class="language-markup">&lt;pre></code> element in the following simple format:</p>
 	<ul>
 		<li>A single number refers to the line with that number</li>
 		<li>Ranges are denoted by two numbers, separated with a hyphen (-)</li>
@@ -52,6 +52,8 @@
 		<dt>1-2, 5, 9-20</dt>
 		<dd>Lines 1 through 2, line 5, lines 9 through 20</dd>
 	</dl>
+
+	<p>Optional: To automatically present some lines as output, you can prefix those lines with any string and specify the prefix using the <code class="language-markup">data-filter-output</code> attribute on the <code class="language-markup">&lt;pre></code> element. For example, <code class="language-markup">data-filter-output="(out)"</code> will treat lines beginning with <code class="language-markup">(out)</code> as output and remove the prefix.</p>
 </section>
 
 <section>
diff --git a/plugins/command-line/prism-command-line.js b/plugins/command-line/prism-command-line.js
index 89e3673..b8595d7 100644
--- a/plugins/command-line/prism-command-line.js
+++ b/plugins/command-line/prism-command-line.js
@@ -4,33 +4,101 @@ if (typeof self === 'undefined' || !self.Prism || !self.document) {
 	return;
 }
 
-Prism.hooks.add('complete', function (env) {
-	if (!env.code) {
+var clsReg = /\s*\bcommand-line\b\s*/;
+
+Prism.hooks.add('before-highlight', function (env) {
+	env.vars = env.vars || {};
+	env.vars['command-line'] = env.vars['command-line'] || {};
+
+	if (env.vars['command-line'].complete || !env.code) {
+		env.vars['command-line'].complete = true;
 		return;
 	}
 
 	// Works only for <code> wrapped inside <pre> (not inline).
 	var pre = env.element.parentNode;
-	var clsReg = /\s*\bcommand-line\b\s*/;
-	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))
-	) {
+	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))) {
+		env.vars['command-line'].complete = true;
 		return;
 	}
 
-	if (env.element.querySelector('.command-line-prompt')) {
-		// Abort if prompt already exists.
+	if (env.element.querySelector('.command-line-prompt')) { // Abort if prompt already exists.
+		env.vars['command-line'].complete = true;
 		return;
 	}
 
-	if (clsReg.test(env.element.className)) {
-		// Remove the class "command-line" from the <code>
-		env.element.className = env.element.className.replace(clsReg, '');
+	var codeLines = env.code.split('\n');
+	env.vars['command-line'].numberOfLines = codeLines.length;
+	env.vars['command-line'].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;
+				}
+				// 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++) {
+					env.vars['command-line'].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 (codeLines[i].indexOf(outputFilter) === 0) { // This line is output. -- cwells
+				env.vars['command-line'].outputLines[i] = codeLines[i].slice(outputFilter.length);
+				codeLines[i] = '';
+			}
+		}
+	}
+
+	env.code = codeLines.join('\n');
+});
+
+Prism.hooks.add('before-insert', function (env) {
+	env.vars = env.vars || {};
+	env.vars['command-line'] = env.vars['command-line'] || {};
+	if (env.vars['command-line'].complete) {
+		return;
 	}
-	if (!clsReg.test(pre.className)) {
-		// Add the class "command-line" to the <pre>
+
+	// Reinsert the output lines into the highlighted code. -- cwells
+	var codeLines = env.highlightedCode.split('\n');
+	for (var i = 0; i < env.vars['command-line'].outputLines.length; i++) {
+		if (env.vars['command-line'].outputLines.hasOwnProperty(i)) {
+			codeLines[i] = env.vars['command-line'].outputLines[i];
+		}
+	}
+	env.highlightedCode = codeLines.join('\n');
+});
+
+Prism.hooks.add('complete', function (env) {
+	env.vars = env.vars || {};
+	env.vars['command-line'] = env.vars['command-line'] || {};
+	if (env.vars['command-line'].complete) {
+		return;
+	}
+
+	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';
 	}
 
@@ -39,43 +107,33 @@ Prism.hooks.add('complete', function (env) {
 	};
 
 	// Create the "rows" that will become the command-line prompts. -- cwells
-	var lines = new Array(1 + env.code.split('\n').length);
+	var promptLines = new Array(env.vars['command-line'].numberOfLines + 1);
 	var promptText = getAttribute('data-prompt', '');
 	if (promptText !== '') {
-		lines = lines.join('<span data-prompt="' + promptText + '"></span>');
+		promptLines = promptLines.join('<span data-prompt="' + promptText + '"></span>');
 	} else {
 		var user = getAttribute('data-user', 'user');
 		var host = getAttribute('data-host', 'localhost');
-		lines = lines.join('<span data-user="' + user + '" data-host="' + host + '"></span>');
+		promptLines = promptLines.join('<span data-user="' + user + '" data-host="' + host + '"></span>');
 	}
 
 	// Create the wrapper element. -- cwells
 	var prompt = document.createElement('span');
 	prompt.className = 'command-line-prompt';
-	prompt.innerHTML = lines;
-
-	// Mark the output lines so they can be styled differently (no prompt). -- cwells
-	var outputSections = pre.getAttribute('data-output') || '';
-	outputSections = outputSections.split(',');
-	for (var i = 0; i < outputSections.length; i++) {
-		var outputRange = outputSections[i].split('-');
-		var outputStart = parseInt(outputRange[0]);
-		var outputEnd = outputStart; // Default: end at the first line when it's not an actual range. -- cwells
-		if (outputRange.length === 2) {
-			outputEnd = parseInt(outputRange[1]);
-		}
+	prompt.innerHTML = promptLines;
 
-		if (!isNaN(outputStart) && !isNaN(outputEnd)) {
-			for (var j = outputStart; j <= outputEnd && j <= prompt.children.length; j++) {
-				var node = prompt.children[j - 1];
-				node.removeAttribute('data-user');
-				node.removeAttribute('data-host');
-				node.removeAttribute('data-prompt');
-			}
+	// Remove the prompt from the output lines. -- cwells
+	for (var i = 0; i < env.vars['command-line'].outputLines.length; i++) {
+		if (env.vars['command-line'].outputLines.hasOwnProperty(i)) {
+			var node = prompt.children[i];
+			node.removeAttribute('data-user');
+			node.removeAttribute('data-host');
+			node.removeAttribute('data-prompt');
 		}
 	}
 
-	env.element.innerHTML = prompt.outerHTML + env.element.innerHTML;
+	env.element.insertBefore(prompt, env.element.firstChild);
+	env.vars['command-line'].complete = true;
 });
 
 }());
diff --git a/plugins/command-line/prism-command-line.min.js b/plugins/command-line/prism-command-line.min.js
index 6e40c05..159f409 100644
--- a/plugins/command-line/prism-command-line.min.js
+++ b/plugins/command-line/prism-command-line.min.js
@@ -1 +1 @@
-!function(){"undefined"!=typeof self&&self.Prism&&self.document&&Prism.hooks.add("complete",function(e){if(e.code){var t=e.element.parentNode,a=/\s*\bcommand-line\b\s*/;if(t&&/pre/i.test(t.nodeName)&&(a.test(t.className)||a.test(e.element.className))&&!e.element.querySelector(".command-line-prompt")){a.test(e.element.className)&&(e.element.className=e.element.className.replace(a,"")),a.test(t.className)||(t.className+=" command-line");var n=function(e,a){return(t.getAttribute(e)||a).replace(/"/g,"&quot")},s=new Array(1+e.code.split("\n").length),r=n("data-prompt","");if(""!==r)s=s.join('<span data-prompt="'+r+'"></span>');else{var l=n("data-user","user"),m=n("data-host","localhost");s=s.join('<span data-user="'+l+'" data-host="'+m+'"></span>')}var o=document.createElement("span");o.className="command-line-prompt",o.innerHTML=s;var i=t.getAttribute("data-output")||"";i=i.split(",");for(var c=0;c<i.length;c++){var p=i[c].split("-"),d=parseInt(p[0]),u=d;if(2===p.length&&(u=parseInt(p[1])),!isNaN(d)&&!isNaN(u))for(var f=d;u>=f&&f<=o.children.length;f++){var N=o.children[f-1];N.removeAttribute("data-user"),N.removeAttribute("data-host"),N.removeAttribute("data-prompt")}}e.element.innerHTML=o.outerHTML+e.element.innerHTML}}})}();
\ No newline at end of file
+!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var e=/\s*\bcommand-line\b\s*/;Prism.hooks.add("before-highlight",function(a){if(a.vars=a.vars||{},a.vars["command-line"]=a.vars["command-line"]||{},a.vars["command-line"].complete||!a.code)return a.vars["command-line"].complete=!0,void 0;var n=a.element.parentNode;if(!n||!/pre/i.test(n.nodeName)||!e.test(n.className)&&!e.test(a.element.className))return a.vars["command-line"].complete=!0,void 0;if(a.element.querySelector(".command-line-prompt"))return a.vars["command-line"].complete=!0,void 0;var t=a.code.split("\n");a.vars["command-line"].numberOfLines=t.length,a.vars["command-line"].outputLines=[];var r=n.getAttribute("data-output"),s=n.getAttribute("data-filter-output");if(r||""===r){r=r.split(",");for(var o=0;o<r.length;o++){var m=r[o].split("-"),i=parseInt(m[0],10),l=2===m.length?parseInt(m[1],10):i;if(!isNaN(i)&&!isNaN(l)){1>i&&(i=1),l>t.length&&(l=t.length),i--,l--;for(var d=i;l>=d;d++)a.vars["command-line"].outputLines[d]=t[d],t[d]=""}}}else if(s)for(var o=0;o<t.length;o++)0===t[o].indexOf(s)&&(a.vars["command-line"].outputLines[o]=t[o].slice(s.length),t[o]="");a.code=t.join("\n")}),Prism.hooks.add("before-insert",function(e){if(e.vars=e.vars||{},e.vars["command-line"]=e.vars["command-line"]||{},!e.vars["command-line"].complete){for(var a=e.highlightedCode.split("\n"),n=0;n<e.vars["command-line"].outputLines.length;n++)e.vars["command-line"].outputLines.hasOwnProperty(n)&&(a[n]=e.vars["command-line"].outputLines[n]);e.highlightedCode=a.join("\n")}}),Prism.hooks.add("complete",function(a){if(a.vars=a.vars||{},a.vars["command-line"]=a.vars["command-line"]||{},!a.vars["command-line"].complete){var n=a.element.parentNode;e.test(a.element.className)&&(a.element.className=a.element.className.replace(e," ")),e.test(n.className)||(n.className+=" command-line");var t=function(e,a){return(n.getAttribute(e)||a).replace(/"/g,"&quot")},r=new Array(a.vars["command-line"].numberOfLines+1),s=t("data-prompt","");if(""!==s)r=r.join('<span data-prompt="'+s+'"></span>');else{var o=t("data-user","user"),m=t("data-host","localhost");r=r.join('<span data-user="'+o+'" data-host="'+m+'"></span>')}var i=document.createElement("span");i.className="command-line-prompt",i.innerHTML=r;for(var l=0;l<a.vars["command-line"].outputLines.length;l++)if(a.vars["command-line"].outputLines.hasOwnProperty(l)){var d=i.children[l];d.removeAttribute("data-user"),d.removeAttribute("data-host"),d.removeAttribute("data-prompt")}a.element.insertBefore(i,a.element.firstChild),a.vars["command-line"].complete=!0}})}}();
\ No newline at end of file