Created
October 11, 2017 14:17
-
-
Save miya0001/d17a036a2a82a86f293b459f4d7868d9 to your computer and use it in GitHub Desktop.
patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/features/bootstrap/FeatureContext.php b/features/bootstrap/FeatureContext.php | |
index aaf39713..ae0adb9d 100644 | |
--- a/features/bootstrap/FeatureContext.php | |
+++ b/features/bootstrap/FeatureContext.php | |
@@ -1,14 +1,14 @@ | |
<?php | |
use Behat\Behat\Context\ClosuredContextInterface, | |
- Behat\Behat\Context\TranslatedContextInterface, | |
- Behat\Behat\Context\BehatContext, | |
- Behat\Behat\Event\SuiteEvent; | |
+Behat\Behat\Context\TranslatedContextInterface, | |
+Behat\Behat\Context\BehatContext, | |
+Behat\Behat\Event\SuiteEvent; | |
use \WP_CLI\Process; | |
use \WP_CLI\Utils; | |
-// Inside a community package | |
+ | |
if ( file_exists( __DIR__ . '/utils.php' ) ) { | |
require_once __DIR__ . '/utils.php'; | |
require_once __DIR__ . '/Process.php'; | |
@@ -27,7 +27,7 @@ if ( file_exists( __DIR__ . '/utils.php' ) ) { | |
putenv( 'WP_CLI_CONFIG_PATH=' . $project_config ); | |
} | |
} | |
-// Inside WP-CLI | |
+ | |
} else { | |
require_once __DIR__ . '/../../php/utils.php'; | |
require_once __DIR__ . '/../../php/WP_CLI/Process.php'; | |
@@ -39,41 +39,41 @@ if ( file_exists( __DIR__ . '/utils.php' ) ) { | |
} | |
} | |
-/** | |
- * Features context. | |
- */ | |
+ | |
+ | |
+ | |
class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
- /** | |
- * The current working directory for scenarios that have a "Given a WP install" or "Given an empty directory" step. Variable RUN_DIR. Lives until the end of the scenario. | |
- */ | |
+ | |
+ | |
+ | |
private static $run_dir; | |
- /** | |
- * Where WordPress core is downloaded to for caching, and which is copied to RUN_DIR during a "Given a WP install" step. Lives until manually deleted. | |
- */ | |
+ | |
+ | |
+ | |
private static $cache_dir; | |
- /** | |
- * The directory that holds the install cache, and which is copied to RUN_DIR during a "Given a WP install" step. Recreated on each suite run. | |
- */ | |
+ | |
+ | |
+ | |
private static $install_cache_dir; | |
- /** | |
- * The directory that the WP-CLI cache (WP_CLI_CACHE_DIR, normally "$HOME/.wp-cli/cache") is set to on a "Given an empty cache" step. | |
- * Variable SUITE_CACHE_DIR. Lives until the end of the scenario (or until another "Given an empty cache" step within the scenario). | |
- */ | |
+ | |
+ | |
+ | |
+ | |
private static $suite_cache_dir; | |
- /** | |
- * Where the current WP-CLI source repository is copied to for Composer-based tests with a "Given a dependency on current wp-cli" step. | |
- * Variable COMPOSER_LOCAL_REPOSITORY. Lives until the end of the suite. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
private static $composer_local_repository; | |
- /** | |
- * The test database settings. All but `dbname` can be set via environment variables. The database is dropped at the start of each scenario and created on a "Given a WP install" step. | |
- */ | |
+ | |
+ | |
+ | |
private static $db_settings = array( | |
'dbname' => 'wp_cli_test', | |
'dbuser' => 'wp_cli_test', | |
@@ -81,44 +81,44 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
'dbhost' => '127.0.0.1', | |
); | |
- /** | |
- * Array of background process ids started by the current scenario. Used to terminate them at the end of the scenario. | |
- */ | |
+ | |
+ | |
+ | |
private $running_procs = array(); | |
- /** | |
- * Array of variables available as {VARIABLE_NAME}. Some are always set: CORE_CONFIG_SETTINGS, SRC_DIR, CACHE_DIR, WP_VERSION-version-latest. Some are step-dependent: | |
- * RUN_DIR, SUITE_CACHE_DIR, COMPOSER_LOCAL_REPOSITORY, PHAR_PATH. Scenarios can define their own variables using "Given save" steps. Variables are reset for each scenario. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
public $variables = array(); | |
- /** | |
- * The current feature file and scenario line number as '<file>.<line>'. Used in RUN_DIR and SUITE_CACHE_DIR directory names. Set at the start of each scenario. | |
- */ | |
+ | |
+ | |
+ | |
private static $temp_dir_infix; | |
- /** | |
- * Settings and variables for WP_CLI_TEST_LOG_RUN_TIMES run time logging. | |
- */ | |
- private static $log_run_times; // Whether to log run times - WP_CLI_TEST_LOG_RUN_TIMES env var. Set on `@BeforeScenario'. | |
- private static $suite_start_time; // When the suite started, set on `@BeforeScenario'. | |
- private static $output_to; // Where to output log - stdout|error_log. Set on `@BeforeSuite`. | |
- private static $num_top_processes; // Number of processes/methods to output by longest run times. Set on `@BeforeSuite`. | |
- private static $num_top_scenarios; // Number of scenarios to output by longest run times. Set on `@BeforeSuite`. | |
- | |
- private static $scenario_run_times = array(); // Scenario run times (top `self::$num_top_scenarios` only). | |
- private static $scenario_count = 0; // Scenario count, incremented on `@AfterScenario`. | |
- private static $proc_method_run_times = array(); // Array of run time info for proc methods, keyed by method name and arg, each a 2-element array containing run time and run count. | |
- | |
- /** | |
- * Get the environment variables required for launched `wp` processes | |
- */ | |
+ | |
+ | |
+ | |
+ private static $log_run_times; | |
+ private static $suite_start_time; | |
+ private static $output_to; | |
+ private static $num_top_processes; | |
+ private static $num_top_scenarios; | |
+ | |
+ private static $scenario_run_times = array(); | |
+ private static $scenario_count = 0; | |
+ private static $proc_method_run_times = array(); | |
+ | |
+ | |
+ | |
+ | |
private static function get_process_env_variables() { | |
- // Ensure we're using the expected `wp` binary | |
+ | |
$bin_dir = getenv( 'WP_CLI_BIN_DIR' ) ?: realpath( __DIR__ . '/../../bin' ); | |
$vendor_dir = realpath( __DIR__ . '/../../vendor/bin' ); | |
$env = array( | |
- 'PATH' => $bin_dir . ':' . $vendor_dir . ':' . getenv( 'PATH' ), | |
+ 'PATH' => $bin_dir . ':' . $vendor_dir . ':' . getenv( 'PATH' ), | |
'BEHAT_RUN' => 1, | |
'HOME' => sys_get_temp_dir() . '/wp-cli-home', | |
); | |
@@ -140,10 +140,10 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return $env; | |
} | |
- /** | |
- * We cache the results of `wp core download` to improve test performance. | |
- * Ideally, we'd cache at the HTTP layer for more reliable tests. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
private static function cache_wp_files() { | |
$wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; | |
self::$cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-download-cache' . $wp_version_suffix; | |
@@ -158,11 +158,11 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
Process::create( $cmd, null, self::get_process_env_variables() )->run_check(); | |
} | |
- /** | |
- * @BeforeSuite | |
- */ | |
+ | |
+ | |
+ | |
public static function prepare( SuiteEvent $event ) { | |
- // Test performance statistics - useful for detecting slow tests. | |
+ | |
if ( self::$log_run_times = getenv( 'WP_CLI_TEST_LOG_RUN_TIMES' ) ) { | |
self::log_run_times_before_suite( $event ); | |
} | |
@@ -176,7 +176,7 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
echo 'WordPress ' . $result->stdout; | |
echo PHP_EOL; | |
- // Remove install cache if any (not setting the static var). | |
+ | |
$wp_version_suffix = ( $wp_version = getenv( 'WP_VERSION' ) ) ? "-$wp_version" : ''; | |
$install_cache_dir = sys_get_temp_dir() . '/wp-cli-test-core-install-cache' . $wp_version_suffix; | |
if ( file_exists( $install_cache_dir ) ) { | |
@@ -184,9 +184,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
} | |
} | |
- /** | |
- * @AfterSuite | |
- */ | |
+ | |
+ | |
+ | |
public static function afterSuite( SuiteEvent $event ) { | |
if ( self::$composer_local_repository ) { | |
self::remove_dir( self::$composer_local_repository ); | |
@@ -198,9 +198,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
} | |
} | |
- /** | |
- * @BeforeScenario | |
- */ | |
+ | |
+ | |
+ | |
public function beforeScenario( $event ) { | |
if ( self::$log_run_times ) { | |
self::log_run_times_before_scenario( $event ); | |
@@ -208,38 +208,38 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
$this->variables['SRC_DIR'] = realpath( __DIR__ . '/../..' ); | |
- // Used in the names of the RUN_DIR and SUITE_CACHE_DIR directories. | |
+ | |
self::$temp_dir_infix = null; | |
if ( $file = self::get_event_file( $event, $line ) ) { | |
self::$temp_dir_infix = basename( $file ) . '.' . $line; | |
} | |
} | |
- /** | |
- * @AfterScenario | |
- */ | |
+ | |
+ | |
+ | |
public function afterScenario( $event ) { | |
if ( self::$run_dir ) { | |
- // remove altered WP install, unless there's an error | |
+ | |
if ( $event->getResult() < 4 ) { | |
self::remove_dir( self::$run_dir ); | |
} | |
self::$run_dir = null; | |
} | |
- // Remove WP-CLI package directory if any. Set to `wp package path` by package-command and scaffold-package-command features, and by cli-info.feature. | |
+ | |
if ( isset( $this->variables['PACKAGE_PATH'] ) ) { | |
self::remove_dir( $this->variables['PACKAGE_PATH'] ); | |
} | |
- // Remove SUITE_CACHE_DIR if any. | |
+ | |
if ( self::$suite_cache_dir ) { | |
self::remove_dir( self::$suite_cache_dir ); | |
self::$suite_cache_dir = null; | |
} | |
- // Remove any background processes. | |
+ | |
foreach ( $this->running_procs as $proc ) { | |
$status = proc_get_status( $proc ); | |
self::terminate_proc( $status['pid'] ); | |
@@ -250,9 +250,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
} | |
} | |
- /** | |
- * Terminate a process and any of its children. | |
- */ | |
+ | |
+ | |
+ | |
private static function terminate_proc( $master_pid ) { | |
$output = `ps -o ppid,pid,command | grep $master_pid`; | |
@@ -270,16 +270,16 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
if ( ! posix_kill( (int) $master_pid, 9 ) ) { | |
$errno = posix_get_last_error(); | |
- // Ignore "No such process" error as that's what we want. | |
- if ( 3 /*ESRCH*/ !== $errno ) { | |
+ | |
+ if ( 3 !== $errno ) { | |
throw new RuntimeException( posix_strerror( $errno ) ); | |
} | |
} | |
} | |
- /** | |
- * Create a temporary WP_CLI_CACHE_DIR. Exposed as SUITE_CACHE_DIR in "Given an empty cache" step. | |
- */ | |
+ | |
+ | |
+ | |
public static function create_cache_dir() { | |
if ( self::$suite_cache_dir ) { | |
self::remove_dir( self::$suite_cache_dir ); | |
@@ -289,12 +289,12 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return self::$suite_cache_dir; | |
} | |
- /** | |
- * Initializes context. | |
- * Every scenario gets its own context object. | |
- * | |
- * @param array $parameters context parameters (set them up through behat.yml) | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
public function __construct( array $parameters ) { | |
if ( getenv( 'WP_CLI_TEST_DBUSER' ) ) { | |
self::$db_settings['dbuser'] = getenv( 'WP_CLI_TEST_DBUSER' ); | |
@@ -321,9 +321,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return array(); | |
} | |
- /** | |
- * Replace {VARIABLE_NAME}. Note that variable names can only contain uppercase letters and underscores (no numbers). | |
- */ | |
+ | |
+ | |
+ | |
public function replace_variables( $str ) { | |
$ret = preg_replace_callback( '/\{([A-Z_]+)\}/', array( $this, '_replace_var' ), $str ); | |
if ( false !== strpos( $str, '{WP_VERSION-' ) ) { | |
@@ -332,9 +332,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return $ret; | |
} | |
- /** | |
- * Replace variables callback. | |
- */ | |
+ | |
+ | |
+ | |
private function _replace_var( $matches ) { | |
$cmd = $matches[0]; | |
@@ -345,9 +345,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return $cmd; | |
} | |
- /** | |
- * Substitute "{WP_VERSION-version-latest}" variables. | |
- */ | |
+ | |
+ | |
+ | |
private function _replace_wp_versions( $str ) { | |
static $wp_versions = null; | |
if ( null === $wp_versions ) { | |
@@ -355,7 +355,7 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
$response = Requests::get( 'https://api.wordpress.org/core/version-check/1.7/', null, array( 'timeout' => 30 ) ); | |
if ( 200 === $response->status_code && ( $body = json_decode( $response->body ) ) && is_object( $body ) && isset( $body->offers ) && is_array( $body->offers ) ) { | |
- // Latest version alias. | |
+ | |
$wp_versions["{WP_VERSION-latest}"] = count( $body->offers ) ? $body->offers[0]->version : ''; | |
foreach ( $body->offers as $offer ) { | |
$sub_ver = preg_replace( '/(^[0-9]+\.[0-9]+)\.[0-9]+$/', '$1', $offer->version ); | |
@@ -376,9 +376,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return strtr( $str, $wp_versions ); | |
} | |
- /** | |
- * Get the file and line number for the current behat event. | |
- */ | |
+ | |
+ | |
+ | |
private static function get_event_file( $event, &$line ) { | |
if ( method_exists( $event, 'getScenario' ) ) { | |
$scenario_feature = $event->getScenario(); | |
@@ -393,9 +393,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return $scenario_feature->getFile(); | |
} | |
- /** | |
- * Create the RUN_DIR directory, unless already set for this scenario. | |
- */ | |
+ | |
+ | |
+ | |
public function create_run_dir() { | |
if ( !isset( $this->variables['RUN_DIR'] ) ) { | |
self::$run_dir = $this->variables['RUN_DIR'] = sys_get_temp_dir() . '/' . uniqid( 'wp-cli-test-run-' . self::$temp_dir_infix . '-', TRUE ); | |
@@ -406,14 +406,14 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
public function build_phar( $version = 'same' ) { | |
$this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' . uniqid( "wp-cli-build-", TRUE ) . '.phar'; | |
- // Test running against a package installed as a WP-CLI dependency | |
- // WP-CLI installed as a project dependency | |
+ | |
+ | |
$make_phar_path = __DIR__ . '/../../../../../utils/make-phar.php'; | |
if ( ! file_exists( $make_phar_path ) ) { | |
- // Test running against WP-CLI proper | |
+ | |
$make_phar_path = __DIR__ . '/../../utils/make-phar.php'; | |
if ( ! file_exists( $make_phar_path ) ) { | |
- // WP-CLI as a dependency of this project | |
+ | |
$make_phar_path = __DIR__ . '/../../vendor/wp-cli/wp-cli/utils/make-phar.php'; | |
} | |
} | |
@@ -437,8 +437,8 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
); | |
$this->variables['PHAR_PATH'] = $this->variables['RUN_DIR'] . '/' | |
- . uniqid( 'wp-cli-download-', true ) | |
- . '.phar'; | |
+ . uniqid( 'wp-cli-download-', true ) | |
+ . '.phar'; | |
Process::create( Utils\esc_cmd( | |
'curl -sSfL %1$s > %2$s && chmod +x %2$s', | |
@@ -447,9 +447,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
) )->run_check(); | |
} | |
- /** | |
- * CACHE_DIR is a cache for downloaded test data such as images. Lives until manually deleted. | |
- */ | |
+ | |
+ | |
+ | |
private function set_cache_dir() { | |
$path = sys_get_temp_dir() . '/wp-cli-test-cache'; | |
if ( ! file_exists( $path ) ) { | |
@@ -458,13 +458,13 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
$this->variables['CACHE_DIR'] = $path; | |
} | |
- /** | |
- * Run a MySQL command with `$db_settings`. | |
- * | |
- * @param string $sql_cmd Command to run. | |
- * @param array $assoc_args Optional. Associative array of options. Default empty. | |
- * @param bool $add_database Optional. Whether to add dbname to the $sql_cmd. Default false. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
private static function run_sql( $sql_cmd, $assoc_args = array(), $add_database = false ) { | |
$default_assoc_args = array( | |
'host' => self::$db_settings['dbhost'], | |
@@ -509,9 +509,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return Process::create( $command, $cwd, $env ); | |
} | |
- /** | |
- * Start a background process. Will automatically be closed when the tests finish. | |
- */ | |
+ | |
+ | |
+ | |
public function background_proc( $cmd ) { | |
$descriptors = array( | |
0 => STDIN, | |
@@ -536,16 +536,16 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
rename( $this->variables['RUN_DIR'] . "/$src", $this->variables['RUN_DIR'] . "/$dest" ); | |
} | |
- /** | |
- * Remove a directory (recursive). | |
- */ | |
+ | |
+ | |
+ | |
public static function remove_dir( $dir ) { | |
Process::create( Utils\esc_cmd( 'rm -rf %s', $dir ) )->run_check(); | |
} | |
- /** | |
- * Copy a directory (recursive). Destination directory must exist. | |
- */ | |
+ | |
+ | |
+ | |
public static function copy_dir( $src_dir, $dest_dir ) { | |
Process::create( Utils\esc_cmd( "cp -r %s/* %s", $src_dir, $dest_dir ) )->run_check(); | |
} | |
@@ -565,7 +565,7 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
self::copy_dir( self::$cache_dir, $dest_dir ); | |
- // disable emailing | |
+ | |
mkdir( $dest_dir . '/wp-content/mu-plugins' ); | |
copy( __DIR__ . '/../extra/no-mail.php', $dest_dir . '/wp-content/mu-plugins/no-mail.php' ); | |
} | |
@@ -573,7 +573,7 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
public function create_config( $subdir = '', $extra_php = false ) { | |
$params = self::$db_settings; | |
- // Replaces all characters that are not alphanumeric or an underscore into an underscore. | |
+ | |
$params['dbprefix'] = $subdir ? preg_replace( '#[^a-zA-Z\_0-9]#', '_', $subdir ) : 'wp_'; | |
$params['skip-salts'] = true; | |
@@ -628,13 +628,13 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
if ( $install_cache_path && file_exists( $install_cache_path ) ) { | |
self::copy_dir( $install_cache_path, $run_dir ); | |
- self::run_sql( 'mysql --no-defaults', array( 'execute' => "source {$install_cache_path}.sql" ), true /*add_database*/ ); | |
+ self::run_sql( 'mysql --no-defaults', array( 'execute' => "source {$install_cache_path}.sql" ), true ); | |
} else { | |
$this->proc( 'wp core install', $install_args, $subdir )->run_check(); | |
if ( $install_cache_path ) { | |
mkdir( $install_cache_path ); | |
self::dir_diff_copy( $run_dir, self::$cache_dir, $install_cache_path ); | |
- self::run_sql( 'mysqldump --no-defaults', array( 'result-file' => "{$install_cache_path}.sql" ), true /*add_database*/ ); | |
+ self::run_sql( 'mysqldump --no-defaults', array( 'result-file' => "{$install_cache_path}.sql" ), true ); | |
} | |
} | |
} | |
@@ -721,9 +721,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
$this->proc( $this->variables['COMPOSER_PATH'] . ' ' . $cmd )->run_check(); | |
} | |
- /** | |
- * Initialize run time logging. | |
- */ | |
+ | |
+ | |
+ | |
private static function log_run_times_before_suite( $event ) { | |
self::$suite_start_time = microtime( true ); | |
@@ -731,12 +731,12 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
$travis = getenv( 'TRAVIS' ); | |
- // Default output settings. | |
+ | |
self::$output_to = 'stdout'; | |
self::$num_top_processes = $travis ? 10 : 40; | |
self::$num_top_scenarios = $travis ? 10 : 20; | |
- // Allow setting of above with "WP_CLI_TEST_LOG_RUN_TIMES=<output_to>[,<num_top_processes>][,<num_top_scenarios>]" formatted env var. | |
+ | |
if ( preg_match( '/^(stdout|error_log)?(,[0-9]+)?(,[0-9]+)?$/i', self::$log_run_times, $matches ) ) { | |
if ( isset( $matches[1] ) ) { | |
self::$output_to = strtolower( $matches[1] ); | |
@@ -750,18 +750,18 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
} | |
} | |
- /** | |
- * Record the start time of the scenario into the `$scenario_run_times` array. | |
- */ | |
+ | |
+ | |
+ | |
private static function log_run_times_before_scenario( $event ) { | |
if ( $scenario_key = self::get_scenario_key( $event ) ) { | |
self::$scenario_run_times[ $scenario_key ] = -microtime( true ); | |
} | |
} | |
- /** | |
- * Save the run time of the scenario into the `$scenario_run_times` array. Only the top `self::$num_top_scenarios` are kept. | |
- */ | |
+ | |
+ | |
+ | |
private static function log_run_times_after_scenario( $event ) { | |
if ( $scenario_key = self::get_scenario_key( $event ) ) { | |
self::$scenario_run_times[ $scenario_key ] += microtime( true ); | |
@@ -773,14 +773,14 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
} | |
} | |
- /** | |
- * Copy files in updated directory that are not in source directory to copy directory. ("Incremental backup".) | |
- * Note: does not deal with changed files (ie does not compare file contents for changes), for speed reasons. | |
- * | |
- * @param string $upd_dir The directory to search looking for files/directories not in `$src_dir`. | |
- * @param string $src_dir The directory to be compared to `$upd_dir`. | |
- * @param string $cop_dir Where to copy any files/directories in `$upd_dir` but not in `$src_dir` to. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
private static function dir_diff_copy( $upd_dir, $src_dir, $cop_dir ) { | |
if ( false === ( $files = scandir( $upd_dir ) ) ) { | |
$error = error_get_last(); | |
@@ -792,7 +792,7 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
$cop_file = $cop_dir . '/' . $file; | |
if ( ! file_exists( $src_file ) ) { | |
if ( is_dir( $upd_file ) ) { | |
- if ( ! file_exists( $cop_file ) && ! mkdir( $cop_file, 0777, true /*recursive*/ ) ) { | |
+ if ( ! file_exists( $cop_file ) && ! mkdir( $cop_file, 0777, true ) ) { | |
$error = error_get_last(); | |
throw new \RuntimeException( sprintf( "Failed to create copy directory '%s': %s. " . __FILE__ . ':' . __LINE__, $cop_file, $error['message'] ) ); | |
} | |
@@ -809,10 +809,10 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
} | |
} | |
- /** | |
- * Get the scenario key used for `$scenario_run_times` array. | |
- * Format "<grandparent-dir> <feature-file>:<line-number>", eg "core-command core-update.feature:221". | |
- */ | |
+ | |
+ | |
+ | |
+ | |
private static function get_scenario_key( $event ) { | |
$scenario_key = ''; | |
if ( $file = self::get_event_file( $event, $line ) ) { | |
@@ -822,21 +822,21 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return $scenario_key; | |
} | |
- /** | |
- * Print out stats on the run times of processes and scenarios. | |
- */ | |
+ | |
+ | |
+ | |
private static function log_run_times_after_suite( $event ) { | |
$suite = ''; | |
if ( self::$scenario_run_times ) { | |
- // Grandparent directory is first part of key. | |
+ | |
$keys = array_keys( self::$scenario_run_times ); | |
$suite = substr( $keys[0], 0, strpos( $keys[0], ' ' ) ); | |
} | |
$run_from = Utils\basename( dirname( dirname( __DIR__ ) ) ); | |
- // Format same as Behat, if have minutes. | |
+ | |
$fmt = function ( $time ) { | |
$mins = floor( $time / 60 ); | |
return round( $time, 3 ) . ( $mins ? ( ' (' . $mins . 'm' . round( $time - ( $mins * 60 ), 3 ) . 's)' ) : '' ); | |
@@ -846,7 +846,7 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
$log = PHP_EOL . str_repeat( '(', 80 ) . PHP_EOL; | |
- // Process and proc method run times. | |
+ | |
$run_times = array_merge( Process::$run_times, self::$proc_method_run_times ); | |
list( $ptime, $calls ) = array_reduce( $run_times, function ( $carry, $item ) { | |
@@ -863,7 +863,7 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
); | |
uasort( $run_times, function ( $a, $b ) { | |
- return $a[0] === $b[0] ? 0 : ( $a[0] < $b[0] ? 1 : -1 ); // Reverse sort. | |
+ return $a[0] === $b[0] ? 0 : ( $a[0] < $b[0] ? 1 : -1 ); | |
} ); | |
$tops = array_slice( $run_times, 0, self::$num_top_processes, true ); | |
@@ -873,7 +873,7 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
return sprintf( ' %3d. %7.3f %3d %s', $i + 1, round( $v[0], 3 ), $v[1], $k ); | |
}, array_keys( $tops ), $tops, array_keys( array_keys( $tops ) ) ) ) . PHP_EOL; | |
- // Scenario run times. | |
+ | |
arsort( self::$scenario_run_times ); | |
$tops = array_slice( self::$scenario_run_times, 0, self::$num_top_scenarios, true ); | |
@@ -892,9 +892,9 @@ class FeatureContext extends BehatContext implements ClosuredContextInterface { | |
} | |
} | |
- /** | |
- * Log the run time of a proc method (one that doesn't use Process but does (use a function that does) a `proc_open()`). | |
- */ | |
+ | |
+ | |
+ | |
private static function log_proc_method_run_time( $key, $start_time ) { | |
$run_time = microtime( true ) - $start_time; | |
if ( ! isset( self::$proc_method_run_times[ $key ] ) ) { | |
diff --git a/features/bootstrap/Process.php b/features/bootstrap/Process.php | |
index 5016580b..fda17256 100644 | |
--- a/features/bootstrap/Process.php | |
+++ b/features/bootstrap/Process.php | |
@@ -2,51 +2,51 @@ | |
namespace WP_CLI; | |
-/** | |
- * Run a system process, and learn what happened. | |
- */ | |
+ | |
+ | |
+ | |
class Process { | |
- /** | |
- * @var string The full command to execute by the system. | |
- */ | |
+ | |
+ | |
+ | |
private $command; | |
- /** | |
- * @var string|null The path of the working directory for the process or NULL if not specified (defaults to current working directory). | |
- */ | |
+ | |
+ | |
+ | |
private $cwd; | |
- /** | |
- * @var array Environment variables to set when running the command. | |
- */ | |
+ | |
+ | |
+ | |
private $env; | |
- /** | |
- * @var array Descriptor spec for `proc_open()`. | |
- */ | |
+ | |
+ | |
+ | |
private static $descriptors = array( | |
0 => STDIN, | |
1 => array( 'pipe', 'w' ), | |
2 => array( 'pipe', 'w' ), | |
); | |
- /** | |
- * @var bool Whether to log run time info or not. | |
- */ | |
+ | |
+ | |
+ | |
public static $log_run_times = false; | |
- /** | |
- * @var array Array of process run time info, keyed by process command, each a 2-element array containing run time and run count. | |
- */ | |
+ | |
+ | |
+ | |
public static $run_times = array(); | |
- /** | |
- * @param string $command Command to execute. | |
- * @param string $cwd Directory to execute the command in. | |
- * @param array $env Environment variables to set when running the command. | |
- * | |
- * @return Process | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
public static function create( $command, $cwd = null, $env = array() ) { | |
$proc = new self; | |
@@ -59,11 +59,11 @@ class Process { | |
private function __construct() {} | |
- /** | |
- * Run the command. | |
- * | |
- * @return ProcessRun | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
public function run() { | |
$start_time = microtime( true ); | |
@@ -100,15 +100,15 @@ class Process { | |
); | |
} | |
- /** | |
- * Run the command, but throw an Exception on error. | |
- * | |
- * @return ProcessRun | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
public function run_check() { | |
$r = $this->run(); | |
- // $r->STDERR is incorrect, but kept incorrect for backwards-compat | |
+ | |
if ( $r->return_code || ! empty( $r->STDERR ) ) { | |
throw new \RuntimeException( $r ); | |
} | |
diff --git a/features/bootstrap/ProcessRun.php b/features/bootstrap/ProcessRun.php | |
index 96b4c80b..1ee16b04 100644 | |
--- a/features/bootstrap/ProcessRun.php | |
+++ b/features/bootstrap/ProcessRun.php | |
@@ -2,61 +2,61 @@ | |
namespace WP_CLI; | |
-/** | |
- * Results of an executed command. | |
- */ | |
+ | |
+ | |
+ | |
class ProcessRun { | |
- /** | |
- * @var string The full command executed by the system. | |
- */ | |
+ | |
+ | |
+ | |
public $command; | |
- /** | |
- * @var string Captured output from the process' STDOUT. | |
- */ | |
+ | |
+ | |
+ | |
public $stdout; | |
- /** | |
- * @var string Captured output from the process' STDERR. | |
- */ | |
+ | |
+ | |
+ | |
public $stderr; | |
- /** | |
- * @var string|null The path of the working directory for the process or NULL if not specified (defaults to current working directory). | |
- */ | |
+ | |
+ | |
+ | |
public $cwd; | |
- /** | |
- * @var array Environment variables set for this process. | |
- */ | |
+ | |
+ | |
+ | |
public $env; | |
- /** | |
- * @var int Exit code of the process. | |
- */ | |
+ | |
+ | |
+ | |
public $return_code; | |
- /** | |
- * @var float The run time of the process. | |
- */ | |
+ | |
+ | |
+ | |
public $run_time; | |
- /** | |
- * @var array $props Properties of executed command. | |
- */ | |
+ | |
+ | |
+ | |
public function __construct( $props ) { | |
foreach ( $props as $key => $value ) { | |
$this->$key = $value; | |
} | |
} | |
- /** | |
- * Return properties of executed command as a string. | |
- * | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
public function __toString() { | |
- $out = "$ $this->command\n"; | |
+ $out = "$ $this->command\n"; | |
$out .= "$this->stdout\n$this->stderr"; | |
$out .= "cwd: $this->cwd\n"; | |
$out .= "run time: $this->run_time\n"; | |
diff --git a/features/bootstrap/support.php b/features/bootstrap/support.php | |
index a37a064f..4df7b790 100644 | |
--- a/features/bootstrap/support.php | |
+++ b/features/bootstrap/support.php | |
@@ -1,6 +1,12 @@ | |
<?php | |
-// Utility functions used by Behat steps | |
+ | |
+ | |
+function assertRegExp( $regex, $actual ) { | |
+ if ( ! preg_match( $regex, $actual ) ) { | |
+ throw new Exception( "Actual value: " . var_export( $actual, true ) ); | |
+ } | |
+} | |
function assertEquals( $expected, $actual ) { | |
if ( $expected != $actual ) { | |
@@ -53,7 +59,7 @@ function checkString( $output, $expected, $action, $message = false ) { | |
} | |
function compareTables( $expected_rows, $actual_rows, $output ) { | |
- // the first row is the header and must be present | |
+ | |
if ( $expected_rows[0] != $actual_rows[0] ) { | |
throw new \Exception( $output ); | |
} | |
@@ -89,41 +95,41 @@ function compareContents( $expected, $actual ) { | |
return true; | |
} | |
-/** | |
- * Compare two strings containing JSON to ensure that @a $actualJson contains at | |
- * least what the JSON string @a $expectedJson contains. | |
- * | |
- * @return whether or not @a $actualJson contains @a $expectedJson | |
- * @retval true @a $actualJson contains @a $expectedJson | |
- * @retval false @a $actualJson does not contain @a $expectedJson | |
- * | |
- * @param[in] $actualJson the JSON string to be tested | |
- * @param[in] $expectedJson the expected JSON string | |
- * | |
- * Examples: | |
- * expected: {'a':1,'array':[1,3,5]} | |
- * | |
- * 1 ) | |
- * actual: {'a':1,'b':2,'c':3,'array':[1,2,3,4,5]} | |
- * return: true | |
- * | |
- * 2 ) | |
- * actual: {'b':2,'c':3,'array':[1,2,3,4,5]} | |
- * return: false | |
- * element 'a' is missing from the root object | |
- * | |
- * 3 ) | |
- * actual: {'a':0,'b':2,'c':3,'array':[1,2,3,4,5]} | |
- * return: false | |
- * the value of element 'a' is not 1 | |
- * | |
- * 4 ) | |
- * actual: {'a':1,'b':2,'c':3,'array':[1,2,4,5]} | |
- * return: false | |
- * the contents of 'array' does not include 3 | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function checkThatJsonStringContainsJsonString( $actualJson, $expectedJson ) { | |
- $actualValue = json_decode( $actualJson ); | |
+ $actualValue = json_decode( $actualJson ); | |
$expectedValue = json_decode( $expectedJson ); | |
if ( !$actualValue ) { | |
@@ -133,26 +139,26 @@ function checkThatJsonStringContainsJsonString( $actualJson, $expectedJson ) { | |
return compareContents( $expectedValue, $actualValue ); | |
} | |
-/** | |
- * Compare two strings to confirm $actualCSV contains $expectedCSV | |
- * Both strings are expected to have headers for their CSVs. | |
- * $actualCSV must match all data rows in $expectedCSV | |
- * | |
- * @param string A CSV string | |
- * @param array A nested array of values | |
- * @return bool Whether $actualCSV contains $expectedCSV | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function checkThatCsvStringContainsValues( $actualCSV, $expectedCSV ) { | |
$actualCSV = array_map( 'str_getcsv', explode( PHP_EOL, $actualCSV ) ); | |
if ( empty( $actualCSV ) ) | |
return false; | |
- // Each sample must have headers | |
+ | |
$actualHeaders = array_values( array_shift( $actualCSV ) ); | |
$expectedHeaders = array_values( array_shift( $expectedCSV ) ); | |
- // Each expectedCSV must exist somewhere in actualCSV in the proper column | |
+ | |
$expectedResult = 0; | |
foreach ( $expectedCSV as $expected_row ) { | |
$expected_row = array_combine( $expectedHeaders, $expected_row ); | |
@@ -170,19 +176,19 @@ function checkThatCsvStringContainsValues( $actualCSV, $expectedCSV ) { | |
return $expectedResult >= count( $expectedCSV ); | |
} | |
-/** | |
- * Compare two strings containing YAML to ensure that @a $actualYaml contains at | |
- * least what the YAML string @a $expectedYaml contains. | |
- * | |
- * @return whether or not @a $actualYaml contains @a $expectedJson | |
- * @retval true @a $actualYaml contains @a $expectedJson | |
- * @retval false @a $actualYaml does not contain @a $expectedJson | |
- * | |
- * @param[in] $actualYaml the YAML string to be tested | |
- * @param[in] $expectedYaml the expected YAML string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function checkThatYamlStringContainsYamlString( $actualYaml, $expectedYaml ) { | |
- $actualValue = Mustangostang\Spyc::YAMLLoad( $actualYaml ); | |
+ $actualValue = Mustangostang\Spyc::YAMLLoad( $actualYaml ); | |
$expectedValue = Mustangostang\Spyc::YAMLLoad( $expectedYaml ); | |
if ( !$actualValue ) { | |
diff --git a/features/bootstrap/utils.php b/features/bootstrap/utils.php | |
index 32caac15..2989254e 100644 | |
--- a/features/bootstrap/utils.php | |
+++ b/features/bootstrap/utils.php | |
@@ -1,6 +1,6 @@ | |
<?php | |
-// Utilities that do NOT depend on WordPress code. | |
+ | |
namespace WP_CLI\Utils; | |
@@ -16,7 +16,7 @@ function inside_phar() { | |
return 0 === strpos( WP_CLI_ROOT, PHAR_STREAM_PREFIX ); | |
} | |
-// Files that need to be read by external programs have to be extracted from the Phar archive. | |
+ | |
function extract_from_phar( $path ) { | |
if ( ! inside_phar() ) { | |
return $path; | |
@@ -30,7 +30,9 @@ function extract_from_phar( $path ) { | |
register_shutdown_function( | |
function() use ( $tmp_path ) { | |
- @unlink( $tmp_path ); | |
+ if ( file_exists( $tmp_path ) ) { | |
+ unlink( $tmp_path ); | |
+ } | |
} | |
); | |
@@ -58,15 +60,15 @@ function load_dependencies() { | |
} | |
if ( ! $has_autoload ) { | |
- fputs( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" ); | |
+ fwrite( STDERR, "Internal error: Can't find Composer autoloader.\nTry running: composer install\n" ); | |
exit( 3 ); | |
} | |
} | |
function get_vendor_paths() { | |
$vendor_paths = array( | |
- WP_CLI_ROOT . '/../../../vendor', // part of a larger project / installed via Composer (preferred) | |
- WP_CLI_ROOT . '/vendor', // top-level project / installed as Git clone | |
+ WP_CLI_ROOT . '/../../../vendor', | |
+ WP_CLI_ROOT . '/vendor', | |
); | |
$maybe_composer_json = WP_CLI_ROOT . '/../../../composer.json'; | |
if ( file_exists( $maybe_composer_json ) && is_readable( $maybe_composer_json ) ) { | |
@@ -78,7 +80,7 @@ function get_vendor_paths() { | |
return $vendor_paths; | |
} | |
-// Using require() directly inside a class grants access to private methods to the loaded code | |
+ | |
function load_file( $path ) { | |
require_once $path; | |
} | |
@@ -91,25 +93,25 @@ function load_command( $name ) { | |
} | |
} | |
-/** | |
- * Like array_map(), except it returns a new iterator, instead of a modified array. | |
- * | |
- * Example: | |
- * | |
- * $arr = array('Football', 'Socker'); | |
- * | |
- * $it = iterator_map($arr, 'strtolower', function($val) { | |
- * return str_replace('foo', 'bar', $val); | |
- * }); | |
- * | |
- * foreach ( $it as $val ) { | |
- * var_dump($val); | |
- * } | |
- * | |
- * @param array|object Either a plain array or another iterator | |
- * @param callback The function to apply to an element | |
- * @return object An iterator that applies the given callback(s) | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function iterator_map( $it, $fn ) { | |
if ( is_array( $it ) ) { | |
$it = new \ArrayIterator( $it ); | |
@@ -126,20 +128,20 @@ function iterator_map( $it, $fn ) { | |
return $it; | |
} | |
-/** | |
- * Search for file by walking up the directory tree until the first file is found or until $stop_check($dir) returns true | |
- * @param string|array The files (or file) to search for | |
- * @param string|null The directory to start searching from; defaults to CWD | |
- * @param callable Function which is passed the current dir each time a directory level is traversed | |
- * @return null|string Null if the file was not found | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function find_file_upward( $files, $dir = null, $stop_check = null ) { | |
$files = (array) $files; | |
if ( is_null( $dir ) ) { | |
$dir = getcwd(); | |
} | |
- while ( @is_readable( $dir ) ) { | |
- // Stop walking up when the supplied callable returns true being passed the $dir | |
+ while ( is_readable( $dir ) ) { | |
+ | |
if ( is_callable( $stop_check ) && call_user_func( $stop_check, $dir ) ) { | |
return null; | |
} | |
@@ -161,30 +163,30 @@ function find_file_upward( $files, $dir = null, $stop_check = null ) { | |
} | |
function is_path_absolute( $path ) { | |
- // Windows | |
+ | |
if ( isset( $path[1] ) && ':' === $path[1] ) { | |
return true; | |
} | |
- return $path[0] === '/'; | |
+ return '/' === $path[0]; | |
} | |
-/** | |
- * Composes positional arguments into a command string. | |
- * | |
- * @param array | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function args_to_str( $args ) { | |
return ' ' . implode( ' ', array_map( 'escapeshellarg', $args ) ); | |
} | |
-/** | |
- * Composes associative arguments into a command string. | |
- * | |
- * @param array | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function assoc_args_to_str( $assoc_args ) { | |
$str = ''; | |
@@ -207,10 +209,10 @@ function assoc_args_to_str( $assoc_args ) { | |
return $str; | |
} | |
-/** | |
- * Given a template string and an arbitrary number of arguments, | |
- * returns the final command, with the parameters escaped. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
function esc_cmd( $cmd ) { | |
if ( func_num_args() < 2 ) { | |
trigger_error( 'esc_cmd() requires at least two arguments.', E_USER_WARNING ); | |
@@ -227,12 +229,12 @@ function locate_wp_config() { | |
static $path; | |
if ( null === $path ) { | |
+ $path = false; | |
+ | |
if ( file_exists( ABSPATH . 'wp-config.php' ) ) { | |
$path = ABSPATH . 'wp-config.php'; | |
} elseif ( file_exists( ABSPATH . '../wp-config.php' ) && ! file_exists( ABSPATH . '/../wp-settings.php' ) ) { | |
$path = ABSPATH . '../wp-config.php'; | |
- } else { | |
- $path = false; | |
} | |
if ( $path ) { | |
@@ -244,69 +246,71 @@ function locate_wp_config() { | |
} | |
function wp_version_compare( $since, $operator ) { | |
- return version_compare( str_replace( array( '-src' ), '', $GLOBALS['wp_version'] ), $since, $operator ); | |
+ $wp_version = str_replace( '-src', '', $GLOBALS['wp_version'] ); | |
+ $since = str_replace( '-src', '', $since ); | |
+ return version_compare( $wp_version, $since, $operator ); | |
} | |
-/** | |
- * Render a collection of items as an ASCII table, JSON, CSV, YAML, list of ids, or count. | |
- * | |
- * Given a collection of items with a consistent data structure: | |
- * | |
- * ``` | |
- * $items = array( | |
- * array( | |
- * 'key' => 'foo', | |
- * 'value' => 'bar', | |
- * ) | |
- * ); | |
- * ``` | |
- * | |
- * Render `$items` as an ASCII table: | |
- * | |
- * ``` | |
- * WP_CLI\Utils\format_items( 'table', $items, array( 'key', 'value' ) ); | |
- * | |
- * # +-----+-------+ | |
- * # | key | value | | |
- * # +-----+-------+ | |
- * # | foo | bar | | |
- * # +-----+-------+ | |
- * ``` | |
- * | |
- * Or render `$items` as YAML: | |
- * | |
- * ``` | |
- * WP_CLI\Utils\format_items( 'yaml', $items, array( 'key', 'value' ) ); | |
- * | |
- * # --- | |
- * # - | |
- * # key: foo | |
- * # value: bar | |
- * ``` | |
- * | |
- * @access public | |
- * @category Output | |
- * | |
- * @param string $format Format to use: 'table', 'json', 'csv', 'yaml', 'ids', 'count' | |
- * @param array $items An array of items to output. | |
- * @param array|string $fields Named fields for each item of data. Can be array or comma-separated list. | |
- * @return null | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function format_items( $format, $items, $fields ) { | |
$assoc_args = compact( 'format', 'fields' ); | |
$formatter = new \WP_CLI\Formatter( $assoc_args ); | |
$formatter->display_items( $items ); | |
} | |
-/** | |
- * Write data as CSV to a given file. | |
- * | |
- * @access public | |
- * | |
- * @param resource $fd File descriptor | |
- * @param array $rows Array of rows to output | |
- * @param array $headers List of CSV columns (optional) | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function write_csv( $fd, $rows, $headers = array() ) { | |
if ( ! empty( $headers ) ) { | |
fputcsv( $fd, $headers ); | |
@@ -321,13 +325,13 @@ function write_csv( $fd, $rows, $headers = array() ) { | |
} | |
} | |
-/** | |
- * Pick fields from an associative array or object. | |
- * | |
- * @param array|object Associative array or object to pick fields from | |
- * @param array List of fields to pick | |
- * @return array | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function pick_fields( $item, $fields ) { | |
$item = (object) $item; | |
@@ -340,15 +344,15 @@ function pick_fields( $item, $fields ) { | |
return $values; | |
} | |
-/** | |
- * Launch system's $EDITOR for the user to edit some text. | |
- * | |
- * @access public | |
- * @category Input | |
- * | |
- * @param string $content Some form of text to edit (e.g. post content) | |
- * @return string|bool Edited text, if file is saved from editor; false, if no change to file. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { | |
check_proc_available( 'launch_editor_for_input' ); | |
@@ -358,9 +362,9 @@ function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { | |
do { | |
$tmpfile = basename( $filename ); | |
$tmpfile = preg_replace( '|\.[^.]*$|', '', $tmpfile ); | |
- $tmpfile .= '-' . substr( md5( rand() ), 0, 6 ); | |
+ $tmpfile .= '-' . substr( md5( mt_rand() ), 0, 6 ); | |
$tmpfile = $tmpdir . $tmpfile . '.tmp'; | |
- $fp = @fopen( $tmpfile, 'x' ); | |
+ $fp = fopen( $tmpfile, 'xb' ); | |
if ( ! $fp && is_writable( $tmpdir ) && file_exists( $tmpfile ) ) { | |
$tmpfile = ''; | |
continue; | |
@@ -379,10 +383,10 @@ function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { | |
$editor = getenv( 'EDITOR' ); | |
if ( ! $editor ) { | |
+ $editor = 'vi'; | |
+ | |
if ( isset( $_SERVER['OS'] ) && false !== strpos( $_SERVER['OS'], 'indows' ) ) { | |
$editor = 'notepad'; | |
- } else { | |
- $editor = 'vi'; | |
} | |
} | |
@@ -404,21 +408,21 @@ function launch_editor_for_input( $input, $filename = 'WP-CLI' ) { | |
return $output; | |
} | |
-/** | |
- * @param string MySQL host string, as defined in wp-config.php | |
- * @return array | |
- */ | |
+ | |
+ | |
+ | |
+ | |
function mysql_host_to_cli_args( $raw_host ) { | |
$assoc_args = array(); | |
- $host_parts = explode( ':', $raw_host ); | |
+ $host_parts = explode( ':', $raw_host ); | |
if ( count( $host_parts ) == 2 ) { | |
list( $assoc_args['host'], $extra ) = $host_parts; | |
$extra = trim( $extra ); | |
if ( is_numeric( $extra ) ) { | |
- $assoc_args['port'] = intval( $extra ); | |
+ $assoc_args['port'] = (int) $extra; | |
$assoc_args['protocol'] = 'tcp'; | |
- } elseif ( $extra !== '' ) { | |
+ } elseif ( '' !== $extra ) { | |
$assoc_args['socket'] = $extra; | |
} | |
} else { | |
@@ -436,7 +440,9 @@ function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) { | |
} | |
if ( isset( $assoc_args['host'] ) ) { | |
+ | |
$assoc_args = array_merge( $assoc_args, mysql_host_to_cli_args( $assoc_args['host'] ) ); | |
+ | |
} | |
$pass = $assoc_args['pass']; | |
@@ -461,11 +467,11 @@ function run_mysql_command( $cmd, $assoc_args, $descriptors = null ) { | |
} | |
} | |
-/** | |
- * Render PHP or other types of files using Mustache templates. | |
- * | |
- * IMPORTANT: Automatic HTML escaping is disabled! | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function mustache_render( $template_name, $data = array() ) { | |
if ( ! file_exists( $template_name ) ) { | |
$template_name = WP_CLI_ROOT . "/templates/$template_name"; | |
@@ -483,34 +489,34 @@ function mustache_render( $template_name, $data = array() ) { | |
return $m->render( $template, $data ); | |
} | |
-/** | |
- * Create a progress bar to display percent completion of a given operation. | |
- * | |
- * Progress bar is written to STDOUT, and disabled when command is piped. Progress | |
- * advances with `$progress->tick()`, and completes with `$progress->finish()`. | |
- * Process bar also indicates elapsed time and expected total time. | |
- * | |
- * ``` | |
- * # `wp user generate` ticks progress bar each time a new user is created. | |
- * # | |
- * # $ wp user generate --count=500 | |
- * # Generating users 22 % [=======> ] 0:05 / 0:23 | |
- * | |
- * $progress = \WP_CLI\Utils\make_progress_bar( 'Generating users', $count ); | |
- * for ( $i = 0; $i < $count; $i++ ) { | |
- * // uses wp_insert_user() to insert the user | |
- * $progress->tick(); | |
- * } | |
- * $progress->finish(); | |
- * ``` | |
- * | |
- * @access public | |
- * @category Output | |
- * | |
- * @param string $message Text to display before the progress bar. | |
- * @param integer $count Total number of ticks to be performed. | |
- * @return cli\progress\Bar|WP_CLI\NoOp | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function make_progress_bar( $message, $count ) { | |
if ( \cli\Shell::isPiped() ) { | |
return new \WP_CLI\NoOp; | |
@@ -529,25 +535,25 @@ function parse_url( $url ) { | |
return $url_parts; | |
} | |
-/** | |
- * Check if we're running in a Windows environment (cmd.exe). | |
- * | |
- * @return bool | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function is_windows() { | |
return false !== ( $test_is_windows = getenv( 'WP_CLI_TEST_IS_WINDOWS' ) ) ? (bool) $test_is_windows : strtoupper( substr( PHP_OS, 0, 3 ) ) === 'WIN'; | |
} | |
-/** | |
- * Replace magic constants in some PHP source code. | |
- * | |
- * @param string $source The PHP code to manipulate. | |
- * @param string $path The path to use instead of the magic constants | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function replace_path_consts( $source, $path ) { | |
$replacements = array( | |
'__FILE__' => "'$path'", | |
- '__DIR__' => "'" . dirname( $path ) . "'", | |
+ '__DIR__' => "'" . dirname( $path ) . "'", | |
); | |
$old = array_keys( $replacements ); | |
@@ -556,33 +562,34 @@ function replace_path_consts( $source, $path ) { | |
return str_replace( $old, $new, $source ); | |
} | |
-/** | |
- * Make a HTTP request to a remote URL. | |
- * | |
- * Wraps the Requests HTTP library to ensure every request includes a cert. | |
- * | |
- * ``` | |
- * # `wp core download` verifies the hash for a downloaded WordPress archive | |
- * | |
- * $md5_response = Utils\http_request( 'GET', $download_url . '.md5' ); | |
- * if ( 20 != substr( $md5_response->status_code, 0, 2 ) ) { | |
- * WP_CLI::error( "Couldn't access md5 hash for release (HTTP code {$response->status_code})" ); | |
- * } | |
- * ``` | |
- * | |
- * @access public | |
- * | |
- * @param string $method HTTP method (GET, POST, DELETE, etc.) | |
- * @param string $url URL to make the HTTP request to. | |
- * @param array $headers Add specific headers to the request. | |
- * @param array $options | |
- * @return object | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function http_request( $method, $url, $data = null, $headers = array(), $options = array() ) { | |
$cert_path = '/rmccue/requests/library/Requests/Transport/cacert.pem'; | |
+ $halt_on_error = ! isset( $options['halt_on_error'] ) || (bool) $options['halt_on_error']; | |
if ( inside_phar() ) { | |
- // cURL can't read Phar archives | |
+ | |
$options['verify'] = extract_from_phar( | |
WP_CLI_VENDOR_DIR . $cert_path | |
); | |
@@ -594,61 +601,72 @@ function http_request( $method, $url, $data = null, $headers = array(), $options | |
} | |
} | |
if ( empty( $options['verify'] ) ) { | |
- WP_CLI::error( 'Cannot find SSL certificate.' ); | |
+ $error_msg = 'Cannot find SSL certificate.'; | |
+ if ( $halt_on_error ) { | |
+ WP_CLI::error( $error_msg ); | |
+ } | |
+ throw new \RuntimeException( $error_msg ); | |
} | |
} | |
try { | |
- $request = \Requests::request( $url, $headers, $data, $method, $options ); | |
- return $request; | |
+ return \Requests::request( $url, $headers, $data, $method, $options ); | |
} catch ( \Requests_Exception $ex ) { | |
- // CURLE_SSL_CACERT_BADFILE only defined for PHP >= 7. | |
- if ( 'curlerror' !== $ex->getType() || ! in_array( curl_errno( $ex->getData() ), array( CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 /*CURLE_SSL_CACERT_BADFILE*/ ), true ) ) { | |
- \WP_CLI::error( sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ) ); | |
+ | |
+ if ( 'curlerror' !== $ex->getType() || ! in_array( curl_errno( $ex->getData() ), array( CURLE_SSL_CONNECT_ERROR, CURLE_SSL_CERTPROBLEM, 77 ), true ) ) { | |
+ $error_msg = sprintf( "Failed to get url '%s': %s.", $url, $ex->getMessage() ); | |
+ if ( $halt_on_error ) { | |
+ WP_CLI::error( $error_msg ); | |
+ } | |
+ throw new \RuntimeException( $error_msg, null, $ex ); | |
} | |
- // Handle SSL certificate issues gracefully | |
+ | |
\WP_CLI::warning( sprintf( "Re-trying without verify after failing to get verified url '%s' %s.", $url, $ex->getMessage() ) ); | |
$options['verify'] = false; | |
try { | |
return \Requests::request( $url, $headers, $data, $method, $options ); | |
} catch ( \Requests_Exception $ex ) { | |
- \WP_CLI::error( sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ) ); | |
+ $error_msg = sprintf( "Failed to get non-verified url '%s' %s.", $url, $ex->getMessage() ); | |
+ if ( $halt_on_error ) { | |
+ WP_CLI::error( $error_msg ); | |
+ } | |
+ throw new \RuntimeException( $error_msg, null, $ex ); | |
} | |
} | |
} | |
-/** | |
- * Increments a version string using the "x.y.z-pre" format | |
- * | |
- * Can increment the major, minor or patch number by one | |
- * If $new_version == "same" the version string is not changed | |
- * If $new_version is not a known keyword, it will be used as the new version string directly | |
- * | |
- * @param string $current_version | |
- * @param string $new_version | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function increment_version( $current_version, $new_version ) { | |
- // split version assuming the format is x.y.z-pre | |
- $current_version = explode( '-', $current_version, 2 ); | |
+ | |
+ $current_version = explode( '-', $current_version, 2 ); | |
$current_version[0] = explode( '.', $current_version[0] ); | |
switch ( $new_version ) { | |
case 'same': | |
- // do nothing | |
+ | |
break; | |
case 'patch': | |
$current_version[0][2]++; | |
- $current_version = array( $current_version[0] ); // drop possible pre-release info | |
+ $current_version = array( $current_version[0] ); | |
break; | |
case 'minor': | |
$current_version[0][1]++; | |
$current_version[0][2] = 0; | |
- $current_version = array( $current_version[0] ); // drop possible pre-release info | |
+ $current_version = array( $current_version[0] ); | |
break; | |
case 'major': | |
@@ -656,30 +674,30 @@ function increment_version( $current_version, $new_version ) { | |
$current_version[0][1] = 0; | |
$current_version[0][2] = 0; | |
- $current_version = array( $current_version[0] ); // drop possible pre-release info | |
+ $current_version = array( $current_version[0] ); | |
break; | |
- default: // not a keyword | |
+ default: | |
$current_version = array( array( $new_version ) ); | |
break; | |
} | |
- // reconstruct version string | |
+ | |
$current_version[0] = implode( '.', $current_version[0] ); | |
- $current_version = implode( '-', $current_version ); | |
+ $current_version = implode( '-', $current_version ); | |
return $current_version; | |
} | |
-/** | |
- * Compare two version strings to get the named semantic version. | |
- * | |
- * @access public | |
- * | |
- * @param string $new_version | |
- * @param string $original_version | |
- * @return string $name 'major', 'minor', 'patch' | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function get_named_sem_ver( $new_version, $original_version ) { | |
if ( ! Comparator::greaterThan( $new_version, $original_version ) ) { | |
@@ -698,71 +716,73 @@ function get_named_sem_ver( $new_version, $original_version ) { | |
if ( ! is_null( $minor ) && Semver::satisfies( $new_version, "{$major}.{$minor}.x" ) ) { | |
return 'patch'; | |
- } elseif ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { | |
+ } | |
+ | |
+ if ( Semver::satisfies( $new_version, "{$major}.x.x" ) ) { | |
return 'minor'; | |
- } else { | |
- return 'major'; | |
} | |
+ | |
+ return 'major'; | |
} | |
-/** | |
- * Return the flag value or, if it's not set, the $default value. | |
- * | |
- * Because flags can be negated (e.g. --no-quiet to negate --quiet), this | |
- * function provides a safer alternative to using | |
- * `isset( $assoc_args['quiet'] )` or similar. | |
- * | |
- * @access public | |
- * @category Input | |
- * | |
- * @param array $assoc_args Arguments array. | |
- * @param string $flag Flag to get the value. | |
- * @param mixed $default Default value for the flag. Default: NULL | |
- * @return mixed | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function get_flag_value( $assoc_args, $flag, $default = null ) { | |
return isset( $assoc_args[ $flag ] ) ? $assoc_args[ $flag ] : $default; | |
} | |
-/** | |
- * Get the home directory. | |
- * | |
- * @access public | |
- * @category System | |
- * | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function get_home_dir() { | |
$home = getenv( 'HOME' ); | |
if ( ! $home ) { | |
- // In Windows $HOME may not be defined | |
+ | |
$home = getenv( 'HOMEDRIVE' ) . getenv( 'HOMEPATH' ); | |
} | |
return rtrim( $home, '/\\' ); | |
} | |
-/** | |
- * Appends a trailing slash. | |
- * | |
- * @access public | |
- * @category System | |
- * | |
- * @param string $string What to add the trailing slash to. | |
- * @return string String with trailing slash added. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function trailingslashit( $string ) { | |
return rtrim( $string, '/\\' ) . '/'; | |
} | |
-/** | |
- * Get the system's temp directory. Warns user if it isn't writable. | |
- * | |
- * @access public | |
- * @category System | |
- * | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function get_temp_dir() { | |
static $temp = ''; | |
@@ -770,35 +790,35 @@ function get_temp_dir() { | |
return $temp; | |
} | |
- // `sys_get_temp_dir()` introduced PHP 5.2.1. | |
+ $temp = '/tmp/'; | |
+ | |
+ | |
if ( $try = sys_get_temp_dir() ) { | |
$temp = trailingslashit( $try ); | |
} elseif ( $try = ini_get( 'upload_tmp_dir' ) ) { | |
$temp = trailingslashit( $try ); | |
- } else { | |
- $temp = '/tmp/'; | |
} | |
- if ( ! @is_writable( $temp ) ) { | |
+ if ( ! is_writable( $temp ) ) { | |
\WP_CLI::warning( "Temp directory isn't writable: {$temp}" ); | |
} | |
return $temp; | |
} | |
-/** | |
- * Parse a SSH url for its host, port, and path. | |
- * | |
- * Similar to parse_url(), but adds support for defined SSH aliases. | |
- * | |
- * ``` | |
- * host OR host/path/to/wordpress OR host:port/path/to/wordpress | |
- * ``` | |
- * | |
- * @access public | |
- * | |
- * @return mixed | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function parse_ssh_url( $url, $component = -1 ) { | |
preg_match( '#^((docker|docker\-compose|ssh|vagrant):)?(([^@:]+)@)?([^:/~]+)(:([\d]*))?((/|~)(.+))?$#', $url, $matches ); | |
$bits = array(); | |
@@ -829,31 +849,34 @@ function parse_ssh_url( $url, $component = -1 ) { | |
} | |
} | |
-/** | |
- * Report the results of the same operation against multiple resources. | |
- * | |
- * @access public | |
- * @category Input | |
- * | |
- * @param string $noun Resource being affected (e.g. plugin) | |
- * @param string $verb Type of action happening to the noun (e.g. activate) | |
- * @param integer $total Total number of resource being affected. | |
- * @param integer $successes Number of successful operations. | |
- * @param integer $failures Number of failures. | |
- */ | |
-function report_batch_operation_results( $noun, $verb, $total, $successes, $failures ) { | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+function report_batch_operation_results( $noun, $verb, $total, $successes, $failures, $skips = null ) { | |
$plural_noun = $noun . 's'; | |
$past_tense_verb = past_tense_verb( $verb ); | |
$past_tense_verb_upper = ucfirst( $past_tense_verb ); | |
if ( $failures ) { | |
+ $failed_skipped_message = null === $skips ? '' : " ({$failures} failed" . ( $skips ? ", {$skips} skipped" : '' ) . ')'; | |
if ( $successes ) { | |
- WP_CLI::error( "Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}." ); | |
+ WP_CLI::error( "Only {$past_tense_verb} {$successes} of {$total} {$plural_noun}{$failed_skipped_message}." ); | |
} else { | |
- WP_CLI::error( "No {$plural_noun} {$past_tense_verb}." ); | |
+ WP_CLI::error( "No {$plural_noun} {$past_tense_verb}{$failed_skipped_message}." ); | |
} | |
} else { | |
- if ( $successes ) { | |
- WP_CLI::success( "{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}." ); | |
+ $skipped_message = $skips ? " ({$skips} skipped)" : ''; | |
+ if ( $successes || $skips ) { | |
+ WP_CLI::success( "{$past_tense_verb_upper} {$successes} of {$total} {$plural_noun}{$skipped_message}." ); | |
} else { | |
$message = $total > 1 ? ucfirst( $plural_noun ) : ucfirst( $noun ); | |
WP_CLI::success( "{$message} already {$past_tense_verb}." ); | |
@@ -861,22 +884,22 @@ function report_batch_operation_results( $noun, $verb, $total, $successes, $fail | |
} | |
} | |
-/** | |
- * Parse a string of command line arguments into an $argv-esqe variable. | |
- * | |
- * @access public | |
- * @category Input | |
- * | |
- * @param string $arguments | |
- * @return array | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function parse_str_to_argv( $arguments ) { | |
preg_match_all( '/(?<=^|\s)([\'"]?)(.+?)(?<!\\\\)\1(?=$|\s)/', $arguments, $matches ); | |
$argv = isset( $matches[0] ) ? $matches[0] : array(); | |
$argv = array_map( | |
function( $arg ) { | |
foreach ( array( '"', "'" ) as $char ) { | |
- if ( $char === substr( $arg, 0, 1 ) && $char === substr( $arg, -1 ) ) { | |
+ if ( substr( $arg, 0, 1 ) === $char && substr( $arg, -1 ) === $char ) { | |
$arg = substr( $arg, 1, -1 ); | |
break; | |
} | |
@@ -887,57 +910,58 @@ function parse_str_to_argv( $arguments ) { | |
return $argv; | |
} | |
-/** | |
- * Locale-independent version of basename() | |
- * | |
- * @access public | |
- * | |
- * @param string $path | |
- * @param string $suffix | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function basename( $path, $suffix = '' ) { | |
return urldecode( \basename( str_replace( array( '%2F', '%5C' ), '/', urlencode( $path ) ), $suffix ) ); | |
} | |
-/** | |
- * Checks whether the output of the current script is a TTY or a pipe / redirect | |
- * | |
- * Returns true if STDOUT output is being redirected to a pipe or a file; false is | |
- * output is being sent directly to the terminal. | |
- * | |
- * If an env variable SHELL_PIPE exists, returned result depends it's | |
- * value. Strings like 1, 0, yes, no, that validate to booleans are accepted. | |
- * | |
- * To enable ASCII formatting even when shell is piped, use the | |
- * ENV variable SHELL_PIPE=0 | |
- * | |
- * @access public | |
- * | |
- * @return bool | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function isPiped() { | |
$shellPipe = getenv( 'SHELL_PIPE' ); | |
- if ( $shellPipe !== false ) { | |
+ if ( false !== $shellPipe ) { | |
return filter_var( $shellPipe, FILTER_VALIDATE_BOOLEAN ); | |
- } else { | |
- return (function_exists( 'posix_isatty' ) && ! posix_isatty( STDOUT )); | |
} | |
+ | |
+ return (function_exists( 'posix_isatty' ) && ! posix_isatty( STDOUT )); | |
} | |
-/** | |
- * Expand within paths to their matching paths. | |
- * | |
- * Has no effect on paths which do not use glob patterns. | |
- * | |
- * @param string|array $paths Single path as a string, or an array of paths. | |
- * @param int $flags Optional. Flags to pass to glob. Defaults to GLOB_BRACE. | |
- * | |
- * @return array Expanded paths. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function expand_globs( $paths, $flags = 'default' ) { | |
- // Compatibility for systems without GLOB_BRACE. | |
+ | |
$glob_func = 'glob'; | |
if ( 'default' === $flags ) { | |
if ( ! defined( 'GLOB_BRACE' ) || getenv( 'WP_CLI_TEST_EXPAND_GLOBS_NO_GLOB_BRACE' ) ) { | |
@@ -980,10 +1004,10 @@ function glob_brace( $pattern, $dummy_flags = null ) { | |
static $next_brace_sub; | |
if ( ! $next_brace_sub ) { | |
- // Find the end of the subpattern in a brace expression. | |
+ | |
$next_brace_sub = function ( $pattern, $current ) { | |
- $length = strlen( $pattern ); | |
- $depth = 0; | |
+ $length = strlen( $pattern ); | |
+ $depth = 0; | |
while ( $current < $length ) { | |
if ( '\\' === $pattern[ $current ] ) { | |
@@ -992,9 +1016,11 @@ function glob_brace( $pattern, $dummy_flags = null ) { | |
} | |
$current++; | |
} else { | |
- if ( ( '}' === $pattern[ $current ] && $depth-- === 0 ) || ( ',' === $pattern[ $current ] && 0 === $depth ) ) { | |
+ if ( ( '}' === $pattern[ $current ] && 0 === $depth-- ) || ( ',' === $pattern[ $current ] && 0 === $depth ) ) { | |
break; | |
- } elseif ( '{' === $pattern[ $current++ ] ) { | |
+ } | |
+ | |
+ if ( '{' === $pattern[ $current++ ] ) { | |
$depth++; | |
} | |
} | |
@@ -1006,7 +1032,7 @@ function glob_brace( $pattern, $dummy_flags = null ) { | |
$length = strlen( $pattern ); | |
- // Find first opening brace. | |
+ | |
for ( $begin = 0; $begin < $length; $begin++ ) { | |
if ( '\\' === $pattern[ $begin ] ) { | |
$begin++; | |
@@ -1015,14 +1041,14 @@ function glob_brace( $pattern, $dummy_flags = null ) { | |
} | |
} | |
- // Find comma or matching closing brace. | |
+ | |
if ( null === ( $next = $next_brace_sub( $pattern, $begin + 1 ) ) ) { | |
return glob( $pattern ); | |
} | |
$rest = $next; | |
- // Point `$rest` to matching closing brace. | |
+ | |
while ( '}' !== $pattern[ $rest ] ) { | |
if ( null === ( $rest = $next_brace_sub( $pattern, $rest + 1 ) ) ) { | |
return glob( $pattern ); | |
@@ -1032,13 +1058,13 @@ function glob_brace( $pattern, $dummy_flags = null ) { | |
$paths = array(); | |
$p = $begin + 1; | |
- // For each comma-separated subpattern. | |
+ | |
do { | |
$subpattern = substr( $pattern, 0, $begin ) | |
. substr( $pattern, $p, $next - $p ) | |
. substr( $pattern, $rest + 1 ); | |
- if ( ( $result = glob_brace( $subpattern ) ) ) { | |
+ if ( $result = glob_brace( $subpattern ) ) { | |
$paths = array_merge( $paths, $result ); | |
} | |
@@ -1046,30 +1072,55 @@ function glob_brace( $pattern, $dummy_flags = null ) { | |
break; | |
} | |
- $p = $next + 1; | |
+ $p = $next + 1; | |
$next = $next_brace_sub( $pattern, $p ); | |
} while ( null !== $next ); | |
return array_values( array_unique( $paths ) ); | |
} | |
-/** | |
- * Get the closest suggestion for a mis-typed target term amongst a list of | |
- * options. | |
- * | |
- * Uses the Levenshtein algorithm to calculate the relative "distance" between | |
- * terms. | |
- * | |
- * If the "distance" to the closest term is higher than the threshold, an empty | |
- * string is returned. | |
- * | |
- * @param string $target Target term to get a suggestion for. | |
- * @param array $options Array with possible options. | |
- * @param int $threshold Threshold above which to return an empty string. | |
- * | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function get_suggestion( $target, array $options, $threshold = 2 ) { | |
+ | |
+ $suggestion_map = array( | |
+ 'check' => 'check-update', | |
+ 'clear' => 'flush', | |
+ 'decrement' => 'decr', | |
+ 'del' => 'delete', | |
+ 'directory' => 'dir', | |
+ 'exec' => 'eval', | |
+ 'exec-file' => 'eval-file', | |
+ 'increment' => 'incr', | |
+ 'language' => 'locale', | |
+ 'lang' => 'locale', | |
+ 'new' => 'create', | |
+ 'number' => 'count', | |
+ 'remove' => 'delete', | |
+ 'regen' => 'regenerate', | |
+ 'rep' => 'replace', | |
+ 'repl' => 'replace', | |
+ 'v' => 'version', | |
+ ); | |
+ | |
+ if ( array_key_exists( $target, $suggestion_map ) ) { | |
+ return $suggestion_map[ $target ]; | |
+ } | |
+ | |
if ( empty( $options ) ) { | |
return ''; | |
} | |
@@ -1078,31 +1129,31 @@ function get_suggestion( $target, array $options, $threshold = 2 ) { | |
$levenshtein[ $option ] = $distance; | |
} | |
- // Sort known command strings by distance to user entry. | |
+ | |
asort( $levenshtein ); | |
- // Fetch the closest command string. | |
+ | |
reset( $levenshtein ); | |
$suggestion = key( $levenshtein ); | |
- // Only return a suggestion if below a given threshold. | |
+ | |
return $levenshtein[ $suggestion ] <= $threshold && $suggestion !== $target | |
? (string) $suggestion | |
: ''; | |
} | |
-/** | |
- * Get a Phar-safe version of a path. | |
- * | |
- * For paths inside a Phar, this strips the outer filesystem's location to | |
- * reduce the path to what it needs to be within the Phar archive. | |
- * | |
- * Use the __FILE__ or __DIR__ constants as a starting point. | |
- * | |
- * @param string $path An absolute path that might be within a Phar. | |
- * | |
- * @return string A Phar-safe version of the path. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function phar_safe_path( $path ) { | |
if ( ! inside_phar() ) { | |
@@ -1116,17 +1167,17 @@ function phar_safe_path( $path ) { | |
); | |
} | |
-/** | |
- * Check whether a given Command object is part of the bundled set of | |
- * commands. | |
- * | |
- * This function accepts both a fully qualified class name as a string as | |
- * well as an object that extends `WP_CLI\Dispatcher\CompositeCommand`. | |
- * | |
- * @param \WP_CLI\Dispatcher\CompositeCommand|string $command | |
- * | |
- * @return bool | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function is_bundled_command( $command ) { | |
static $classes; | |
@@ -1147,14 +1198,14 @@ function is_bundled_command( $command ) { | |
: false; | |
} | |
-/** | |
- * Maybe prefix command string with "/usr/bin/env". | |
- * Removes (if there) if Windows, adds (if not there) if not. | |
- * | |
- * @param string $command | |
- * | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function force_env_on_nix_systems( $command ) { | |
$env_prefix = '/usr/bin/env '; | |
$env_prefix_len = strlen( $env_prefix ); | |
@@ -1170,14 +1221,14 @@ function force_env_on_nix_systems( $command ) { | |
return $command; | |
} | |
-/** | |
- * Check that `proc_open()` and `proc_close()` haven't been disabled. | |
- * | |
- * @param string $context Optional. If set will appear in error message. Default null. | |
- * @param bool $return Optional. If set will return false rather than error out. Default false. | |
- * | |
- * @return bool | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function check_proc_available( $context = null, $return = false ) { | |
if ( ! function_exists( 'proc_open' ) || ! function_exists( 'proc_close' ) ) { | |
if ( $return ) { | |
@@ -1193,13 +1244,13 @@ function check_proc_available( $context = null, $return = false ) { | |
return true; | |
} | |
-/** | |
- * Returns past tense of verb, with limited accuracy. Only regular verbs catered for, apart from "reset". | |
- * | |
- * @param string $verb Verb to return past tense of. | |
- * | |
- * @return string | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function past_tense_verb( $verb ) { | |
static $irregular = array( | |
'reset' => 'reset', | |
@@ -1213,7 +1264,7 @@ function past_tense_verb( $verb ) { | |
} elseif ( 'y' === $last && ! preg_match( '/[aeiou]y$/', $verb ) ) { | |
$verb = substr( $verb, 0, -1 ) . 'i'; | |
} elseif ( preg_match( '/^[^aeiou]*[aeiou][^aeiouhwxy]$/', $verb ) ) { | |
- // Rule of thumb that most (all?) one-voweled regular verbs ending in vowel + consonant (excluding "h", "w", "x", "y") double their final consonant - misses many cases (eg "submit"). | |
+ | |
$verb .= $last; | |
} | |
return $verb . 'ed'; | |
diff --git a/features/extra/no-mail.php b/features/extra/no-mail.php | |
index de7a4227..baaf4ccb 100644 | |
--- a/features/extra/no-mail.php | |
+++ b/features/extra/no-mail.php | |
@@ -1,7 +1,7 @@ | |
<?php | |
function wp_mail( $to ) { | |
- // Log for testing purposes | |
+ | |
WP_CLI::log( "WP-CLI test suite: Sent email to {$to}." ); | |
} | |
diff --git a/features/steps/given.php b/features/steps/given.php | |
index f2eae113..167b1799 100644 | |
--- a/features/steps/given.php | |
+++ b/features/steps/given.php | |
@@ -1,8 +1,8 @@ | |
<?php | |
use Behat\Gherkin\Node\PyStringNode, | |
- Behat\Gherkin\Node\TableNode, | |
- WP_CLI\Process; | |
+Behat\Gherkin\Node\TableNode, | |
+WP_CLI\Process; | |
$steps->Given( '/^an empty directory$/', | |
function ( $world ) { | |
@@ -21,7 +21,7 @@ $steps->Given( '/^an? (empty|non-existent) ([^\s]+) directory$/', | |
} | |
$world->remove_dir( $dir ); | |
if ( 'empty' === $empty_or_nonexistent ) { | |
- mkdir( $dir, 0777, true /*recursive*/ ); | |
+ mkdir( $dir, 0777, true ); | |
} | |
} | |
); | |
@@ -38,7 +38,7 @@ $steps->Given( '/^an? ([^\s]+) file:$/', | |
$full_path = $world->variables['RUN_DIR'] . "/$path"; | |
$dir = dirname( $full_path ); | |
if ( ! file_exists( $dir ) ) { | |
- mkdir( $dir, 0777, true /*recursive*/ ); | |
+ mkdir( $dir, 0777, true ); | |
} | |
file_put_contents( $full_path, $content ); | |
} | |
@@ -131,7 +131,7 @@ $steps->Given( '/^download:$/', | |
foreach ( $table->getHash() as $row ) { | |
$path = $world->replace_variables( $row['path'] ); | |
if ( file_exists( $path ) ) { | |
- // assume it's the same file and skip re-download | |
+ | |
continue; | |
} | |
diff --git a/features/steps/then.php b/features/steps/then.php | |
index aa1f3f67..40efac0e 100644 | |
--- a/features/steps/then.php | |
+++ b/features/steps/then.php | |
@@ -1,7 +1,7 @@ | |
<?php | |
use Behat\Gherkin\Node\PyStringNode, | |
- Behat\Gherkin\Node\TableNode; | |
+Behat\Gherkin\Node\TableNode; | |
$steps->Then( '/^the return code should be (\d+)$/', | |
function ( $world, $return_code ) { | |
@@ -42,7 +42,7 @@ $steps->Then( '/^(STDOUT|STDERR) should not be a number$/', | |
$steps->Then( '/^STDOUT should be a table containing rows:$/', | |
function ( $world, TableNode $expected ) { | |
- $output = $world->result->stdout; | |
+ $output = $world->result->stdout; | |
$actual_rows = explode( "\n", rtrim( $output, "\n" ) ); | |
$expected_rows = array(); | |
@@ -56,7 +56,7 @@ $steps->Then( '/^STDOUT should be a table containing rows:$/', | |
$steps->Then( '/^STDOUT should end with a table containing rows:$/', | |
function ( $world, TableNode $expected ) { | |
- $output = $world->result->stdout; | |
+ $output = $world->result->stdout; | |
$actual_rows = explode( "\n", rtrim( $output, "\n" ) ); | |
$expected_rows = array(); | |
@@ -153,13 +153,13 @@ $steps->Then( '/^(STDOUT|STDERR) should be a version string (<|<=|>|>=|==|=|!=|< | |
throw new Exception( $world->result ); | |
} | |
} | |
-); | |
+); | |
$steps->Then( '/^the (.+) (file|directory) should (exist|not exist|be:|contain:|not contain:)$/', | |
function ( $world, $path, $type, $action, $expected = null ) { | |
$path = $world->replace_variables( $path ); | |
- // If it's a relative path, make it relative to the current test dir | |
+ | |
if ( '/' !== $path[0] ) | |
$path = $world->variables['RUN_DIR'] . "/$path"; | |
@@ -200,6 +200,25 @@ $steps->Then( '/^the (.+) (file|directory) should (exist|not exist|be:|contain:| | |
} | |
); | |
+$steps->Then( '/^the contents of the (.+) file should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', | |
+ function ( $world, $path, $expected ) { | |
+ $path = $world->replace_variables( $path ); | |
+ | |
+ if ( '/' !== $path[0] ) { | |
+ $path = $world->variables['RUN_DIR'] . "/$path"; | |
+ } | |
+ $contents = file_get_contents( $path ); | |
+ assertRegExp( $expected, $contents ); | |
+ } | |
+); | |
+ | |
+$steps->Then( '/^(STDOUT|STDERR) should match (((\/.+\/)|(#.+#))([a-z]+)?)$/', | |
+ function ( $world, $stream, $expected ) { | |
+ $stream = strtolower( $stream ); | |
+ assertRegExp( $expected, $world->result->$stream ); | |
+ } | |
+); | |
+ | |
$steps->Then( '/^an email should (be sent|not be sent)$/', function( $world, $expected ) { | |
if ( 'be sent' === $expected ) { | |
assertNotEquals( 0, $world->email_sends ); | |
diff --git a/features/steps/when.php b/features/steps/when.php | |
index afe3f7a0..835fe9d8 100644 | |
--- a/features/steps/when.php | |
+++ b/features/steps/when.php | |
@@ -1,8 +1,8 @@ | |
<?php | |
use Behat\Gherkin\Node\PyStringNode, | |
- Behat\Gherkin\Node\TableNode, | |
- WP_CLI\Process; | |
+Behat\Gherkin\Node\TableNode, | |
+WP_CLI\Process; | |
function invoke_proc( $proc, $mode ) { | |
$map = array( | |
diff --git a/utils/behat-tags.php b/utils/behat-tags.php | |
index ee51fc91..4d09bc79 100644 | |
--- a/utils/behat-tags.php | |
+++ b/utils/behat-tags.php | |
@@ -1,17 +1,17 @@ | |
<?php | |
-/** | |
- * Generate a list of tags to skip during the test run. | |
- * | |
- * Require a minimum version of WordPress: | |
- * | |
- * @require-wp-4.0 | |
- * Scenario: Core translation CRUD | |
- * | |
- * Then use in bash script: | |
- * | |
- * BEHAT_TAGS=$(php behat-tags.php) | |
- * vendor/bin/behat --format progress $BEHAT_TAGS | |
- */ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
function version_tags( $prefix, $current, $operator = '<' ) { | |
if ( ! $current ) | |
@@ -32,8 +32,8 @@ function version_tags( $prefix, $current, $operator = '<' ) { | |
} | |
$wp_version_reqs = array(); | |
-// Only apply @require-wp tags when WP_VERSION isn't 'latest' or 'nightly' | |
-// 'latest' and 'nightly' are expected to work with all features | |
+ | |
+ | |
if ( ! in_array( getenv( 'WP_VERSION' ), array( 'latest', 'nightly', 'trunk' ), true ) ) { | |
$wp_version_reqs = version_tags( 'require-wp', getenv( 'WP_VERSION' ), '<' ); | |
} | |
@@ -44,13 +44,13 @@ $skip_tags = array_merge( | |
version_tags( 'less-than-php', PHP_VERSION, '>' ) | |
); | |
-# Skip Github API tests by default because of rate limiting. See https://github.com/wp-cli/wp-cli/issues/1612 | |
+ | |
$skip_tags[] = '@github-api'; | |
-# Skip tests known to be broken. | |
+ | |
$skip_tags[] = '@broken'; | |
-# Require PHP extension, eg 'imagick'. | |
+ | |
function extension_tags() { | |
$extension_tags = array(); | |
exec( "grep '@require-extension-[A-Za-z_]*' -h -o features/*.feature | uniq", $extension_tags ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment