$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". */