Experienced Drupal developers can hardly do without Drush (Drupal shell) utility, as it speeds up a good deal of routine function along the way. The standard drush-commands package that has the usual content (like module installation, data-base dump creation, admin password cleanup etc.) is accessible on this site, here. As it happens, though, at times you want (or have) to get some custom commands (of your own make) into play, too. This article relates to what could be done with regard to fulfillment of the above requirement.
Let us have a look at the case of creating a command that will delete all items of a selected type (lots of people do face this job, as it is). I just want to be upfront and reveal it to you how the syntax of the command will look like: drush delete-content %content_type%, whereby %content_type% being the machine name assigned to the type of contact.
Drush-command staging is divided into three steps:
- example_drush.drush.inc file creation
- invocation of hook_drush_command() in the created file
- commands' description
example_module.drush.inc file creation
It is an instance of routine module creation: we start with creating example_drush.info and example_drush.module files. The module's main file may stay empty, it should exist, anyway, for the sake of the drush-command's correct functioning. Next, we get down to creating the batch file example_drush.drush.inc. It's ending should contain "drush.inc" so that the utility could recognize it as the one with drush-commands. Drush is looking up the batch files in the following folders:
- /path/to/drush/commands folder
- foulders enlisted in the "include" option
- system foulder for batch files drush. E.g., /usr/share/drush/commands
- ".drush" folder in the user's home folder
- sites/all/drush in the current installation of Drupal
- the included modules' folders
hook_drush_command() invocation in the created file
The batch file's most important element is hook_drush_command(). It provides for the descriptions of the commands proper as well as the way they operate. The description resembles, in a way, the description of the elements in hook_menu. Let us investigate the key components of it:
- aliases - the list of contracted names of the commands. E.g., we can promptly substitute a long name for the command "pm-download" with a more concise "dl". Provided there are the synonyms supplied, the "long" command will keep functioning.
- callback - the name of the function which will be called in to perform the command. The function's name should contain (at its beginning) the relevant file's name. In our case we have example_drush_. The parameter is optional, so In case it were skipped, drush_invoke() will generate the name automatically.
- description - command's description.
- arguments - parameter array that's perceived of by the function. It's only utilized for the "drush help" command.
- required-arguments - is by default set up to FALSE. Once switched over to TRUE, the parameters will become obligatory.
- Drupal dependencies - Drupal modules that should definitely be switched.
- drush dependencies - drush-files that are required for executing the command.
- Bootstrap - the specified phase that Drupal has been bootstrapped to facilitate cooperation with the command.
This is how hook_drush_command() is looking, in our case:
/** * Implements hook_drush_command(). */ function example_drush_drush_command() { $items = array(); $items['delete-content'] = array( 'description' => 'Delete test content', 'drupal dependencies' => array('example_drush'), 'aliases' => array('dc'), 'arguments' => array( 'type' => 'Nodes of this type will be deleted', ), ); return $items; }
The command execution description
The function that describes the execution of the command will look here as follows:
/** * Deletes content of specific type. */ function drush_example_drush_delete_content($type) { if ($type) { $nids = db_query("SELECT nid FROM {node} WHERE type = :type", array(':type' => $type))->fetchCol(); if ($nids) { $deleted = 0; foreach ($nids as $nid) { node_delete($nid); $deleted++; _drush_print_progress($deleted / count($nids)); } $message = $deleted ? t('Quantity of deleted "@type" nodes: !qty', array('!qty' => $deleted, '@type' => $type)) : t('There are no nodes of type "@type"', array('@type' => $type)); } else { $message = t('There are no nodes of specified content type.'); } drush_print($message); } else { drush_print(t('Specify content type')); } }
In order to call in the newly created command, we need to input "drush delete-content %content_type%" or "drush dc %content_type%" in the terminal, whereby %content_type% is the machine name for the type of content.
A succession of service functions is called in, prior and after the drush-command execution. The list of possible options can be looked up here. In our case we are going to investigate the validation function:
/** * Validate handler for delete-content command. */ function drush_example_drush_delete_content_validate($type) { $types = node_type_get_types(); if ($type && !in_array($type, array_keys($types))) { return drush_set_error('error', t('Content type "@type" is not exists', array('@type' => $type))); } }
The command, hence, will only be executed, provided the existing content type has been specified.
In view that the site can contain masses of materials which will result in the substantial loading time, we should keep the user aware of it that it's a planned delay related to deleting of the content. For this purpose a progress bar will be developed, with use of the _drush_print_progress() function:
/** * Provides progress bar. */ function _drush_print_progress($ratio) { $percentage = floor($ratio * 100) . '%'; $columns = drush_get_context('DRUSH_COLUMNS', 80); // Subtract 8 characters for the percentage, brackets, spaces and arrow. $progress_columns = $columns - 8; // If ratio is 1 (complete), the > becomes a = to make a full bar. $arrow = ($ratio < 1) ? '>' : '='; // Print a new line if ratio is 1 (complete). Otherwise, use a CR. $line_ending = ($ratio < 1) ? "\r" : "\n"; // Determine the current length of the progress string. $current_length = floor($ratio * $progress_columns); $progress_string = str_pad('', $current_length, '='); $output = '['; $output .= $progress_string . $arrow; $output .= str_pad('', $progress_columns - $current_length); $output .= ']'; $output .= str_pad('', 5 - strlen($percentage)) . $percentage; $output .= $line_ending; print $output; }