3 use Drupal\Core\Render\Markup;
4 use Drush\Log\LogLevel;
7 * @defgroup outputfunctions Process output text.
12 * Prints a message with optional indentation. In general,
13 * drush_log($message, LogLevel::OK) is often a better choice than this function.
14 * That gets your confirmation message (for example) into the logs for this
15 * drush request. Consider that drush requests may be executed remotely and
19 * The message to print.
21 * The indentation (space chars)
23 * File handle to write to. NULL will write
24 * to standard output, STDERR will write to the standard
25 * error. See http://php.net/manual/en/features.commandline.io-streams.php
27 * Add a "\n" to the end of the output. Defaults to TRUE.
29 function drush_print($message = '', $indent = 0, $handle = NULL, $newline = TRUE) {
30 $msg = str_repeat(' ', $indent) . (string)$message;
34 if (($charset = drush_get_option('output_charset')) && function_exists('iconv')) {
35 $msg = iconv('UTF-8', $charset, $msg);
37 if (!isset($handle)) {
39 // In the past, Drush would use `print` here; now that we are using
40 // fwrite (to avoid problems with php sending the http headers), we
41 // must explicitly capture the output, because ob_start() / ob_end()
42 // does not capture output written via fwrite to STDOUT.
43 drush_backend_output_collect($msg);
45 fwrite($handle, $msg);
49 * Print a prompt -- that is, a message with no trailing newline.
51 function drush_print_prompt($message, $indent = 0, $handle = NULL) {
52 drush_print($message, $indent, $handle, FALSE);
56 * Stores a message which is printed during drush_shutdown() if in compact mode.
58 * The message to print. If $message is an array,
59 * then each element of the array is printed on a
62 function drush_print_pipe($message = '') {
63 $buffer = &drush_get_context('DRUSH_PIPE_BUFFER' , '');
64 if (is_array($message)) {
65 $message = implode("\n", $message) . "\n";
71 * Prints an array or string.
75 * Add a "\n" to the end of the output. Defaults to TRUE.
77 function drush_print_r($array, $handle = NULL, $newline = TRUE) {
78 drush_print(print_r($array, TRUE), 0, $handle, $newline);
82 * Format some data and print it out. Respect --format option.
84 function drush_print_format($input, $default_format, $metadata = NULL) {
85 drush_print(drush_format($input, $metadata, drush_get_option('format', $default_format)));
89 * Prepares a variable for printing. Loads the requested output
90 * formatter and uses it to process the provided input.
94 * @param string $metadata
95 * Optional formatting metadata. Not used by all formats.
96 * 'label' - text to label data with in some formats (e.g. export, config)
97 * @param string $format
98 * Optional format; defaults to print_r.
100 * The variable formatted according to specified format.
101 * Ready for drush_print().
103 function drush_format($input, $metadata = NULL, $format = NULL) {
105 // Look up the format and label, and fill in default values for metadata
106 if (is_string($metadata)) {
107 $metadata = array('label' => $metadata);
109 if (!is_array($metadata)) {
113 'metameta' => array(),
115 if (isset($metadata['format'])) {
116 // If the format is set in metadata, then it will
117 // override whatever is passed in via the $format parameter.
118 $format = $metadata['format'];
120 if (!isset($format)) {
121 // TODO: we shouldn't ever call drush_get_option here.
122 // Get rid of this once we confirm that there are no
123 // callers that still need it.
124 $format = drush_get_option('format', 'print-r');
127 $formatter = drush_load_engine('outputformat', $format);
129 if ($formatter === TRUE) {
130 return drush_set_error(dt('No outputformat class defined for !format', array('!format' => $format)));
132 $output = $formatter->process($input, $metadata);
139 * Rudimentary replacement for Drupal API t() function.
142 * String to process, possibly with replacement item.
144 * An associative array of replacement items.
147 * The processed string.
151 function dt($string, $args = array()) {
153 if (function_exists('t') && drush_drupal_major_version() == 7) {
154 $output = t($string, $args);
156 // The language system requires a working container which has the string
157 // translation service.
158 else if (drush_drupal_major_version() >= 8 && \Drupal::hasService('string_translation')) {
159 // Drupal 8 removes !var replacements, creating a user-level error when
160 // these are used, so we'll pre-replace these before calling translate().
161 list($string, $args) = replace_legacy_dt_args($string, $args);
162 $output = (string) \Drupal::translation()->translate($string, $args);
164 else if (function_exists('t') && drush_drupal_major_version() <= 7 && function_exists('theme')) {
165 $output = t($string, $args);
168 // If Drupal's t() function unavailable.
169 if (!isset($output)) {
171 $output = strtr($string, $args);
181 * Replace placeholders that begin with a '!' with '@'.
183 function replace_legacy_dt_args(&$string, &$legacy_args) {
186 foreach ($legacy_args as $name => $argument) {
187 if ($name[0] == '!') {
188 $new_arg = '@' . substr($name, 1);
189 $replace[$name] = $new_arg;
190 $args[$new_arg] = Markup::create($argument);
193 $args[$name] = $argument;
197 strtr($string, $replace),
203 * Convert html to readable text. Compatible API to
204 * drupal_html_to_text, but less functional. Caller
205 * might prefer to call drupal_html_to_text if there
206 * is a bootstrapped Drupal site available.
208 * @param string $html
209 * The html text to convert.
212 * The plain-text representation of the input.
214 function drush_html_to_text($html, $allowed_tags = NULL) {
215 $replacements = array(
216 '<hr>' => '------------------------------------------------------------------------------',
226 $text = str_replace(array_keys($replacements), array_values($replacements), $html);
227 return html_entity_decode(preg_replace('/ *<[^>]*> */', ' ', $text));
232 * Print a formatted table.
237 * If TRUE, the first line will be treated as table header.
239 * An associative array whose keys are column IDs and values are widths of each column (in characters).
240 * If not specified this will be determined automatically, based on a "best fit" algorithm.
242 * File handle to write to. NULL will write
243 * to standard output, STDERR will write to the standard
244 * error. See http://php.net/manual/en/features.commandline.io-streams.php
246 * Use $tbl->getTable() to get the output from the return value.
248 function drush_print_table($rows, $header = FALSE, $widths = array(), $handle = NULL) {
249 $tbl = _drush_format_table($rows, $header, $widths);
250 $output = $tbl->getTable();
251 if (!stristr(PHP_OS, 'WIN')) {
252 $output = str_replace("\r\n", PHP_EOL, $output);
255 drush_print(rtrim($output), 0, $handle);
260 * Format a table of data.
265 * If TRUE, the first line will be treated as table header.
267 * An associative array whose keys are column IDs and values are widths of each column (in characters).
268 * If not specified this will be determined automatically, based on a "best fit" algorithm.
269 * @param array $console_table_options
270 * An array that is passed along when constructing a Console_Table instance.
272 * The formatted output.
274 function drush_format_table($rows, $header = FALSE, $widths = array(), $console_table_options = array()) {
275 $tbl = _drush_format_table($rows, $header, $widths, $console_table_options);
276 $output = $tbl->getTable();
277 if (!drush_is_windows()) {
278 $output = str_replace("\r\n", PHP_EOL, $output);
283 function _drush_format_table($rows, $header = FALSE, $widths = array(), $console_table_options = array()) {
285 $tbl = new ReflectionClass('Console_Table');
286 $console_table_options += array(CONSOLE_TABLE_ALIGN_LEFT , '');
287 $tbl = $tbl->newInstanceArgs($console_table_options);
289 $auto_widths = drush_table_column_autowidth($rows, $widths);
291 // Do wordwrap on all cells.
293 foreach ($rows as $rowkey => $row) {
294 foreach ($row as $col_num => $cell) {
295 $newrows[$rowkey][$col_num] = wordwrap($cell, $auto_widths[$col_num], "\n", TRUE);
296 if (isset($widths[$col_num])) {
297 $newrows[$rowkey][$col_num] = str_pad($newrows[$rowkey][$col_num], $widths[$col_num]);
302 $headers = array_shift($newrows);
303 $tbl->setHeaders($headers);
306 $tbl->addData($newrows);
311 * Convert an associative array of key : value pairs into
312 * a table suitable for processing by drush_print_table.
314 * @param $keyvalue_table
315 * An associative array of key : value pairs.
317 * 'key-value-item': If the value is an array, then
318 * the item key determines which item from the value
319 * will appear in the output.
321 * An array of arrays, where the keys from the input
322 * array are stored in the first column, and the values
323 * are stored in the third. A second colum is created
324 * specifically to hold the ':' separator.
326 function drush_key_value_to_array_table($keyvalue_table, $metadata = array()) {
327 if (!is_array($keyvalue_table)) {
328 return drush_set_error('DRUSH_INVALID_FORMAT', dt("Data not compatible with selected key-value output format."));
330 if (!is_array($metadata)) {
331 $metadata = array('key-value-item' => $metadata);
333 $item_key = array_key_exists('key-value-item', $metadata) ? $metadata['key-value-item'] : NULL;
339 foreach ($keyvalue_table as $key => $value) {
341 if (is_array($value) && isset($item_key)) {
342 $value = $value[$item_key];
344 // We should only have simple values here, but if
345 // we don't, use drush_format() to flatten as a fallback.
346 if (is_array($value)) {
347 $value = drush_format($value, $metadata, 'list');
350 if (isset($metadata['include-field-labels']) && !$metadata['include-field-labels']) {
351 $table[] = array(isset($value) ? $value : '');
353 elseif (isset($value)) {
354 $table[] = array($key, ' :', $value);
357 $table[] = array($key . ':', '', '');
364 * Select the fields that should be used.
366 function drush_select_fields($all_field_labels, $fields, $strict = TRUE) {
367 $field_labels = array();
368 foreach ($fields as $field) {
369 if (array_key_exists($field, $all_field_labels)) {
370 $field_labels[$field] = $all_field_labels[$field];
373 // Allow the user to select fields via their human-readable names.
374 // This is less convenient than the field name (since the human-readable
375 // names may contain spaces, and must therefore be quoted), but these are
376 // the values that the user sees in the command output. n.b. the help
377 // text lists fields by their more convenient machine names.
378 $key = array_search(strtolower($field), array_map('strtolower', $all_field_labels));
379 if ($key !== FALSE) {
380 $field_labels[$key] = $all_field_labels[$key];
383 $field_labels[$field] = $field;
387 return $field_labels;
391 * Select the fields from the input array that should be output.
394 * An associative array of key:value pairs to be output
396 * An associative array that maps FROM a field in $input
397 * TO the corresponding field name in $output.
399 * An associative array that maps FROM a field in $fields
400 * TO the actual field in $input to use in the preceeding
401 * translation described above.
403 * The input array, re-ordered and re-keyed per $fields
405 function drush_select_output_fields($input, $fields, $mapping = array(), $default_value = NULL) {
407 if (empty($fields)) {
411 foreach ($fields as $key => $label) {
412 $value = drush_lookup_field_by_path($input, $key, $mapping, $default_value);
414 $result[$label] = $value;
422 * Return a specific item inside an array, as identified
423 * by the provided path.
426 * An array of items, potentially multiple layers deep.
428 * A specifier of array keys, either in an array or separated by
429 * a '/', that list the elements of the array to access. This
430 * works much like a very simple version of xpath for arrays, with
431 * all items being treated identically (like elements).
433 * (optional) An array whose keys may correspond to the $path parameter and
434 * whose values are the corresponding paths to be used in $input.
438 * $input = array('#name' => 'site.dev', '#id' => '222');
440 * result: 'site.dev';
444 * $input = array('ca' => array('sf' => array('mission'=>array('1700'=>'woodward'))));
445 * $path = 'ca/sf/mission/1701';
450 * $input = array('#name' => 'site.dev', '#id' => '222');
452 * $mapping = array('name' => '#name');
453 * result: 'site.dev';
455 function drush_lookup_field_by_path($input, $path, $mapping = array(), $default_value = NULL) {
457 if (isset($mapping[$path])) {
458 $path = $mapping[$path];
460 if (!is_array($path)) {
461 $parts = explode('/', $path);
463 if (!empty($parts)) {
465 foreach ($parts as $key) {
466 if ((is_array($result)) && (isset($result[$key]))) {
467 $result = $result[$key];
470 return $default_value;
478 * Given a table array (an associative array of associative arrays),
479 * return an array of all of the values with the specified field name.
481 function drush_output_get_selected_field($input, $field_name, $default_value = '') {
483 foreach ($input as $key => $data) {
484 if (is_array($data) && isset($data[$field_name])) {
485 $result[] = $data[$field_name];
488 $result[] = $default_value;
495 * Hide any fields that are empty
497 function drush_hide_empty_fields($input, $fields) {
499 foreach ($input as $key => $data) {
500 foreach ($fields as $field => $label) {
501 if (isset($data[$field]) && !empty($data[$field])) {
502 $has_data[$field] = TRUE;
506 foreach ($fields as $field => $label) {
507 if (!isset($has_data[$field])) {
508 unset($fields[$field]);
515 * Convert an array of data rows, where each row contains an
516 * associative array of key : value pairs, into
517 * a table suitable for processing by drush_print_table.
518 * The provided $header determines the order that the items
519 * will appear in the output. Only data items listed in the
520 * header will be placed in the output.
522 * @param $rows_of_keyvalue_table
524 * 'row1' => array('col1' => 'data', 'col2' => 'data'),
525 * 'row2' => array('col1' => 'data', 'col2' => 'data'),
528 * array('col1' => 'Column 1 Label', 'col2' => 'Column 2 Label')
530 * (optional) An array of special options, all optional:
531 * - strip-tags: call the strip_tags function on the data
532 * before placing it in the table
533 * - concatenate-columns: an array of:
534 * - dest-col: array('src-col1', 'src-col2')
535 * Appends all of the src columns with whatever is
536 * in the destination column. Appended columns are
537 * separated by newlines.
538 * - transform-columns: an array of:
539 * - dest-col: array('from' => 'to'),
540 * Transforms any occurance of 'from' in 'dest-col' to 'to'.
541 * - format-cell: Drush output format name to use to format
542 * any cell that is an array.
543 * - process-cell: php function name to use to process
544 * any cell that is an array.
545 * - field-mappings: an array whose keys are some or all of the keys in
546 * $header and whose values are the corresponding keys to use when
547 * indexing the values of $rows_of_keyvalue_table.
551 function drush_rows_of_key_value_to_array_table($rows_of_keyvalue_table, $header, $metadata) {
552 if (isset($metadata['hide-empty-fields'])) {
553 $header = drush_hide_empty_fields($rows_of_keyvalue_table, $header);
555 if (empty($header)) {
556 $first = (array)reset($rows_of_keyvalue_table);
557 $keys = array_keys($first);
558 $header = array_combine($keys, $keys);
560 $table = array(array_values($header));
561 if (isset($rows_of_keyvalue_table) && is_array($rows_of_keyvalue_table) && !empty($rows_of_keyvalue_table)) {
562 foreach ($rows_of_keyvalue_table as $row_index => $row_data) {
563 $row_data = (array)$row_data;
565 foreach ($header as $column_key => $column_label) {
566 $data = drush_lookup_field_by_path($row_data, $column_key, $metadata['field-mappings']);
567 if (array_key_exists('transform-columns', $metadata)) {
568 foreach ($metadata['transform-columns'] as $dest_col => $transformations) {
569 if ($dest_col == $column_key) {
570 $data = str_replace(array_keys($transformations), array_values($transformations), $data);
574 if (array_key_exists('concatenate-columns', $metadata)) {
575 foreach ($metadata['concatenate-columns'] as $dest_col => $src_cols) {
576 if ($dest_col == $column_key) {
578 if (!is_array($src_cols)) {
579 $src_cols = array($src_cols);
581 foreach($src_cols as $src) {
582 if (array_key_exists($src, $row_data) && !empty($row_data[$src])) {
586 $data .= $row_data[$src];
592 if (array_key_exists('format-cell', $metadata) && is_array($data)) {
593 $data = drush_format($data, array(), $metadata['format-cell']);
595 if (array_key_exists('process-cell', $metadata) && is_array($data)) {
596 $data = $metadata['process-cell']($data, $metadata);
598 if (array_key_exists('strip-tags', $metadata)) {
599 $data = strip_tags($data);
610 * Determine the best fit for column widths.
613 * The rows to use for calculations.
615 * Manually specified widths of each column (in characters) - these will be
618 function drush_table_column_autowidth($rows, $widths) {
619 $auto_widths = $widths;
621 // First we determine the distribution of row lengths in each column.
622 // This is an array of descending character length keys (i.e. starting at
623 // the rightmost character column), with the value indicating the number
624 // of rows where that character column is present.
626 foreach ($rows as $rowkey => $row) {
627 foreach ($row as $col_id => $cell) {
628 if (empty($widths[$col_id])) {
629 $length = strlen($cell);
631 $col_dist[$col_id][0] = 0;
633 while ($length > 0) {
634 if (!isset($col_dist[$col_id][$length])) {
635 $col_dist[$col_id][$length] = 0;
637 $col_dist[$col_id][$length]++;
643 foreach ($col_dist as $col_id => $count) {
644 // Sort the distribution in decending key order.
645 krsort($col_dist[$col_id]);
646 // Initially we set all columns to their "ideal" longest width
647 // - i.e. the width of their longest column.
648 $auto_widths[$col_id] = max(array_keys($col_dist[$col_id]));
651 // We determine what width we have available to use, and what width the
652 // above "ideal" columns take up.
653 $available_width = drush_get_context('DRUSH_COLUMNS', 80) - (count($auto_widths) * 2);
654 $auto_width_current = array_sum($auto_widths);
656 // If we need to reduce a column so that we can fit the space we use this
657 // loop to figure out which column will cause the "least wrapping",
658 // (relative to the other columns) and reduce the width of that column.
659 while ($auto_width_current > $available_width) {
662 foreach ($col_dist as $col_id => $counts) {
663 // If we are just starting out, select the first column.
665 // OR: if this column would cause less wrapping than the currently
666 // selected column, then select it.
667 (current($counts) < $count) ||
668 // OR: if this column would cause the same amount of wrapping, but is
669 // longer, then we choose to wrap the longer column (proportionally
670 // less wrapping, and helps avoid triple line wraps).
671 (current($counts) == $count && key($counts) > $width)) {
672 // Select the column number, and record the count and current width
673 // for later comparisons.
675 $count = current($counts);
676 $width = key($counts);
680 // If we have reached a width of 1 then give up, so wordwrap can still progress.
683 // Reduce the width of the selected column.
684 $auto_widths[$column]--;
685 // Reduce our overall table width counter.
686 $auto_width_current--;
687 // Remove the corresponding data from the disctribution, so next time
688 // around we use the data for the row to the left.
689 unset($col_dist[$column][$width]);
695 * Print the contents of a file.
697 * @param string $file
698 * Full path to a file.
700 function drush_print_file($file) {
701 // Don't even bother to print the file in --no mode
702 if (drush_get_context('DRUSH_NEGATIVE')) {
705 if ((substr($file,-4) == ".htm") || (substr($file,-5) == ".html")) {
706 $tmp_file = drush_tempnam(basename($file));
707 file_put_contents($tmp_file, drush_html_to_text(file_get_contents($file)));
710 // Do not wait for user input in --yes or --pipe modes
711 if (drush_get_context('DRUSH_PIPE')) {
712 drush_print_pipe(file_get_contents($file));
714 elseif (drush_get_context('DRUSH_AFFIRMATIVE')) {
715 drush_print(file_get_contents($file));
717 elseif (drush_shell_exec_interactive("less %s", $file)) {
720 elseif (drush_shell_exec_interactive("more %s", $file)) {
724 drush_print(file_get_contents($file));
730 * Converts a PHP variable into its Javascript equivalent.
732 * We provide a copy of D7's drupal_json_encode since this function is
733 * unavailable on earlier versions of Drupal.
735 * @see drupal_json_decode()
736 * @ingroup php_wrappers
738 function drush_json_encode($var) {
739 if (version_compare(phpversion(), '5.4.0', '>=')) {
740 $json = json_encode($var, JSON_PRETTY_PRINT);
743 $json = json_encode($var);
745 // json_encode() does not escape <, > and &, so we do it with str_replace().
746 return str_replace(array('<', '>', '&'), array('\u003c', '\u003e', '\u0026'), $json);
750 * Converts an HTML-safe JSON string into its PHP equivalent.
752 * We provide a copy of D7's drupal_json_decode since this function is
753 * unavailable on earlier versions of Drupal.
755 * @see drupal_json_encode()
756 * @ingroup php_wrappers
758 function drush_json_decode($var) {
759 return json_decode($var, TRUE);
763 * Drupal-friendly var_export(). Taken from utility.inc in Drupal 8.
766 * The variable to export.
768 * A prefix that will be added at the beginning of every lines of the output.
771 * The variable exported in a way compatible to Drupal's coding standards.
773 function drush_var_export($var, $prefix = '') {
774 if (is_array($var)) {
779 $output = "array(\n";
780 // Don't export keys if the array is non associative.
781 $export_keys = array_values($var) != $var;
782 foreach ($var as $key => $value) {
783 $output .= ' ' . ($export_keys ? drush_var_export($key) . ' => ' : '') . drush_var_export($value, ' ', FALSE) . ",\n";
788 elseif (is_bool($var)) {
789 $output = $var ? 'TRUE' : 'FALSE';
791 elseif (is_string($var)) {
792 $line_safe_var = str_replace("\n", '\n', $var);
793 if (strpos($var, "\n") !== FALSE || strpos($var, "'") !== FALSE) {
794 // If the string contains a line break or a single quote, use the
795 // double quote export mode. Encode backslash and double quotes and
796 // transform some common control characters.
797 $var = str_replace(array('\\', '"', "\n", "\r", "\t"), array('\\\\', '\"', '\n', '\r', '\t'), $var);
798 $output = '"' . $var . '"';
801 $output = "'" . $var . "'";
804 elseif (is_object($var) && get_class($var) === 'stdClass') {
805 // var_export() will export stdClass objects using an undefined
806 // magic method __set_state() leaving the export broken. This
807 // workaround avoids this by casting the object as an array for
808 // export and casting it back to an object when evaluated.
809 $output = '(object) ' . drush_var_export((array) $var, $prefix);
812 $output = var_export($var, TRUE);
816 $output = str_replace("\n", "\n$prefix", $output);
823 * @} End of "defgroup outputfunctions".