engines.inc

  1. 8.0.x includes/engines.inc
  2. 6.x includes/engines.inc
  3. 7.x includes/engines.inc
  4. master includes/engines.inc

The drush engines API implementation and helpers.

Functions

Namesort descending Description
drush_engine_add_help_topics Add engine topics to the command topics, if any.
drush_engine_topic_command Implementation of command hook for docs-output-formats
drush_get_command_engine_config Returns engine config supplied in the command definition.
drush_get_engine Return the engine of the specified type that was loaded by the Drush command.
drush_get_engines Return a structured array of engines of a specific type.
drush_get_engine_topics Take a look at all of the available engines, and create topic commands for each one that declares a topic.
drush_get_engine_types_info Obtain all engine types info and normalize with defaults.
drush_include_engine Include the engine code for a specific named engine of a certain type.
drush_load_command_engine Selects and loads an engine implementing the given type.
drush_load_command_engines Include, instantiate and validate command engines.
drush_load_engine Loads and validate an engine of the given type.
drush_merge_engine_data Add command structure info from each engine type back into the command.
drush_select_engine Selects an engine between the available ones.
drush_set_engine Called by the Drush command (to cache the active engine instance.
_drush_array_overlay_recursive

File

includes/engines.inc
View source
  1. <?php
  2. /**
  3. * @file
  4. * The drush engines API implementation and helpers.
  5. */
  6. use Drush\Log\LogLevel;
  7. /**
  8. * Obtain all engine types info and normalize with defaults.
  9. *
  10. * @see hook_drush_engine_type_info().
  11. */
  12. function drush_get_engine_types_info() {
  13. $info = drush_command_invoke_all('drush_engine_type_info');
  14. foreach ($info as $type => $data) {
  15. $info[$type] += array(
  16. 'description' => '',
  17. 'option' => FALSE,
  18. 'default' => NULL,
  19. 'options' => array(),
  20. 'sub-options' => array(),
  21. 'config-aliases' => array(),
  22. 'add-options-to-command' => FALSE,
  23. 'combine-help' => FALSE,
  24. );
  25. }
  26. return $info;
  27. }
  28. /**
  29. * Return a structured array of engines of a specific type.
  30. *
  31. * Engines are pluggable subsystems. Each engine of a specific type will
  32. * implement the same set of API functions and perform the same high-level
  33. * task using a different backend or approach.
  34. *
  35. * This function/hook is useful when you have a selection of several mutually
  36. * exclusive options to present to a user to select from.
  37. *
  38. * Other commands are able to extend this list and provide their own engines.
  39. * The hook can return useful information to help users decide which engine
  40. * they need, such as description or list of available engine options.
  41. *
  42. * The engine path element will automatically default to a subdirectory (within
  43. * the directory of the commandfile that implemented the hook) with the name of
  44. * the type of engine - e.g. an engine "wget" of type "handler" provided by
  45. * the "pm" commandfile would automatically be found if the file
  46. * "pm/handler/wget.inc" exists and a specific path is not provided.
  47. *
  48. * @param $engine_type
  49. * The type of engine.
  50. *
  51. * @return
  52. * A structured array of engines.
  53. */
  54. function drush_get_engines($engine_type) {
  55. $info = drush_get_engine_types_info();
  56. if (!isset($info[$engine_type])) {
  57. return drush_set_error('DRUSH_UNKNOWN_ENGINE_TYPE', dt('Unknown engine type !engine_type', array('!engine_type' => $engine_type)));
  58. }
  59. $engines = array(
  60. 'info' => $info[$engine_type],
  61. 'engines' => array(),
  62. );
  63. $list = drush_commandfile_list();
  64. $hook = 'drush_engine_' . str_replace('-', '_', $engine_type);
  65. foreach ($list as $commandfile => $path) {
  66. if (drush_command_hook($commandfile, $hook)) {
  67. $function = $commandfile . '_' . $hook;
  68. $result = $function();
  69. foreach ($result as $engine_name => $engine) {
  70. // Add some defaults.
  71. $engine += array(
  72. 'commandfile' => $commandfile,
  73. 'options' => array(),
  74. 'sub-options' => array(),
  75. 'drupal dependencies' => array(),
  76. );
  77. // Legacy engines live in a subdirectory
  78. // of the commandfile that declared them.
  79. $engine_path = sprintf("%s/%s", dirname($path), $engine_type);
  80. if (file_exists($engine_path)) {
  81. $engine['path'] = $engine_path;
  82. }
  83. // Build engine class name, in case the engine doesn't provide it.
  84. // The class name is based on the engine type and name, converted
  85. // from snake_case to CamelCase.
  86. // For example for type 'package_handler' and engine 'git_drupalorg'
  87. // the class is \Drush\PackageHandler\GitDrupalorg
  88. elseif (!isset($engine['class'])) {
  89. $parts = array();
  90. $parts[] = '\Drush';
  91. $parts[] = str_replace(' ', '', ucwords(strtr($engine_type, '_', ' ')));
  92. $parts[] = str_replace(' ', '', ucwords(strtr($engine_name, '_', ' ')));
  93. $engine['class'] = implode('\\', $parts);
  94. }
  95. $engines['engines'][$engine_name] = $engine;
  96. }
  97. }
  98. }
  99. return $engines;
  100. }
  101. /**
  102. * Take a look at all of the available engines,
  103. * and create topic commands for each one that
  104. * declares a topic.
  105. */
  106. function drush_get_engine_topics() {
  107. $items = array();
  108. $info = drush_get_engine_types_info();
  109. foreach ($info as $engine => $data) {
  110. if (array_key_exists('topic', $data)) {
  111. $items[$data['topic']] = array(
  112. 'description' => $data['description'],
  113. 'hidden' => TRUE,
  114. 'topic' => TRUE,
  115. 'bootstrap' => DRUSH_BOOTSTRAP_DRUSH,
  116. 'callback' => 'drush_engine_topic_command',
  117. 'callback arguments' => array($engine),
  118. );
  119. }
  120. }
  121. return $items;
  122. }
  123. /**
  124. * Include, instantiate and validate command engines.
  125. *
  126. * @return FALSE if a engine doesn't validate.
  127. */
  128. function drush_load_command_engines($command) {
  129. $result = TRUE;
  130. foreach ($command['engines'] as $engine_type => $config) {
  131. $result = drush_load_command_engine($command, $engine_type);
  132. // Stop loading engines if any of them fails.
  133. if ($result === FALSE) {
  134. break;
  135. }
  136. }
  137. return $result;
  138. }
  139. /**
  140. * Returns engine config supplied in the command definition.
  141. */
  142. function drush_get_command_engine_config($command, $engine_type, $metadata = array()) {
  143. if (isset($command['engines'][$engine_type])) {
  144. $metadata = array_merge($metadata, $command['engines'][$engine_type]);
  145. }
  146. return $metadata;
  147. }
  148. /**
  149. * Selects and loads an engine implementing the given type.
  150. *
  151. * Loaded engines are stored as a context.
  152. */
  153. function drush_load_command_engine($command, $engine_type, $metadata = array()) {
  154. drush_log(dt("Loading !engine engine.", array('!engine' => $engine_type), LogLevel::BOOTSTRAP));
  155. $config = drush_get_command_engine_config($command, $engine_type, $metadata);
  156. $engine_info = drush_get_engines($engine_type);
  157. $engine = drush_select_engine($config, $engine_info);
  158. $version = drush_drupal_major_version();
  159. $context = $engine_type . '_engine_' . $engine . '_' . $version;
  160. $instance = drush_get_context($context, FALSE);
  161. if ($instance != FALSE) {
  162. drush_set_engine($engine_type, $instance);
  163. }
  164. else {
  165. $instance = drush_load_engine($engine_type, $engine, $config);
  166. if ($instance == FALSE) {
  167. return FALSE;
  168. }
  169. drush_set_context($context, $instance);
  170. }
  171. return $instance;
  172. }
  173. /**
  174. * Add command structure info from each engine type back into the command.
  175. */
  176. function drush_merge_engine_data(&$command) {
  177. // First remap engine data from the shortcut location
  178. // ($command['engine_type']) to the standard location
  179. // ($command['engines']['engine_type'])
  180. $info = drush_get_engine_types_info();
  181. foreach ($info as $engine_type => $info) {
  182. if (isset($command[$engine_type])) {
  183. $config = $command[$engine_type];
  184. foreach ($info['config-aliases'] as $engine_option_alias_name => $engine_option_standard_name) {
  185. if (array_key_exists($engine_option_alias_name, $config)) {
  186. $config[$engine_option_standard_name] = $config[$engine_option_alias_name];
  187. unset($config[$engine_option_alias_name]);
  188. }
  189. }
  190. // Convert single string values of 'require-engine-capability' to an array.
  191. if (isset($config['require-engine-capability']) && is_string($config['require-engine-capability'])) {
  192. $config['require-engine-capability'] = array($config['require-engine-capability']);
  193. }
  194. $command['engines'][$engine_type] = $config;
  195. }
  196. }
  197. foreach ($command['engines'] as $engine_type => $config) {
  198. // Normalize engines structure.
  199. if (!is_array($config)) {
  200. unset($command['engines'][$engine_type]);
  201. $command['engines'][$config] = array();
  202. $engine_type = $config;
  203. }
  204. // Get all implementations for this engine type.
  205. $engine_info = drush_get_engines($engine_type);
  206. if ($engine_info === FALSE) {
  207. return FALSE;
  208. }
  209. // Complete command-declared engine type with default info.
  210. $command['engines'][$engine_type] += $engine_info['info'];
  211. $config = $command['engines'][$engine_type];
  212. $engine_data = array();
  213. // If there's a single implementation for this engine type, it will be
  214. // loaded by default, and makes no sense to provide a command line option
  215. // to select the only flavor (ie. --release_info=updatexml), so we won't
  216. // add an option in this case.
  217. // Additionally, depending on the command, it may be convenient to extend
  218. // the command with the engine options.
  219. if (count($engine_info['engines']) == 1) {
  220. if ($config['add-options-to-command'] !== FALSE) {
  221. // Add options and suboptions of the engine type and
  222. // the sole implementation.
  223. $engine = key($engine_info['engines']);
  224. $data = $engine_info['engines'][$engine];
  225. $engine_data += array(
  226. 'options' => $config['options'] + $data['options'],
  227. 'sub-options' => $config['sub-options'] + $data['sub-options'],
  228. );
  229. }
  230. }
  231. // Otherwise, provide a command option to choose between engines and add
  232. // the engine options and sub-options.
  233. else {
  234. // Add engine type global options and suboptions.
  235. $engine_data += array(
  236. 'options' => $config['options'],
  237. 'sub-options' => $config['sub-options'],
  238. );
  239. // If the 'combine-help' flag is set in the engine config,
  240. // then we will combine all of the help items into the help
  241. // text for $config['option'].
  242. $combine_help = $config['combine-help'];
  243. $combine_help_data = array();
  244. // Process engines in order. First the default engine, the rest alphabetically.
  245. $default = drush_select_engine($config, $engine_info);
  246. $engines = array_keys($engine_info['engines']);
  247. asort($engines);
  248. array_unshift($engines, $default);
  249. $engines = array_unique($engines);
  250. foreach ($engines as $engine) {
  251. $data = $engine_info['engines'][$engine];
  252. // Check to see if the command requires any particular
  253. // capabilities. If no capabilities are required, then
  254. // all engines are acceptable.
  255. $engine_is_usable = TRUE;
  256. if (array_key_exists('require-engine-capability', $config)) {
  257. // See if the engine declares that it provides any
  258. // capabilities. If no capabilities are listed, then
  259. // it is assumed that the engine can satisfy all requirements.
  260. if (array_key_exists('engine-capabilities', $data)) {
  261. $engine_is_usable = FALSE;
  262. // If 'require-engine-capability' is TRUE instead of an array,
  263. // then only engines that are universal (do not declare any
  264. // particular capabilities) are usable.
  265. if (is_array($config['require-engine-capability'])) {
  266. foreach ($config['require-engine-capability'] as $required) {
  267. // We need an engine that provides any one of the requirements.
  268. if (in_array($required, $data['engine-capabilities'])) {
  269. $engine_is_usable = TRUE;
  270. }
  271. }
  272. }
  273. }
  274. }
  275. if ($engine_is_usable) {
  276. $command['engines'][$engine_type]['usable'][] = $engine;
  277. if (!isset($data['hidden'])) {
  278. $option = $config['option'] . '=' . $engine;
  279. $engine_data['options'][$option]['description'] = array_key_exists('description', $data) ? $data['description'] : NULL;
  280. if ($combine_help) {
  281. $engine_data['options'][$option]['hidden'] = TRUE;
  282. if (drush_get_context('DRUSH_VERBOSE') || ($default == $engine) || !isset($data['verbose-only'])) {
  283. $combine_help_data[$engine] = $engine . ': ' . $data['description'];
  284. }
  285. }
  286. if (isset($data['options'])) {
  287. $engine_data['sub-options'][$option] = $data['options'];
  288. }
  289. if (isset($data['sub-options'])) {
  290. $engine_data['sub-options'] += $data['sub-options'];
  291. }
  292. }
  293. }
  294. }
  295. if (!empty($combine_help_data)) {
  296. $engine_selection_option = $config['option'];
  297. if (!is_array($engine_data['options'][$engine_selection_option])) {
  298. $engine_data['options'][$engine_selection_option] = array('description' => $config['options'][$engine_selection_option]);
  299. }
  300. if (drush_get_context('DRUSH_VERBOSE')) {
  301. $engine_data['options'][$engine_selection_option]['description'] .= "\n" . dt("All available values are:") . "\n - " . implode("\n - ", $combine_help_data) . "\n";
  302. }
  303. else {
  304. $engine_data['options'][$engine_selection_option]['description'] .= " " . dt("Available: ") . implode(', ', array_keys($combine_help_data)) . ". ";
  305. }
  306. $engine_data['options'][$engine_selection_option]['description'] .= dt("Default is !default.", array('!default' => $default));
  307. }
  308. else {
  309. // If the help options are not combined, then extend the
  310. // default engine description.
  311. $desc = $engine_info['engines'][$default]['description'];
  312. $engine_info['engines'][$default]['description'] = dt('Default !type engine.', array('!type' => $engine_type)) . ' ' . $desc;
  313. }
  314. }
  315. // This was simply array_merge_recursive($command, $engine_data), but this
  316. // function has an undesirable behavior when merging primative types.
  317. // If there is a 'key' => 'value' in $command, and the same 'key' => 'value'
  318. // exists in $engine data, then the result will be 'key' => array('value', 'value');.
  319. // This is NOT what we want, so we provide our own 'overlay' function instead.
  320. $command = _drush_array_overlay_recursive($command, $engine_data);
  321. }
  322. }
  323. // Works like array_merge_recursive(), but does not convert primative
  324. // types into arrays. Ever.
  325. function _drush_array_overlay_recursive($a, $b) {
  326. foreach ($b as $key => $value) {
  327. if (!isset($a[$key]) || !is_array($a[$key])) {
  328. $a[$key] = $b[$key];
  329. }
  330. else {
  331. $a[$key] = _drush_array_overlay_recursive($a[$key], $b[$key]);
  332. }
  333. }
  334. return $a;
  335. }
  336. /**
  337. * Implementation of command hook for docs-output-formats
  338. */
  339. function drush_engine_topic_command($engine) {
  340. $engine_instances = drush_get_engines($engine);
  341. $option = $engine_instances['info']['option'];
  342. if (isset($engine_instances['info']['topic-file'])) {
  343. // To do: put this file next to the commandfile that defines the
  344. // engine type, not in the core docs directory.
  345. $docs_dir = drush_get_context('DOC_PREFIX', DRUSH_BASE_PATH);
  346. $path = $engine_instances['info']['topic-file'];
  347. $docs_file = (drush_is_absolute_path($path) ? '' : $docs_dir . '/') . $path;
  348. $doc_text = drush_html_to_text(file_get_contents($docs_file));
  349. }
  350. elseif (isset($engine_instances['info']['topic-text'])) {
  351. $doc_text = $engine_instances['info']['topic-text'];
  352. }
  353. else {
  354. return drush_set_error('DRUSH_BAD_ENGINE_TOPIC', dt("The engine !engine did not define its topic command correctly.", array('!engine' => $engine)));
  355. }
  356. // Look at each instance of the engine; if it has an html
  357. // file in the the 'topics' folder named after itself, then
  358. // include the file contents in the engine topic text.
  359. $instances = $engine_instances['engines'];
  360. ksort($instances);
  361. foreach ($instances as $instance => $config) {
  362. if (isset($config['description'])) {
  363. $doc_text .= "\n\n::: --$option=$instance :::\n" . $config['description'];
  364. $path = $config['path'] . '/topics/' . $instance . '.html';
  365. if (file_exists($path)) {
  366. $doc_text .= "\n" . drush_html_to_text(file_get_contents($path));
  367. }
  368. $additional_topic_text = drush_command_invoke_all('drush_engine_topic_additional_text', $engine, $instance, $config);
  369. if (!empty($additional_topic_text)) {
  370. $doc_text .= "\n\n" . implode("\n\n", $additional_topic_text);
  371. }
  372. }
  373. }
  374. // Write the topic text to a file so that is can be paged
  375. $file = drush_save_data_to_temp_file($doc_text);
  376. drush_print_file($file);
  377. }
  378. /**
  379. * Selects an engine between the available ones.
  380. *
  381. * Precedence:
  382. *
  383. * - preferred engine, if available.
  384. * - user supplied engine via cli.
  385. * - default engine from engine type / command declaration.
  386. * - the first engine available.
  387. *
  388. * @param array $config
  389. * Engine type configuration. My be overridden in command declaration.
  390. * @param array $engine_info
  391. * Engine type declaration.
  392. * @param string $default
  393. * Preferred engine.
  394. *
  395. * @return string
  396. * Selected engine.
  397. */
  398. function drush_select_engine($config, $engine_info, $preferred = NULL) {
  399. $engines = array_keys($engine_info['engines']);
  400. if (in_array($preferred, $engines)) {
  401. return $preferred;
  402. }
  403. if (!empty($config['option'])) {
  404. $engine = drush_get_option($config['option'], FALSE);
  405. if ($engine && in_array($engine, $engines)) {
  406. return $engine;
  407. }
  408. }
  409. if (isset($config['default']) && in_array($config['default'], $engines)) {
  410. return $config['default'];
  411. }
  412. return current($engines);
  413. }
  414. /**
  415. * Loads and validate an engine of the given type.
  416. *
  417. * @param string $type
  418. * Engine type.
  419. * @param string $engine
  420. * Engine name.
  421. * @param array $config
  422. * Engine configuration. Tipically it comes from a command declaration.
  423. *
  424. * @return
  425. * TRUE or instanced object of available class on success. FALSE on fail.
  426. */
  427. function drush_load_engine($type, $engine, $config = array()) {
  428. $engine_info = drush_get_engines($type);
  429. $engine = drush_select_engine($config, $engine_info, $engine);
  430. $config['engine-info'] = $engine_info['engines'][$engine];
  431. // Check engine dependency on drupal modules before include.
  432. $dependencies = $config['engine-info']['drupal dependencies'];
  433. foreach ($dependencies as $dependency) {
  434. if (!drush_module_exists($dependency)) {
  435. return drush_set_error('DRUSH_ENGINE_DEPENDENCY_ERROR', dt('!engine_type: !engine engine needs the following modules installed/enabled to run: !dependencies.', array('!engine_type' => $type, '!engine' => $engine, '!dependencies' => implode(', ', $dependencies))));
  436. }
  437. }
  438. $result = drush_include_engine($type, $engine, $config);
  439. if (is_object($result)) {
  440. $valid = method_exists($result, 'validate') ? $result->validate() : TRUE;
  441. if ($valid) {
  442. drush_set_engine($type, $result);
  443. }
  444. }
  445. else {
  446. $function = strtr($type, '-', '_') . '_validate';
  447. $valid = function_exists($function) ? call_user_func($function) : TRUE;
  448. }
  449. if (!$valid) {
  450. return FALSE;
  451. }
  452. return $result;
  453. }
  454. /**
  455. * Include the engine code for a specific named engine of a certain type.
  456. *
  457. * If the engine type has implemented hook_drush_engine_$type the path to the
  458. * engine specified in the array will be used.
  459. *
  460. * If a class named in the form drush_$type_$engine exists, it will return an
  461. * instance of the class.
  462. *
  463. * @param string $type
  464. * The type of engine.
  465. * @param string $engine
  466. * The name for the engine to include.
  467. * @param array $config
  468. * Parameters for the engine class constructor.
  469. *
  470. * @return
  471. * TRUE or instanced object of available class on success. FALSE on fail.
  472. */
  473. function drush_include_engine($type, $engine, $config = NULL) {
  474. $engine_info = drush_get_engines($type);
  475. // Pick the engine name that actually implements the requested engine.
  476. $engine = isset($engine_info['engines'][$engine]['implemented-by']) ? $engine_info['engines'][$engine]['implemented-by'] : $engine;
  477. // Legacy engines live in a subdirectory of the commandfile
  478. // that declares them. We need to explicitly include the file.
  479. if (isset($engine_info['engines'][$engine]['path'])) {
  480. $path = $engine_info['engines'][$engine]['path'];
  481. if (!drush_include($path, $engine)) {
  482. return drush_set_error('DRUSH_ENGINE_INCLUDE_FAILED', dt('Unable to include the !type engine !engine from !path.' , array('!path' => $path, '!type' => $type, '!engine' => $engine)));
  483. }
  484. // Legacy engines may be implemented in a magic class name.
  485. $class = 'drush_' . $type . '_' . str_replace('-', '_', $engine);
  486. if (class_exists($class)) {
  487. $instance = new $class($config);
  488. $instance->engine_type = $type;
  489. $instance->engine = $engine;
  490. return $instance;
  491. }
  492. return TRUE;
  493. }
  494. return drush_get_class($engine_info['engines'][$engine]['class'], array($type, $engine, $config));
  495. }
  496. /**
  497. * Return the engine of the specified type that was loaded by the Drush command.
  498. */
  499. function drush_get_engine($type) {
  500. return drush_get_context($type . '_engine', FALSE);
  501. }
  502. /**
  503. * Called by the Drush command (@see _drush_load_command_engines())
  504. * to cache the active engine instance.
  505. */
  506. function drush_set_engine($type, $instance) {
  507. drush_set_context($type . '_engine', $instance);
  508. }
  509. /**
  510. * Add engine topics to the command topics, if any.
  511. */
  512. function drush_engine_add_help_topics(&$command) {
  513. $engine_types = drush_get_engine_types_info();
  514. foreach ($command['engines'] as $engine_type => $config) {
  515. $info = $engine_types[$engine_type];
  516. if (isset($info['topics'])) {
  517. $command['topics'] = array_merge($command['topics'], $info['topics']);
  518. }
  519. if (isset($info['topic'])) {
  520. $command['topics'][] = $info['topic'];
  521. }
  522. }
  523. }