From 74e74ad76285d1d436b8ac5e24bd31ce017d1833 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Fri, 13 Jul 2012 13:00:59 -0400
Subject: [PATCH 1/7] Issue #1683026: Refactor ANSI colors into their own
 function.

---
 includes/drush.inc | 53 +++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 39 insertions(+), 14 deletions(-)

diff --git a/includes/drush.inc b/includes/drush.inc
index 873ecf5..e5a6eec 100644
--- a/includes/drush.inc
+++ b/includes/drush.inc
@@ -1363,17 +1363,6 @@ function drush_pipe_output() {
  *   False in case of an error or failed type, True in all other cases.
  */
 function _drush_print_log($entry) {
-  if (drush_get_context('DRUSH_NOCOLOR')) {
-    $red = "[%s]";
-    $yellow = "[%s]";
-    $green = "[%s]";
-  }
-  else {
-    $red = "\033[31;40m\033[1m[%s]\033[0m";
-    $yellow = "\033[1;33;40m\033[1m[%s]\033[0m";
-    $green = "\033[1;32;40m\033[1m[%s]\033[0m";
-  }
-
   $verbose = drush_get_context('DRUSH_VERBOSE');
   $debug = drush_get_context('DRUSH_DEBUG');
 
@@ -1381,11 +1370,11 @@ function _drush_print_log($entry) {
   switch ($entry['type']) {
     case 'warning' :
     case 'cancel' :
-      $type_msg = sprintf($yellow, $entry['type']);
+      $type_msg = sprintf(drush_ansi_color('yellow'), '[' . $entry['type'] . ']');
       break;
     case 'failed' :
     case 'error' :
-      $type_msg = sprintf($red, $entry['type']);
+      $type_msg = sprintf(drush_ansi_color('red'), '[' . $entry['type'] . ']');
       $return = FALSE;
       break;
     case 'ok' :
@@ -1396,7 +1385,7 @@ function _drush_print_log($entry) {
       if (drush_get_context('DRUSH_QUIET')) {
         return TRUE;
       }
-      $type_msg = sprintf($green, $entry['type']);
+      $type_msg = sprintf(drush_ansi_color('green'), '[' . $entry['type'] . ']');
       break;
     case 'notice' :
     case 'message' :
@@ -1444,6 +1433,42 @@ function _drush_print_log($entry) {
   return $return;
 }
 
+/**
+ * Map a color into the ANSI escape sequence used in a terminal to highlight
+ * text in that color.
+ *
+ * @param $color
+ *   The color to display the text in.
+ *
+ * @return
+ *   A string suitable for use with sprintf(). If color is not available in the
+ *   current terminal, '%s' is returned.
+ */
+function drush_ansi_color($color) {
+  if (!drush_get_context('DRUSH_NOCOLOR')) {
+    switch ($color) {
+      case 'black':
+        return "\033[30;40m\033[1m%s\033[0m";
+      case 'red':
+        return "\033[31;40m\033[1m%s\033[0m";
+      case 'green':
+        return "\033[1;32;40m\033[1m%s\033[0m";
+      case 'yellow':
+        return "\033[1;33;40m\033[1m%s\033[0m";
+      case 'blue':
+        return "\033[1;34;40m\033[1m%s\033[0m";
+      case 'magenta':
+        return "\033[1;35;40m\033[1m%s\033[0m";
+      case 'cyan':
+        return "\033[1;36;40m\033[1m%s\033[0m";
+      case 'white':
+        return "\033[1;37;40m\033[1m%s\033[0m";
+    }
+  }
+
+  return '%s';
+}
+
 // Print all timers for the request.
 function drush_print_timers() {
   global $timers;
-- 
1.7.11.3


From 81dc0f49b80ccf87f77e8a9be53e2edc313b4ba6 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Fri, 13 Jul 2012 13:27:49 -0400
Subject: [PATCH 2/7] Issue #1683026: Add a function to format text with ANSI
 escape sequences.

---
 includes/drush.inc | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/includes/drush.inc b/includes/drush.inc
index e5a6eec..832c3ca 100644
--- a/includes/drush.inc
+++ b/includes/drush.inc
@@ -1469,6 +1469,36 @@ function drush_ansi_color($color) {
   return '%s';
 }
 
+/**
+ * Map a format into the appropriate ANSI escape sequence. We assume that if
+ * the terminal doesn't support color, it doesn't support formatting either.
+ *
+ * @param $format
+ *   The format to display the text in, such as bold or underline.
+ *
+ * @return
+ *   A string suitable for use with sprintf(). If color is not available in the
+ *   current terminal, '%s' is returned.
+ */
+function drush_ansi_format($format) {
+  if (!drush_get_context('DRUSH_NOCOLOR')) {
+    switch ($format) {
+      case 'bold':
+        return "\033[1m%s\033[0m";
+      case 'italics':
+        return "\033[3m%s\033[0m";
+      case 'underline':
+        return "\033[4m%s\033[0m";
+      case 'inverse':
+        return "\033[7m%s\033[0m";
+      case 'strikethrough':
+        return "\033[9m%s\033[0m";
+    }
+  }
+
+  return '%s';
+}
+
 // Print all timers for the request.
 function drush_print_timers() {
   global $timers;
-- 
1.7.11.3


From ca65815c51ce580af417547b3cb2b2963664d53a Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 26 Jul 2012 17:43:40 -0400
Subject: [PATCH 3/7] Issue #1683026: Pull in some helper functions from
 filter.module.

---
 includes/output.inc | 39 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/includes/output.inc b/includes/output.inc
index 14c5f2f..99970d5 100644
--- a/includes/output.inc
+++ b/includes/output.inc
@@ -181,6 +181,45 @@ function drush_html_to_text($html, $allowed_tags = NULL) {
   return html_entity_decode(preg_replace('/ *<[^>]*> */', ' ', $text));
 }
 
+/**
+ * Copied from filter_dom_load() so we don't have to require filter.module to
+ * be loaded.
+ *
+ * @param $text
+ *   The partial (X)HTML snippet to load. Invalid mark-up
+ *   will be corrected on import.
+ * @return
+ *   A DOMDocument that represents the loaded (X)HTML snippet.
+ */
+function _drush_filter_dom_load($text) {
+  $dom_document = new DOMDocument();
+  // Ignore warnings during HTML soup loading.
+  @$dom_document->loadHTML('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' . $text . '</body></html>');
+
+  return $dom_document;
+}
+
+/**
+ * Mostly copied from filter_dom_serialize(), but removing the code to handle
+ * <script> and <style> tags which we hope any caller is sane enough to not try
+ * to render in some kind of terminal.
+ *
+ * @param $dom_document
+ *   A DOMDocument object to serialize, only the tags below
+ *   the first <body> node will be converted.
+ * @return
+ *   A valid (X)HTML snippet, as a string.
+ */
+function _drush_filter_dom_serialize($dom_document) {
+  $body_node = $dom_document->getElementsByTagName('body')->item(0);
+  $body_content = '';
+
+  foreach ($body_node->childNodes as $child_node) {
+    $body_content .= $dom_document->saveXML($child_node);
+  }
+
+  return $body_content;
+}
 
 /**
  * Print a formatted table.
-- 
1.7.11.3


From 4da14da84854425fd272861efd3d567f054c73e2 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 26 Jul 2012 17:46:30 -0400
Subject: [PATCH 4/7] Issue #1683026: Add a function to convert HTML to ANSI
 formatted text.

---
 includes/output.inc | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/includes/output.inc b/includes/output.inc
index 99970d5..12c86d1 100644
--- a/includes/output.inc
+++ b/includes/output.inc
@@ -182,6 +182,34 @@ function drush_html_to_text($html, $allowed_tags = NULL) {
 }
 
 /**
+ * Replace HTML tags with ANSI terminal escape sequences, where appropriate.
+ *
+ * @param DOMDocument &$dom_document
+ *   The DOMDocument to convert.
+ *
+ * @return DOMDocument
+ *   The DOMDocument with HTML tags replaced with ANSI equivalents.
+ */
+function drush_html_to_ansi(&$dom_document) {
+  // Describe how we convert HTML tags to ANSI formats.
+  $html_ansi_map = array(
+    'strong' => 'bold',
+    'b' => 'bold',
+    'em' => 'italics',
+    'i' => 'italics',
+    'strike' => 'strikethrough',
+  );
+
+  foreach ($html_ansi_map as $tag => $ansi_format) {
+    $elements = $dom_document->getElementsByTagName($tag);
+    foreach ($elements as $element) {
+      $text = $dom_document->createTextNode(sprintf(drush_ansi_format($ansi_format), $element->textContent));
+      $element->parentNode->replaceChild($text, $element);
+    }
+  }
+}
+
+/**
  * Copied from filter_dom_load() so we don't have to require filter.module to
  * be loaded.
  *
-- 
1.7.11.3


From 2aa81b63ed09ecd99a7254bafcf8300807601922 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 26 Jul 2012 18:48:44 -0400
Subject: [PATCH 5/7] Issue #1683026: Add a function to convert HTML tags to
 ASCII equivalents.

---
 includes/output.inc | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/includes/output.inc b/includes/output.inc
index 12c86d1..f0481aa 100644
--- a/includes/output.inc
+++ b/includes/output.inc
@@ -182,6 +182,58 @@ function drush_html_to_text($html, $allowed_tags = NULL) {
 }
 
 /**
+ * Replace HTML tags with ASCII equivalents, where appropriate.
+ *
+ * @param DOMDocument &$dom_document
+ *   The DOMDocument to convert.
+ *
+ * @return DOMDocument
+ *   The DOMDocument with HTML tags replaced with ASCII equivalents.
+ */
+function drush_html_to_ascii(&$dom_document) {
+  $html_ascii_map = array(
+    'h1' => array(
+      'wrap' => TRUE,
+      'ascii' => '=====',
+    ),
+    'h2' => array(
+      'wrap' => TRUE,
+      'ascii' => '----',
+    ),
+    'h3' => array(
+      'wrap' => TRUE,
+      'ascii' => ':::',
+    ),
+    'hr' => array(
+      'wrap' => FALSE,
+      'ascii' => '------------------------------------------------------------------------------',
+    ),
+    'li' => array(
+      'wrap' => FALSE,
+      'ascii' => '  * ',
+    ),
+    'br' => array(
+      'wrap' => FALSE,
+      'ascii' => "\n",
+    ),
+  );
+
+  foreach ($html_ascii_map as $tag => $replace) {
+    $elements = $dom_document->getElementsByTagName($tag);
+    foreach ($elements as $element) {
+      if ($replace['wrap']) {
+        $content = $replace['ascii'] . ' ' . $element->textContent . ' ' . $replace['ascii'];
+        $text = $dom_document->createTextNode($content);
+      }
+      else {
+        $text = $dom_document->createTextNode($replace['ascii'] . $element->textContent);
+      }
+      $element->parentNode->replaceChild($text, $element);
+    }
+  }
+}
+
+/**
  * Replace HTML tags with ANSI terminal escape sequences, where appropriate.
  *
  * @param DOMDocument &$dom_document
-- 
1.7.11.3


From b51a04dc9a2250fa27d033a951e585c6721adf87 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 26 Jul 2012 18:49:21 -0400
Subject: [PATCH 6/7] Issue #1683026: Use the new DOM-based functions for
 converting HTML to text.

---
 includes/output.inc | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/includes/output.inc b/includes/output.inc
index f0481aa..48b45fd 100644
--- a/includes/output.inc
+++ b/includes/output.inc
@@ -166,18 +166,15 @@ function dt($string, $args = array()) {
  *   The plain-text representation of the input.
  */
 function drush_html_to_text($html, $allowed_tags = NULL) {
-  $replacements = array(
-    '<hr>' => '------------------------------------------------------------------------------',
-    '<li>' => '  * ',
-    '<h1>' => '===== ',
-    '</h1>' => ' =====',
-    '<h2>' => '---- ',
-    '</h2>' => ' ----',
-    '<h3>' => '::: ',
-    '</h3>' => ' :::',
-    '<br/>' => "\n",
-  );
-  $text = str_replace(array_keys($replacements), array_values($replacements), $html);
+  // To prevent issues with double-escaping ANSI sequences, we pass around a
+  // DOMDocument and convert it back to text at the very end.
+  $dom_document = _drush_filter_dom_load($html);
+  drush_html_to_ansi($dom_document);
+  drush_html_to_ascii($dom_document);
+  $text = _drush_filter_dom_serialize($dom_document);
+
+  // Remove anything that looks like an HTML tag but was not replaced by the
+  // previous functions.
   return html_entity_decode(preg_replace('/ *<[^>]*> */', ' ', $text));
 }
 
-- 
1.7.11.3


From 8753836793f057f5e76ab949fc7c54bebf98d8e4 Mon Sep 17 00:00:00 2001
From: Andrew Berry <deviantintegral@gmail.com>
Date: Thu, 26 Jul 2012 19:02:50 -0400
Subject: [PATCH 7/7] Issue #1683026: Restore and document the $allowed_tags
 parameter.

---
 includes/output.inc | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/includes/output.inc b/includes/output.inc
index 48b45fd..a6ebabe 100644
--- a/includes/output.inc
+++ b/includes/output.inc
@@ -161,6 +161,8 @@ function dt($string, $args = array()) {
  *
  * @param string $html
  *   The html text to convert.
+ * @param array $allowed_tags
+ *   Optional parameter of an array of the allowed HTML tags.
  *
  * @return string
  *   The plain-text representation of the input.
@@ -169,8 +171,8 @@ function drush_html_to_text($html, $allowed_tags = NULL) {
   // To prevent issues with double-escaping ANSI sequences, we pass around a
   // DOMDocument and convert it back to text at the very end.
   $dom_document = _drush_filter_dom_load($html);
-  drush_html_to_ansi($dom_document);
-  drush_html_to_ascii($dom_document);
+  drush_html_to_ansi($dom_document, $allowed_tags);
+  drush_html_to_ascii($dom_document, $allowed_tags);
   $text = _drush_filter_dom_serialize($dom_document);
 
   // Remove anything that looks like an HTML tag but was not replaced by the
@@ -183,11 +185,13 @@ function drush_html_to_text($html, $allowed_tags = NULL) {
  *
  * @param DOMDocument &$dom_document
  *   The DOMDocument to convert.
+ * @param array $allowed_tags
+ *   Optional parameter of an array of the allowed HTML tags.
  *
  * @return DOMDocument
  *   The DOMDocument with HTML tags replaced with ASCII equivalents.
  */
-function drush_html_to_ascii(&$dom_document) {
+function drush_html_to_ascii(&$dom_document, $allowed_tags = array()) {
   $html_ascii_map = array(
     'h1' => array(
       'wrap' => TRUE,
@@ -216,6 +220,10 @@ function drush_html_to_ascii(&$dom_document) {
   );
 
   foreach ($html_ascii_map as $tag => $replace) {
+    if (!empty($allowed_tags) && !in_array($tag, $allowed_tags)) {
+      continue;
+    }
+
     $elements = $dom_document->getElementsByTagName($tag);
     foreach ($elements as $element) {
       if ($replace['wrap']) {
@@ -235,11 +243,13 @@ function drush_html_to_ascii(&$dom_document) {
  *
  * @param DOMDocument &$dom_document
  *   The DOMDocument to convert.
+ * @param array $allowed_tags
+ *   Optional parameter of an array of the allowed HTML tags.
  *
  * @return DOMDocument
  *   The DOMDocument with HTML tags replaced with ANSI equivalents.
  */
-function drush_html_to_ansi(&$dom_document) {
+function drush_html_to_ansi(&$dom_document, $allowed_tags = array()) {
   // Describe how we convert HTML tags to ANSI formats.
   $html_ansi_map = array(
     'strong' => 'bold',
@@ -250,6 +260,10 @@ function drush_html_to_ansi(&$dom_document) {
   );
 
   foreach ($html_ansi_map as $tag => $ansi_format) {
+    if (!empty($allowed_tags) && !in_array($tag, $allowed_tags)) {
+      continue;
+    }
+
     $elements = $dom_document->getElementsByTagName($tag);
     foreach ($elements as $element) {
       $text = $dom_document->createTextNode(sprintf(drush_ansi_format($ansi_format), $element->textContent));
-- 
1.7.11.3

