$metadata);
}
if (!is_array($metadata)) {
$metadata = array();
}
$metadata += array(
'metameta' => array(),
);
if (isset($metadata['format'])) {
// If the format is set in metadata, then it will
// override whatever is passed in via the $format parameter.
$format = $metadata['format'];
}
if (!isset($format)) {
// TODO: we shouldn't ever call drush_get_option here.
// Get rid of this once we confirm that there are no
// callers that still need it.
$format = drush_get_option('format', 'print-r');
}
$formatter = drush_load_engine('outputformat', $format);
if ($formatter) {
if ($formatter === TRUE) {
return drush_set_error(dt('No outputformat class defined for !format', array('!format' => $format)));
}
$output = $formatter->process($input, $metadata);
}
return $output;
}
/**
* Rudimentary replacement for Drupal API t() function.
*
* @param string
* String to process, possibly with replacement item.
* @param array
* An associative array of replacement items.
*
* @return
* The processed string.
*
* @see t()
*/
function dt($string, $args = array()) {
$output = NULL;
if (function_exists('t') && drush_drupal_major_version() == 7) {
$output = t($string, $args);
}
// The language system requires a working container which has the string
// translation service.
else if (drush_drupal_major_version() >= 8 && \Drupal::hasService('string_translation')) {
// Drupal 8 removes !var replacements, creating a user-level error when
// these are used, so we'll pre-replace these before calling translate().
list($string, $args) = replace_legacy_dt_args($string, $args);
$output = (string) \Drupal::translation()->translate($string, $args);
}
else if (function_exists('t') && drush_drupal_major_version() <= 7 && function_exists('theme')) {
$output = t($string, $args);
}
// If Drupal's t() function unavailable.
if (!isset($output)) {
if (!empty($args)) {
$output = strtr($string, $args);
}
else {
$output = $string;
}
}
return $output;
}
/**
* Replace placeholders that begin with a '!' with '@'.
*/
function replace_legacy_dt_args(&$string, &$legacy_args) {
$args = array();
$replace = array();
foreach ($legacy_args as $name => $argument) {
if ($name[0] == '!') {
$new_arg = '@' . substr($name, 1);
$replace[$name] = $new_arg;
$args[$new_arg] = Markup::create($argument);
}
else {
$args[$name] = $argument;
}
}
return [
strtr($string, $replace),
$args
];
}
/**
* Convert html to readable text. Compatible API to
* drupal_html_to_text, but less functional. Caller
* might prefer to call drupal_html_to_text if there
* is a bootstrapped Drupal site available.
*
* @param string $html
* The html text to convert.
*
* @return string
* The plain-text representation of the input.
*/
function drush_html_to_text($html, $allowed_tags = NULL) {
$replacements = array(
'
' => '------------------------------------------------------------------------------',
'' => ' * ',
'' => '===== ',
'
' => ' =====',
'' => '---- ',
'
' => ' ----',
'' => '::: ',
'
' => ' :::',
'
' => "\n",
);
$text = str_replace(array_keys($replacements), array_values($replacements), $html);
return html_entity_decode(preg_replace('/ *<[^>]*> */', ' ', $text));
}
/**
* Print a formatted table.
*
* @param $rows
* The rows to print.
* @param $header
* If TRUE, the first line will be treated as table header.
* @param $widths
* An associative array whose keys are column IDs and values are widths of each column (in characters).
* If not specified this will be determined automatically, based on a "best fit" algorithm.
* @param $handle
* File handle to write to. NULL will write
* to standard output, STDERR will write to the standard
* error. See http://php.net/manual/en/features.commandline.io-streams.php
* @return $tbl
* Use $tbl->getTable() to get the output from the return value.
*/
function drush_print_table($rows, $header = FALSE, $widths = array(), $handle = NULL) {
$tbl = _drush_format_table($rows, $header, $widths);
$output = $tbl->getTable();
if (!stristr(PHP_OS, 'WIN')) {
$output = str_replace("\r\n", PHP_EOL, $output);
}
drush_print(rtrim($output), 0, $handle);
return $tbl;
}
/**
* Format a table of data.
*
* @param $rows
* The rows to print.
* @param $header
* If TRUE, the first line will be treated as table header.
* @param $widths
* An associative array whose keys are column IDs and values are widths of each column (in characters).
* If not specified this will be determined automatically, based on a "best fit" algorithm.
* @param array $console_table_options
* An array that is passed along when constructing a Console_Table instance.
* @return $output
* The formatted output.
*/
function drush_format_table($rows, $header = FALSE, $widths = array(), $console_table_options = array()) {
$tbl = _drush_format_table($rows, $header, $widths, $console_table_options);
$output = $tbl->getTable();
if (!drush_is_windows()) {
$output = str_replace("\r\n", PHP_EOL, $output);
}
return $output;
}
function _drush_format_table($rows, $header = FALSE, $widths = array(), $console_table_options = array()) {
// Add defaults.
$tbl = new ReflectionClass('Console_Table');
$console_table_options += array(CONSOLE_TABLE_ALIGN_LEFT , '');
$tbl = $tbl->newInstanceArgs($console_table_options);
$auto_widths = drush_table_column_autowidth($rows, $widths);
// Do wordwrap on all cells.
$newrows = array();
foreach ($rows as $rowkey => $row) {
foreach ($row as $col_num => $cell) {
$newrows[$rowkey][$col_num] = wordwrap($cell, $auto_widths[$col_num], "\n", TRUE);
if (isset($widths[$col_num])) {
$newrows[$rowkey][$col_num] = str_pad($newrows[$rowkey][$col_num], $widths[$col_num]);
}
}
}
if ($header) {
$headers = array_shift($newrows);
$tbl->setHeaders($headers);
}
$tbl->addData($newrows);
return $tbl;
}
/**
* Convert an associative array of key : value pairs into
* a table suitable for processing by drush_print_table.
*
* @param $keyvalue_table
* An associative array of key : value pairs.
* @param $metadata
* 'key-value-item': If the value is an array, then
* the item key determines which item from the value
* will appear in the output.
* @return
* An array of arrays, where the keys from the input
* array are stored in the first column, and the values
* are stored in the third. A second colum is created
* specifically to hold the ':' separator.
*/
function drush_key_value_to_array_table($keyvalue_table, $metadata = array()) {
if (!is_array($keyvalue_table)) {
return drush_set_error('DRUSH_INVALID_FORMAT', dt("Data not compatible with selected key-value output format."));
}
if (!is_array($metadata)) {
$metadata = array('key-value-item' => $metadata);
}
$item_key = array_key_exists('key-value-item', $metadata) ? $metadata['key-value-item'] : NULL;
$metadata += array(
'format' => 'list',
'separator' => ' ',
);
$table = array();
foreach ($keyvalue_table as $key => $value) {
if (isset($value)) {
if (is_array($value) && isset($item_key)) {
$value = $value[$item_key];
}
// We should only have simple values here, but if
// we don't, use drush_format() to flatten as a fallback.
if (is_array($value)) {
$value = drush_format($value, $metadata, 'list');
}
}
if (isset($metadata['include-field-labels']) && !$metadata['include-field-labels']) {
$table[] = array(isset($value) ? $value : '');
}
elseif (isset($value)) {
$table[] = array($key, ' :', $value);
}
else {
$table[] = array($key . ':', '', '');
}
}
return $table;
}
/**
* Select the fields that should be used.
*/
function drush_select_fields($all_field_labels, $fields, $strict = TRUE) {
$field_labels = array();
foreach ($fields as $field) {
if (array_key_exists($field, $all_field_labels)) {
$field_labels[$field] = $all_field_labels[$field];
}
else {
// Allow the user to select fields via their human-readable names.
// This is less convenient than the field name (since the human-readable
// names may contain spaces, and must therefore be quoted), but these are
// the values that the user sees in the command output. n.b. the help
// text lists fields by their more convenient machine names.
$key = array_search(strtolower($field), array_map('strtolower', $all_field_labels));
if ($key !== FALSE) {
$field_labels[$key] = $all_field_labels[$key];
}
elseif (!$strict) {
$field_labels[$field] = $field;
}
}
}
return $field_labels;
}
/**
* Select the fields from the input array that should be output.
*
* @param $input
* An associative array of key:value pairs to be output
* @param $fields
* An associative array that maps FROM a field in $input
* TO the corresponding field name in $output.
* @param $mapping
* An associative array that maps FROM a field in $fields
* TO the actual field in $input to use in the preceeding
* translation described above.
* @return
* The input array, re-ordered and re-keyed per $fields
*/
function drush_select_output_fields($input, $fields, $mapping = array(), $default_value = NULL) {
$result = array();
if (empty($fields)) {
$result = $input;
}
else {
foreach ($fields as $key => $label) {
$value = drush_lookup_field_by_path($input, $key, $mapping, $default_value);
if (isset($value)) {
$result[$label] = $value;
}
}
}
return $result;
}
/**
* Return a specific item inside an array, as identified
* by the provided path.
*
* @param $input:
* An array of items, potentially multiple layers deep.
* @param $path:
* A specifier of array keys, either in an array or separated by
* a '/', that list the elements of the array to access. This
* works much like a very simple version of xpath for arrays, with
* all items being treated identically (like elements).
* @param $mapping:
* (optional) An array whose keys may correspond to the $path parameter and
* whose values are the corresponding paths to be used in $input.
*
* Example 1:
*
* $input = array('#name' => 'site.dev', '#id' => '222');
* $path = '#name';
* result: 'site.dev';
*
* Example 2:
*
* $input = array('ca' => array('sf' => array('mission'=>array('1700'=>'woodward'))));
* $path = 'ca/sf/mission/1701';
* result: 'woodward'
*
* Example 3:
*
* $input = array('#name' => 'site.dev', '#id' => '222');
* $path = 'name';
* $mapping = array('name' => '#name');
* result: 'site.dev';
*/
function drush_lookup_field_by_path($input, $path, $mapping = array(), $default_value = NULL) {
$result = '';
if (isset($mapping[$path])) {
$path = $mapping[$path];
}
if (!is_array($path)) {
$parts = explode('/', $path);
}
if (!empty($parts)) {
$result = $input;
foreach ($parts as $key) {
if ((is_array($result)) && (isset($result[$key]))) {
$result = $result[$key];
}
else {
return $default_value;
}
}
}
return $result;
}
/**
* Given a table array (an associative array of associative arrays),
* return an array of all of the values with the specified field name.
*/
function drush_output_get_selected_field($input, $field_name, $default_value = '') {
$result = array();
foreach ($input as $key => $data) {
if (is_array($data) && isset($data[$field_name])) {
$result[] = $data[$field_name];
}
else {
$result[] = $default_value;
}
}
return $result;
}
/**
* Hide any fields that are empty
*/
function drush_hide_empty_fields($input, $fields) {
$has_data = array();
foreach ($input as $key => $data) {
foreach ($fields as $field => $label) {
if (isset($data[$field]) && !empty($data[$field])) {
$has_data[$field] = TRUE;
}
}
}
foreach ($fields as $field => $label) {
if (!isset($has_data[$field])) {
unset($fields[$field]);
}
}
return $fields;
}
/**
* Convert an array of data rows, where each row contains an
* associative array of key : value pairs, into
* a table suitable for processing by drush_print_table.
* The provided $header determines the order that the items
* will appear in the output. Only data items listed in the
* header will be placed in the output.
*
* @param $rows_of_keyvalue_table
* array(
* 'row1' => array('col1' => 'data', 'col2' => 'data'),
* 'row2' => array('col1' => 'data', 'col2' => 'data'),
* )
* @param $header
* array('col1' => 'Column 1 Label', 'col2' => 'Column 2 Label')
* @param $metadata
* (optional) An array of special options, all optional:
* - strip-tags: call the strip_tags function on the data
* before placing it in the table
* - concatenate-columns: an array of:
* - dest-col: array('src-col1', 'src-col2')
* Appends all of the src columns with whatever is
* in the destination column. Appended columns are
* separated by newlines.
* - transform-columns: an array of:
* - dest-col: array('from' => 'to'),
* Transforms any occurance of 'from' in 'dest-col' to 'to'.
* - format-cell: Drush output format name to use to format
* any cell that is an array.
* - process-cell: php function name to use to process
* any cell that is an array.
* - field-mappings: an array whose keys are some or all of the keys in
* $header and whose values are the corresponding keys to use when
* indexing the values of $rows_of_keyvalue_table.
* @return
* An array of arrays
*/
function drush_rows_of_key_value_to_array_table($rows_of_keyvalue_table, $header, $metadata) {
if (isset($metadata['hide-empty-fields'])) {
$header = drush_hide_empty_fields($rows_of_keyvalue_table, $header);
}
if (empty($header)) {
$first = (array)reset($rows_of_keyvalue_table);
$keys = array_keys($first);
$header = array_combine($keys, $keys);
}
$table = array(array_values($header));
if (isset($rows_of_keyvalue_table) && is_array($rows_of_keyvalue_table) && !empty($rows_of_keyvalue_table)) {
foreach ($rows_of_keyvalue_table as $row_index => $row_data) {
$row_data = (array)$row_data;
$row = array();
foreach ($header as $column_key => $column_label) {
$data = drush_lookup_field_by_path($row_data, $column_key, $metadata['field-mappings']);
if (array_key_exists('transform-columns', $metadata)) {
foreach ($metadata['transform-columns'] as $dest_col => $transformations) {
if ($dest_col == $column_key) {
$data = str_replace(array_keys($transformations), array_values($transformations), $data);
}
}
}
if (array_key_exists('concatenate-columns', $metadata)) {
foreach ($metadata['concatenate-columns'] as $dest_col => $src_cols) {
if ($dest_col == $column_key) {
$data = '';
if (!is_array($src_cols)) {
$src_cols = array($src_cols);
}
foreach($src_cols as $src) {
if (array_key_exists($src, $row_data) && !empty($row_data[$src])) {
if (!empty($data)) {
$data .= "\n";
}
$data .= $row_data[$src];
}
}
}
}
}
if (array_key_exists('format-cell', $metadata) && is_array($data)) {
$data = drush_format($data, array(), $metadata['format-cell']);
}
if (array_key_exists('process-cell', $metadata) && is_array($data)) {
$data = $metadata['process-cell']($data, $metadata);
}
if (array_key_exists('strip-tags', $metadata)) {
$data = strip_tags($data);
}
$row[] = $data;
}
$table[] = $row;
}
}
return $table;
}
/**
* Determine the best fit for column widths.
*
* @param $rows
* The rows to use for calculations.
* @param $widths
* Manually specified widths of each column (in characters) - these will be
* left as is.
*/
function drush_table_column_autowidth($rows, $widths) {
$auto_widths = $widths;
// First we determine the distribution of row lengths in each column.
// This is an array of descending character length keys (i.e. starting at
// the rightmost character column), with the value indicating the number
// of rows where that character column is present.
$col_dist = array();
foreach ($rows as $rowkey => $row) {
foreach ($row as $col_id => $cell) {
if (empty($widths[$col_id])) {
$length = strlen($cell);
if ($length == 0) {
$col_dist[$col_id][0] = 0;
}
while ($length > 0) {
if (!isset($col_dist[$col_id][$length])) {
$col_dist[$col_id][$length] = 0;
}
$col_dist[$col_id][$length]++;
$length--;
}
}
}
}
foreach ($col_dist as $col_id => $count) {
// Sort the distribution in decending key order.
krsort($col_dist[$col_id]);
// Initially we set all columns to their "ideal" longest width
// - i.e. the width of their longest column.
$auto_widths[$col_id] = max(array_keys($col_dist[$col_id]));
}
// We determine what width we have available to use, and what width the
// above "ideal" columns take up.
$available_width = drush_get_context('DRUSH_COLUMNS', 80) - (count($auto_widths) * 2);
$auto_width_current = array_sum($auto_widths);
// If we need to reduce a column so that we can fit the space we use this
// loop to figure out which column will cause the "least wrapping",
// (relative to the other columns) and reduce the width of that column.
while ($auto_width_current > $available_width) {
$count = 0;
$width = 0;
foreach ($col_dist as $col_id => $counts) {
// If we are just starting out, select the first column.
if ($count == 0 ||
// OR: if this column would cause less wrapping than the currently
// selected column, then select it.
(current($counts) < $count) ||
// OR: if this column would cause the same amount of wrapping, but is
// longer, then we choose to wrap the longer column (proportionally
// less wrapping, and helps avoid triple line wraps).
(current($counts) == $count && key($counts) > $width)) {
// Select the column number, and record the count and current width
// for later comparisons.
$column = $col_id;
$count = current($counts);
$width = key($counts);
}
}
if ($width <= 1) {
// If we have reached a width of 1 then give up, so wordwrap can still progress.
break;
}
// Reduce the width of the selected column.
$auto_widths[$column]--;
// Reduce our overall table width counter.
$auto_width_current--;
// Remove the corresponding data from the disctribution, so next time
// around we use the data for the row to the left.
unset($col_dist[$column][$width]);
}
return $auto_widths;
}
/**
* Print the contents of a file.
*
* @param string $file
* Full path to a file.
*/
function drush_print_file($file) {
// Don't even bother to print the file in --no mode
if (drush_get_context('DRUSH_NEGATIVE')) {
return;
}
if ((substr($file,-4) == ".htm") || (substr($file,-5) == ".html")) {
$tmp_file = drush_tempnam(basename($file));
file_put_contents($tmp_file, drush_html_to_text(file_get_contents($file)));
$file = $tmp_file;
}
// Do not wait for user input in --yes or --pipe modes
if (drush_get_context('DRUSH_PIPE')) {
drush_print_pipe(file_get_contents($file));
}
elseif (drush_get_context('DRUSH_AFFIRMATIVE')) {
drush_print(file_get_contents($file));
}
elseif (drush_shell_exec_interactive("less %s", $file)) {
return;
}
elseif (drush_shell_exec_interactive("more %s", $file)) {
return;
}
else {
drush_print(file_get_contents($file));
}
}
/**
* Converts a PHP variable into its Javascript equivalent.
*
* We provide a copy of D7's drupal_json_encode since this function is
* unavailable on earlier versions of Drupal.
*
* @see drupal_json_decode()
* @ingroup php_wrappers
*/
function drush_json_encode($var) {
if (version_compare(phpversion(), '5.4.0', '>=')) {
$json = json_encode($var, JSON_PRETTY_PRINT);
}
else {
$json = json_encode($var);
}
// json_encode() does not escape <, > and &, so we do it with str_replace().
return str_replace(array('<', '>', '&'), array('\u003c', '\u003e', '\u0026'), $json);
}
/**
* Converts an HTML-safe JSON string into its PHP equivalent.
*
* We provide a copy of D7's drupal_json_decode since this function is
* unavailable on earlier versions of Drupal.
*
* @see drupal_json_encode()
* @ingroup php_wrappers
*/
function drush_json_decode($var) {
return json_decode($var, TRUE);
}
/**
* Drupal-friendly var_export(). Taken from utility.inc in Drupal 8.
*
* @param $var
* The variable to export.
* @param $prefix
* A prefix that will be added at the beginning of every lines of the output.
*
* @return
* The variable exported in a way compatible to Drupal's coding standards.
*/
function drush_var_export($var, $prefix = '') {
if (is_array($var)) {
if (empty($var)) {
$output = 'array()';
}
else {
$output = "array(\n";
// Don't export keys if the array is non associative.
$export_keys = array_values($var) != $var;
foreach ($var as $key => $value) {
$output .= ' ' . ($export_keys ? drush_var_export($key) . ' => ' : '') . drush_var_export($value, ' ', FALSE) . ",\n";
}
$output .= ')';
}
}
elseif (is_bool($var)) {
$output = $var ? 'TRUE' : 'FALSE';
}
elseif (is_string($var)) {
$line_safe_var = str_replace("\n", '\n', $var);
if (strpos($var, "\n") !== FALSE || strpos($var, "'") !== FALSE) {
// If the string contains a line break or a single quote, use the
// double quote export mode. Encode backslash and double quotes and
// transform some common control characters.
$var = str_replace(array('\\', '"', "\n", "\r", "\t"), array('\\\\', '\"', '\n', '\r', '\t'), $var);
$output = '"' . $var . '"';
}
else {
$output = "'" . $var . "'";
}
}
elseif (is_object($var) && get_class($var) === 'stdClass') {
// var_export() will export stdClass objects using an undefined
// magic method __set_state() leaving the export broken. This
// workaround avoids this by casting the object as an array for
// export and casting it back to an object when evaluated.
$output = '(object) ' . drush_var_export((array) $var, $prefix);
}
else {
$output = var_export($var, TRUE);
}
if ($prefix) {
$output = str_replace("\n", "\n$prefix", $output);
}
return $output;
}
/**
* @} End of "defgroup outputfunctions".
*/