More updates to stop using dev or alpha or beta versions.
[yaffs-website] / vendor / drush / drush / includes / drush.inc
1 <?php
2
3 /**
4  * @file
5  * The drush API implementation and helpers.
6  */
7
8 use Drush\Drush;
9 use Drush\Log\LogLevel;
10 use Drush\Utils\StringUtils;
11 use Psr\Log\LoggerInterface;
12
13 /**
14  * @name Error status definitions
15  * @{
16  * Error code definitions for interpreting the current error status.
17  * @see drush_set_error(), drush_get_error(), drush_get_error_log(), drush_cmp_error()
18  */
19
20 /** The command completed successfully. */
21 define('DRUSH_SUCCESS', 0);
22 /** The command could not be completed because the framework has specified errors that have occured. */
23 define('DRUSH_FRAMEWORK_ERROR', 1);
24 /** The command was aborted because the user chose to cancel it at some prompt.
25 This exit code is arbitrarily the same as EX_TEMPFAIL in sysexits.h, although
26 note that shell error codes are distinct from C exit codes, so this alignment
27 not meaningful. */
28 define('DRUSH_EXITCODE_USER_ABORT', 75);
29 /** The command that was executed resulted in an application error,
30 The most commom causes for this is invalid PHP or a broken SSH
31 pipe when using drush_backend_invoke in a distributed manner. */
32 define('DRUSH_APPLICATION_ERROR', 255);
33
34 /**
35  * @} End of "name Error status defintions".
36  */
37
38 /**
39  * The number of bytes in a kilobyte. Copied from Drupal.
40  */
41 define('DRUSH_KILOBYTE', 1024);
42
43
44 /**
45  * Convert a csv string, or an array of items which
46  * may contain csv strings, into an array of items.
47  *
48  * @param $args
49  *   A simple csv string; e.g. 'a,b,c'
50  *   or a simple list of items; e.g. array('a','b','c')
51  *   or some combination; e.g. array('a,b','c') or array('a,','b,','c,')
52  *
53  * @returns array
54  *   A simple list of items (e.g. array('a','b','c')
55  *
56  * @deprecated Use \Drush\StringUtils::csvToArray
57  */
58 function _convert_csv_to_array($args) {
59   return StringUtils::csvToArray($args);
60 }
61
62 /**
63  * Convert a nested array into a flat array.  Thows away
64  * the array keys, returning only the values.
65  *
66  * @param $args
67  *   An array that may potentially be nested.
68  *   e.g. array('a', array('b', 'c'))
69  *
70  * @returns array
71  *   A simple list of items (e.g. array('a','b','c')
72  */
73 function drush_flatten_array($a) {
74   $result = [];
75   if (!is_array($a)) {
76     return [$a];
77   }
78   foreach ($a as $value) {
79     $result = array_merge($result, drush_flatten_array($value));
80   }
81   return $result;
82 }
83
84 /**
85  * Get the available global options. Used by list/help commands. All other users
86  * should pull options from $application.
87  *
88  * @param boolean $brief
89  *   Return a reduced set of important options. Used by help command.
90  *
91  * @return
92  *   An associative array containing the option definition as the key,
93  *   and a descriptive array for each of the available options.  The array
94  *   elements for each item are:
95  *
96  *     - short-form: The shortcut form for specifying the key on the commandline.
97  *     - propagate: backend invoke will use drush_get_option to propagate this
98  *       option, when set, to any other Drush command that is called.
99  *     - context: The drush context where the value of this item is cached.  Used
100  *       by backend invoke to propagate values set in code.
101  *     - never-post: If TRUE, backend invoke will never POST this item's value
102  *       on STDIN; it will always be sent as a commandline option.
103  *     - never-propagate: If TRUE, backend invoke will never pass this item on
104  *       to the subcommand being executed.
105  *     - local-context-only: Backend invoke will only pass this value on for local calls.
106  *     - merge: For options such as $options['shell-aliases'] that consist of an array
107  *       of items, make a merged array that contains all of the values specified for
108  *       all of the contexts (config files) where the option is defined.  The value is stored in
109  *       the specified 'context', or in a context named after the option itself if the
110  *       context flag is not specified.
111  *       IMPORTANT: When the merge flag is used, the option value must be obtained via
112  *       drush_get_context('option') rather than drush_get_option('option').
113  *     - merge-pathlist: For options such as --include and --config, make a merged list
114  *       of options from all contexts; works like the 'merge' flag, but also handles string
115  *       values separated by the PATH_SEPARATOR.
116  *     - merge-associative: Like 'merge-pathlist', but key values are preserved.
117  *     - propagate-cli-value: Used to tell backend invoke to include the value for
118  *       this item as specified on the cli.  This can either override 'context'
119  *       (e.g., propagate --include from cli value instead of DRUSH_INCLUDE context),
120  *       or for an independent global setting (e.g. --user)
121  *     - description: The help text for this item. displayed by `drush help`.
122  */
123 function drush_get_global_options($brief = FALSE) {
124   $options['root']               = ['short-form' => 'r', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => "Drupal root directory to use.", 'example-value' => 'path'];
125   $options['uri']                = ['short-form' => 'l', 'short-has-arg' => TRUE, 'never-post' => TRUE, 'description' => 'URI of the drupal site to use.', 'example-value' => 'http://example.com:8888'];
126   $options['verbose']            = ['short-form' => 'v', 'context' => 'DRUSH_VERBOSE', 'description' => 'Display extra information about the command.', 'symfony-conflict' => TRUE];
127   $options['debug']              = ['short-form' => 'd', 'context' => 'DRUSH_DEBUG', 'description' => 'Display even more information.'];
128   $options['yes']                = ['short-form' => 'y', 'context' => 'DRUSH_AFFIRMATIVE', 'description' => "Assume 'yes' as answer to all prompts."];
129   $options['no']                 = ['short-form' => 'n', 'context' => 'DRUSH_NEGATIVE', 'description' => "Assume 'no' as answer to all prompts."];
130   $options['help']               = ['short-form' => 'h', 'description' => "This help system."];
131
132   if (!$brief) {
133     $options['simulate']           = ['short-form' => 's', 'context' => 'DRUSH_SIMULATE', 'never-propagate' => TRUE, 'description' => "Simulate all relevant actions (don't actually change the system).", 'symfony-conflict' => TRUE];
134     $options['pipe']               = ['short-form' => 'p', 'hidden' => TRUE, 'description' => "Emit a compact representation of the command for scripting."];
135     $options['php']                = ['description' => "The absolute path to your PHP interpreter, if not 'php' in the path.", 'example-value' => '/path/to/file', 'never-propagate' => TRUE];
136     $options['interactive']        = ['short-form' => 'ia', 'description' => "Force interactive mode for commands run on multiple targets (e.g. `drush @site1,@site2 cc --ia`).", 'never-propagate' => TRUE];
137     $options['tty']                = ['hidden' => TRUE, 'description' => "Force allocation of tty for remote commands", 'never-propagate' => TRUE];
138     $options['quiet']               = ['short-form' => 'q', 'description' => 'Suppress non-error messages.'];
139     $options['include']             = ['short-form' => 'i', 'short-has-arg' => TRUE, 'context' => 'DRUSH_INCLUDE', 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'merge-pathlist' => TRUE, 'description' => "A list of additional directory paths to search for Drush commands. Commandfiles should be placed in a subfolder called 'Commands'.", 'example-value' => '/path/dir'];
140     $options['exclude']             = ['propagate-cli-value' => TRUE, 'never-post' => TRUE, 'merge-pathlist' => TRUE, 'description' => "A list of files and directory paths to exclude from consideration when searching for drush commandfiles.", 'example-value' => '/path/dir'];
141     $options['config']              = ['short-form' => 'c', 'short-has-arg' => TRUE, 'context' => 'DRUSH_CONFIG', 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'merge-pathlist' => TRUE, 'description' => "Specify an additional config file to load. See example.drush.yml", 'example-value' => '/path/file'];
142     $options['backend']             = ['short-form' => 'b', 'never-propagate' => TRUE, 'description' => "Hide all output and return structured data."];
143     $options['choice']              = ['description' => "Provide an answer to a multiple-choice prompt.", 'example-value' => 'number'];
144     $options['search-depth']        = ['description' => "Control the depth that drush will search for alias files.", 'example-value' => 'number'];
145     $options['ignored-modules']     = ['description' => "Exclude some modules from consideration when searching for drush command files.", 'example-value' => 'token,views'];
146     $options['no-label']            = ['description' => "Remove the site label that drush includes in multi-site command output (e.g. `drush @site1,@site2 status`)."];
147     $options['label-separator']     = ['description' => "Specify the separator to use in multi-site command output (e.g. `drush @sites pm-list --label-separator=',' --format=csv`).", 'example-value' => ','];
148     $options['show-invoke']         = ['description' => "Show all function names which could have been called for the current command. See drush_invoke()."];
149     $options['cache-default-class'] = ['description' => "A cache backend class that implements CacheInterface. Defaults to JSONCache.", 'example-value' => 'JSONCache'];
150     $options['cache-class-<bin>']   = ['description' => "A cache backend class that implements CacheInterface to use for a specific cache bin.", 'example-value' => 'className'];
151     $options['early']               = ['description' => "Include a file (with relative or full path) and call the drush_early_hook() function (where 'hook' is the filename)"];
152     $options['alias-path']          = ['context' => 'ALIAS_PATH', 'local-context-only' => TRUE, 'merge-pathlist' => TRUE, 'propagate-cli-value' => TRUE, 'description' => "Specifies the list of paths where drush will search for alias files.", 'example-value' => '/path/alias1:/path/alias2'];
153     $options['confirm-rollback']    = ['description' => 'Wait for confirmation before doing a rollback when something goes wrong.'];
154     $options['php-options']         = ['hidden' => TRUE, 'description' => "Options to pass to `php` when running drush.  Only effective when specified in a site alias definition.", 'never-propagate' => TRUE, 'example-value' => '-d error_reporting="E_ALL"'];
155     $options['halt-on-error']       = ['propagate-cli-value' => TRUE, 'description' => "Manage recoverable errors. Values: 1=Execution halted. 0=Execution continues."];
156     $options['remote-host']         = ['hidden' => TRUE, 'description' => 'Remote site to execute drush command on. Managed by site alias.', 'example-value' => 'http://example.com'];
157     $options['remote-user']         = ['hidden' => TRUE, 'description' => 'User account to use with a remote drush command. Managed by site alias.', 'example-value' => 'www-data'];
158     $options['remote-os']           = ['hidden' => TRUE, 'description' => 'The operating system used on the remote host. Managed by site alias.', 'example-value' => 'linux'];
159     $options['site-list']           = ['hidden' => TRUE, 'description' => 'List of sites to run commands on. Managed by site alias.', 'example-value' => '@site1,@site2'];
160     $options['reserve-margin']      = ['hidden' => TRUE, 'description' => 'Remove columns from formatted opions. Managed by multi-site command handling.', 'example-value' => 'number'];
161     $options['strict']              = ['propagate' => TRUE, 'description' => 'Return an error on unrecognized options. --strict=0: Allow unrecognized options.'];
162     $options['command-specific']    = ['hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'Command-specific options.'];
163     $options['site-aliases']        = ['hidden' => TRUE, 'merge-associative' => TRUE, 'description' => 'List of site aliases.'];
164     $options['shell-aliases']       = ['hidden' => TRUE, 'merge' => TRUE, 'never-propagate' => TRUE, 'description' => 'List of shell aliases.'];
165     $options['path-aliases']        = ['hidden' => TRUE, 'never-propagate' => TRUE, 'description' => 'Path aliases from site alias.'];
166     $options['ssh-options']         = ['never-propagate' => TRUE, 'description' => 'A string of extra options that will be passed to the ssh command', 'example-value' => '-p 100'];
167     $options['drush-coverage']      = ['hidden' => TRUE, 'never-post' => TRUE, 'propagate-cli-value' => TRUE, 'description' => 'File to save code coverage data into.'];
168     $options['local']               = ['propagate' => TRUE, 'description' => 'Don\'t look in global locations for commandfiles, config, and site aliases'];
169   }
170   return $options;
171 }
172
173 /**
174  * Calls a given function, passing through all arguments unchanged.
175  *
176  * This should be used when calling possibly mutative or destructive functions
177  * (e.g. unlink() and other file system functions) so that can be suppressed
178  * if the simulation mode is enabled.
179  *
180  * Important:  Call @see drush_op_system() to execute a shell command,
181  * or @see drush_shell_exec() to execute a shell command and capture the
182  * shell output.
183  *
184  * @param $callable
185  *   The name of the function. Any additional arguments are passed along.
186  * @return
187  *   The return value of the function, or TRUE if simulation mode is enabled.
188  *
189  */
190 function drush_op($callable) {
191   $args_printed = [];
192   $args = func_get_args();
193   array_shift($args); // Skip function name
194   foreach ($args as $arg) {
195     $args_printed[] = is_scalar($arg) ? $arg : (is_array($arg) ? 'Array' : 'Object');
196   }
197
198   if (!is_array($callable)) {
199     $callable_string = $callable;
200   }
201   else {
202     if (is_object($callable[0])) {
203       $callable_string = get_class($callable[0]) . '::' . $callable[1];
204     }
205     else {
206       $callable_string = implode('::', $callable);
207     }
208   }
209
210   // Special checking for drush_op('system')
211   if ($callable == 'system') {
212     drush_log(dt("Do not call drush_op('system'); use drush_op_system instead"), LogLevel::DEBUG);
213   }
214
215   if (\Drush\Drush::verbose() || \Drush\Drush::simulate()) {
216     drush_log(sprintf("Calling %s(%s)", $callable_string, implode(", ", $args_printed)), LogLevel::DEBUG);
217   }
218
219   if (\Drush\Drush::simulate()) {
220     return TRUE;
221   }
222
223   return drush_call_user_func_array($callable, $args);
224 }
225
226 /**
227  * Mimic cufa but still call function directly. See http://drupal.org/node/329012#comment-1260752
228  */
229 function drush_call_user_func_array($function, $args = []) {
230   if (is_array($function)) {
231     // $callable is a method so always use CUFA.
232     return call_user_func_array($function, $args);
233   }
234
235   switch (count($args)) {
236     case 0: return $function(); break;
237     case 1: return $function($args[0]); break;
238     case 2: return $function($args[0], $args[1]); break;
239     case 3: return $function($args[0], $args[1], $args[2]); break;
240     case 4: return $function($args[0], $args[1], $args[2], $args[3]); break;
241     case 5: return $function($args[0], $args[1], $args[2], $args[3], $args[4]); break;
242     case 6: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]); break;
243     case 7: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6]); break;
244     case 8: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7]); break;
245     case 9: return $function($args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], $args[8]); break;
246     default: return call_user_func_array($function,$args);
247   }
248 }
249
250 /**
251  * Determines the MIME content type of the specified file.
252  *
253  * The power of this function depends on whether the PHP installation
254  * has either mime_content_type() or finfo installed -- if not, only tar,
255  * gz, zip and bzip2 types can be detected.
256  *
257  * If mime type can't be obtained, an error will be set.
258  *
259  * @return mixed
260  *   The MIME content type of the file or FALSE.
261  */
262 function drush_mime_content_type($filename) {
263   $content_type = drush_attempt_mime_content_type($filename);
264   if ($content_type) {
265     drush_log(dt('Mime type for !file is !mt', ['!file' => $filename, '!mt' => $content_type]), LogLevel::INFO);
266     return $content_type;
267   }
268   return drush_set_error('MIME_CONTENT_TYPE_UNKNOWN', dt('Unable to determine mime type for !file.', ['!file' => $filename]));
269 }
270
271 /**
272  * Works like drush_mime_content_type, but does not set an error
273  * if the type is unknown.
274  */
275 function drush_attempt_mime_content_type($filename) {
276   $content_type = FALSE;
277   if (class_exists('finfo')) {
278     $finfo = new finfo(FILEINFO_MIME_TYPE);
279     $content_type = $finfo->file($filename);
280     if ($content_type == 'application/octet-stream') {
281       drush_log(dt('Mime type for !file is application/octet-stream.', ['!file' => $filename]), LogLevel::DEBUG);
282       $content_type = FALSE;
283     }
284   }
285   // If apache is configured in such a way that all files are considered
286   // octet-stream (e.g with mod_mime_magic and an http conf that's serving all
287   // archives as octet-stream for other reasons) we'll detect mime types on our
288   //  own by examing the file's magic header bytes.
289   if (!$content_type) {
290     drush_log(dt('Examining !file headers.', ['!file' => $filename]), LogLevel::DEBUG);
291     if ($file = fopen($filename, 'rb')) {
292       $first = fread($file, 2);
293       fclose($file);
294
295       if ($first !== FALSE) {
296         // Interpret the two bytes as a little endian 16-bit unsigned int.
297         $data = unpack('v', $first);
298         switch ($data[1]) {
299           case 0x8b1f:
300             // First two bytes of gzip files are 0x1f, 0x8b (little-endian).
301             // See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer
302             $content_type = 'application/x-gzip';
303             break;
304
305           case 0x4b50:
306             // First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian).
307             // See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
308             $content_type = 'application/zip';
309             break;
310
311           case 0x5a42:
312             // First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian).
313             // See http://en.wikipedia.org/wiki/Bzip2#File_format
314             $content_type = 'application/x-bzip2';
315             break;
316
317           default:
318             drush_log(dt('Unable to determine mime type from header bytes 0x!hex of !file.', ['!hex' => dechex($data[1]), '!file' => $filename,]), LogLevel::DEBUG);
319         }
320       }
321       else {
322         drush_log(dt('Unable to read !file.', ['!file' => $filename]), LogLevel::WARNING);
323       }
324     }
325     else {
326       drush_log(dt('Unable to open !file.', ['!file' => $filename]), LogLevel::WARNING);
327     }
328   }
329
330   // 3. Lastly if above methods didn't work, try to guess the mime type from
331   // the file extension. This is useful if the file has no identificable magic
332   // header bytes (for example tarballs).
333   if (!$content_type) {
334     drush_log(dt('Examining !file extension.', ['!file' => $filename]), LogLevel::DEBUG);
335
336     // Remove querystring from the filename, if present.
337     $filename = basename(current(explode('?', $filename, 2)));
338     $extension_mimetype = [
339       '.tar'     => 'application/x-tar',
340       '.sql'     => 'application/octet-stream',
341     ];
342     foreach ($extension_mimetype as $extension => $ct) {
343       if (substr($filename, -strlen($extension)) === $extension) {
344         $content_type = $ct;
345         break;
346       }
347     }
348   }
349   return $content_type;
350 }
351
352 /**
353  * Check whether a file is a supported tarball.
354  *
355  * @return mixed
356  *   The file content type if it's a tarball. FALSE otherwise.
357  */
358 function drush_file_is_tarball($path) {
359   $content_type = drush_attempt_mime_content_type($path);
360   $supported = [
361     'application/x-bzip2',
362     'application/x-gzip',
363     'application/x-tar',
364     'application/x-zip',
365     'application/zip',
366   ];
367   if (in_array($content_type, $supported)) {
368     return $content_type;
369   }
370   return FALSE;
371 }
372
373 /**
374  * @defgroup logging Logging information to be provided as output.
375  * @{
376  *
377  * These functions are primarily for diagnostic purposes, but also provide an overview of tasks that were taken
378  * by drush.
379  */
380
381 /**
382  * Add a log message to the log history.
383  *
384  * This function calls the callback stored in the 'DRUSH_LOG_CALLBACK' context with
385  * the resulting entry at the end of execution.
386  *
387  * This allows you to replace it with custom logging implementations if needed,
388  * such as logging to a file or logging to a database (drupal or otherwise).
389  *
390  * The default callback is the Drush\Log\Logger class with prints the messages
391  * to the shell.
392  *
393  * @param message
394  *   String containing the message to be logged.
395  * @param type
396  *   The type of message to be logged. Common types are 'warning', 'error', 'success' and 'notice'.
397  *   A type of 'ok' or 'success' can also be supplied to flag something that worked.
398  *   If you want your log messages to print to screen without the user entering
399  *   a -v or --verbose flag, use type 'ok' or 'notice', this prints log messages out to
400  *   STDERR, which prints to screen (unless you have redirected it). All other
401  *   types of messages will be assumed to be info.
402  *
403  * @deprecated
404  *   Use this->logger()->warning (for example) from an Annotated command method.
405  */
406 function drush_log($message, $type = LogLevel::INFO, $error = null) {
407   $entry = [
408     'type' => $type,
409     'message' => $message,
410     'timestamp' => microtime(TRUE),
411     'memory' => memory_get_usage(),
412   ];
413   $entry['error'] = $error;
414
415   return _drush_log($entry);
416 }
417
418 /**
419  * Call the default logger, or the user's log callback, as
420  * appropriate.
421  */
422 function _drush_log($entry) {
423   $callback = drush_get_context('DRUSH_LOG_CALLBACK');
424   if (!$callback) {
425     $callback = Drush::logger();
426   }
427   if ($callback instanceof LoggerInterface) {
428     _drush_log_to_logger($callback, $entry);
429   }
430   elseif ($callback) {
431     $log =& drush_get_context('DRUSH_LOG', []);
432     $log[] = $entry;
433     drush_backend_packet('log', $entry);
434     return $callback($entry);
435   }
436 }
437
438 // Maintain compatibility with extensions that hook into
439 // DRUSH_LOG_CALLBACK (e.g. drush_ctex_bonus)
440 function _drush_print_log($entry) {
441   $drush_logger = Drush::logger();
442   if ($drush_logger) {
443     _drush_log_to_logger($drush_logger, $entry);
444   }
445 }
446
447 function _drush_log_to_logger($logger, $entry) {
448     $context = $entry;
449     $log_level = $entry['type'];
450     $message = $entry['message'];
451     unset($entry['type']);
452     unset($entry['message']);
453
454     $logger->log($log_level, $message, $context);
455 }
456
457 function drush_log_has_errors($types = [LogLevel::WARNING, LogLevel::ERROR, LogLevel::FAILED]) {
458   $log =& drush_get_context('DRUSH_LOG', []);
459   foreach ($log as $entry) {
460     if (in_array($entry['type'], $types)) {
461       return TRUE;
462     }
463   }
464   return FALSE;
465 }
466
467 /**
468  * Backend command callback. Add a log message to the log history.
469  *
470  * @param entry
471  *   The log entry.
472  */
473 function drush_backend_packet_log($entry, $backend_options) {
474   if (!$backend_options['log']) {
475     return;
476   }
477   if (!is_string($entry['message'])) {
478     $entry['message'] = implode("\n", (array)$entry['message']);
479   }
480   $entry['message'] = $entry['message'];
481   if (array_key_exists('#output-label', $backend_options)) {
482     $entry['message'] = $backend_options['#output-label'] . $entry['message'];
483   }
484
485   // If integrate is FALSE, then log messages are stored in DRUSH_LOG,
486   // but are -not- printed to the console.
487   if ($backend_options['integrate']) {
488     _drush_log($entry);
489   }
490   else {
491     $log =& drush_get_context('DRUSH_LOG', []);
492     $log[] = $entry;
493     // Yes, this looks odd, but we might in fact be a backend command
494     // that ran another backend command.
495     drush_backend_packet('log', $entry);
496   }
497 }
498
499 /**
500  * Retrieve the log messages from the log history
501  *
502  * @return
503  *   Entire log history
504  */
505 function drush_get_log() {
506   return drush_get_context('DRUSH_LOG', []);
507 }
508
509 /**
510  * Run print_r on a variable and log the output.
511  */
512 function dlm($object) {
513   drush_log(print_r($object, TRUE));
514 }
515
516 // Copy of format_size() in Drupal.
517 function drush_format_size($size) {
518   if ($size < DRUSH_KILOBYTE) {
519     // format_plural() not always available.
520     return dt('@count bytes', ['@count' => $size]);
521   }
522   else {
523     $size = $size / DRUSH_KILOBYTE; // Convert bytes to kilobytes.
524     $units = [
525       dt('@size KB', []),
526       dt('@size MB', []),
527       dt('@size GB', []),
528       dt('@size TB', []),
529       dt('@size PB', []),
530       dt('@size EB', []),
531       dt('@size ZB', []),
532       dt('@size YB', []),
533     ];
534     foreach ($units as $unit) {
535       if (round($size, 2) >= DRUSH_KILOBYTE) {
536         $size = $size / DRUSH_KILOBYTE;
537       }
538       else {
539         break;
540       }
541     }
542     return str_replace('@size', round($size, 2), $unit);
543   }
544 }
545
546 /**
547  * @} End of "defgroup logging".
548  */
549
550 /**
551  * @defgroup errorhandling Managing errors that occur in the Drush framework.
552  * @{
553  * Functions that manage the current error status of the Drush framework.
554  *
555  * These functions operate by maintaining a static variable that is a equal to the constant DRUSH_FRAMEWORK_ERROR if an
556  * error has occurred.
557  * This error code is returned at the end of program execution, and provide the shell or calling application with
558  * more information on how to diagnose any problems that may have occurred.
559  */
560
561 /**
562  * Set an error code for the error handling system.
563  *
564  * @param \Drupal\Component\Render\MarkupInterface|string $error
565  *   A text string identifying the type of error.
566  * @param null|string $message
567  *   Optional. Error message to be logged. If no message is specified,
568  *   hook_drush_help will be consulted, using a key of 'error:MY_ERROR_STRING'.
569  * @param null|string $output_label
570  *   Optional. Label to prepend to the error message.
571  *
572  * @return bool
573  *   Always returns FALSE, to allow returning false in the calling functions,
574  *   such as <code>return drush_set_error('DRUSH_FRAMEWORK_ERROR')</code>.
575  */
576 function drush_set_error($error, $message = null, $output_label = "") {
577   $error_code =& drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
578   $error_code = DRUSH_FRAMEWORK_ERROR;
579
580   $error_log =& drush_get_context('DRUSH_ERROR_LOG', []);
581
582   if (is_numeric($error)) {
583     $error = 'DRUSH_FRAMEWORK_ERROR';
584   }
585   elseif (!is_string($error)) {
586     // Typical case: D8 TranslatableMarkup, implementing MarkupInterface.
587     $error = "$error";
588   }
589
590   $message = ($message) ? $message : ''; // drush_command_invoke_all('drush_help', 'error:' . $error);
591
592   if (is_array($message)) {
593     $message = implode("\n", $message);
594   }
595
596   $error_log[$error][] = $message;
597   if (!drush_backend_packet('set_error', ['error' => $error, 'message' => $message])) {
598     drush_log(($message) ? $output_label . $message : $output_label . $error, LogLevel::ERROR, $error);
599   }
600
601   return FALSE;
602 }
603
604 /**
605  * Return the current error handling status
606  *
607  * @return
608  *   The current aggregate error status
609  */
610 function drush_get_error() {
611   return drush_get_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
612 }
613
614 /**
615  * Return the current list of errors that have occurred.
616  *
617  * @return
618  *   An associative array of error messages indexed by the type of message.
619  */
620 function drush_get_error_log() {
621   return drush_get_context('DRUSH_ERROR_LOG', []);
622 }
623
624 /**
625  * Check if a specific error status has been set.
626  *
627  * @param error
628  *   A text string identifying the error that has occurred.
629  * @return
630  *   TRUE if the specified error has been set, FALSE if not
631  */
632 function drush_cmp_error($error) {
633   $error_log = drush_get_error_log();
634
635   if (is_numeric($error)) {
636     $error = 'DRUSH_FRAMEWORK_ERROR';
637   }
638
639   return array_key_exists($error, $error_log);
640 }
641
642 /**
643  * Clear error context.
644  */
645 function drush_clear_error() {
646   drush_set_context('DRUSH_ERROR_CODE', DRUSH_SUCCESS);
647 }
648
649 /**
650  * Turn PHP error handling off.
651  *
652  * This is commonly used while bootstrapping Drupal for install
653  * or updates.
654  *
655  * This also records the previous error_reporting setting, in
656  * case it wasn't recorded previously.
657  *
658  * @see drush_errors_off()
659  */
660 function drush_errors_off() {
661   drush_get_context('DRUSH_ERROR_REPORTING', error_reporting(0));
662   ini_set('display_errors', FALSE);
663 }
664
665 /**
666  * Turn PHP error handling on.
667  *
668  * We default to error_reporting() here just in
669  * case drush_errors_on() is called before drush_errors_off() and
670  * the context is not yet set.
671  *
672  * @arg $errors string
673  *   The default error level to set in drush. This error level will be
674  *   carried through further drush_errors_on()/off() calls even if not
675  *   provided in later calls.
676  *
677  * @see error_reporting()
678  * @see drush_errors_off()
679  */
680 function drush_errors_on($errors = null) {
681   if (!isset($errors)) {
682     $errors = error_reporting();
683   }
684   else {
685     drush_set_context('DRUSH_ERROR_REPORTING', $errors);
686   }
687   error_reporting(drush_get_context('DRUSH_ERROR_REPORTING', $errors));
688   ini_set('display_errors', TRUE);
689 }
690
691 /**
692  * @} End of "defgroup errorhandling".
693  */
694
695 /**
696  * Get the PHP memory_limit value in bytes.
697  */
698 function drush_memory_limit() {
699   $value = trim(ini_get('memory_limit'));
700   $last = strtolower($value[strlen($value)-1]);
701   $size = (int) substr($value, 0, -1);
702   switch ($last) {
703     case 'g':
704       $size *= DRUSH_KILOBYTE;
705     case 'm':
706       $size *= DRUSH_KILOBYTE;
707     case 'k':
708       $size *= DRUSH_KILOBYTE;
709   }
710
711   return $size;
712 }
713
714
715 /**
716  * Form an associative array from a linear array.
717  *
718  * This function walks through the provided array and constructs an associative
719  * array out of it. The keys of the resulting array will be the values of the
720  * input array. The values will be the same as the keys unless a function is
721  * specified, in which case the output of the function is used for the values
722  * instead.
723  *
724  * @param $array
725  *   A linear array.
726  * @param $function
727  *   A name of a function to apply to all values before output.
728  *
729  * @return
730  *   An associative array.
731  */
732 function drush_map_assoc($array, $function = NULL) {
733   // array_combine() fails with empty arrays:
734   // http://bugs.php.net/bug.php?id=34857.
735   $array = !empty($array) ? array_combine($array, $array) : [];
736   if (is_callable($function)) {
737     $array = array_map($function, $array);
738   }
739   return $array;
740 }