mirror of
https://fast.feibisi.com/https://github.com/wp-cli/scaffold-command.git
synced 2025-10-04 17:01:28 +08:00
989 lines
30 KiB
PHP
989 lines
30 KiB
PHP
<?php
|
|
|
|
use WP_CLI\Utils;
|
|
use WP_CLI\Process;
|
|
|
|
/**
|
|
* Generate code for post types, taxonomies, plugins, child themes. etc.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* # Generate a new plugin with unit tests
|
|
* $ wp scaffold plugin sample-plugin
|
|
* Success: Created plugin files.
|
|
* Success: Created test files.
|
|
*
|
|
* # Generate theme based on _s
|
|
* $ wp scaffold _s sample-theme --theme_name="Sample Theme" --author="John Doe"
|
|
* Success: Created theme 'Sample Theme'.
|
|
*
|
|
* # Generate code for post type registration in given theme
|
|
* $ wp scaffold post-type movie --label=Movie --theme=simple-life
|
|
* Success: Created /var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php
|
|
*
|
|
* @package wp-cli
|
|
*/
|
|
class Scaffold_Command extends WP_CLI_Command {
|
|
|
|
/**
|
|
* Generate PHP code for registering a custom post type.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* <slug>
|
|
* : The internal name of the post type.
|
|
*
|
|
* [--label=<label>]
|
|
* : The text used to translate the update messages.
|
|
*
|
|
* [--textdomain=<textdomain>]
|
|
* : The textdomain to use for the labels.
|
|
*
|
|
* [--dashicon=<dashicon>]
|
|
* : The dashicon to use in the menu.
|
|
*
|
|
* [--theme]
|
|
* : Create a file in the active theme directory, instead of sending to
|
|
* STDOUT. Specify a theme with `--theme=<theme>` to have the file placed in that theme.
|
|
*
|
|
* [--plugin=<plugin>]
|
|
* : Create a file in the given plugin's directory, instead of sending to STDOUT.
|
|
*
|
|
* [--raw]
|
|
* : Just generate the `register_post_type()` call and nothing else.
|
|
*
|
|
* [--force]
|
|
* : Overwrite files that already exist.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* # Generate a 'movie' post type for the 'simple-life' theme
|
|
* $ wp scaffold post-type movie --label=Movie --theme=simple-life
|
|
* Success: Created '/var/www/example.com/public_html/wp-content/themes/simple-life/post-types/movie.php'.
|
|
*
|
|
* @subcommand post-type
|
|
*
|
|
* @alias cpt
|
|
*/
|
|
public function post_type( $args, $assoc_args ) {
|
|
|
|
if ( strlen( $args[0] ) > 20 ) {
|
|
WP_CLI::error( "Post type slugs cannot exceed 20 characters in length." );
|
|
}
|
|
|
|
$defaults = array(
|
|
'textdomain' => '',
|
|
'dashicon' => 'admin-post',
|
|
);
|
|
|
|
$this->_scaffold( $args[0], $assoc_args, $defaults, '/post-types/', array(
|
|
'post_type.mustache',
|
|
'post_type_extended.mustache'
|
|
) );
|
|
}
|
|
|
|
/**
|
|
* Generate PHP code for registering a custom taxonomy.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* <slug>
|
|
* : The internal name of the taxonomy.
|
|
*
|
|
* [--post_types=<post-types>]
|
|
* : Post types to register for use with the taxonomy.
|
|
*
|
|
* [--label=<label>]
|
|
* : The text used to translate the update messages.
|
|
*
|
|
* [--textdomain=<textdomain>]
|
|
* : The textdomain to use for the labels.
|
|
*
|
|
* [--theme]
|
|
* : Create a file in the active theme directory, instead of sending to
|
|
* STDOUT. Specify a theme with `--theme=<theme>` to have the file placed in that theme.
|
|
*
|
|
* [--plugin=<plugin>]
|
|
* : Create a file in the given plugin's directory, instead of sending to STDOUT.
|
|
*
|
|
* [--raw]
|
|
* : Just generate the `register_taxonomy()` call and nothing else.
|
|
*
|
|
* [--force]
|
|
* : Overwrite files that already exist.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* # Generate PHP code for registering a custom taxonomy and save in a file
|
|
* $ wp scaffold taxonomy venue --post_types=event,presentation > taxonomy.php
|
|
*
|
|
* @subcommand taxonomy
|
|
*
|
|
* @alias tax
|
|
*/
|
|
public function taxonomy( $args, $assoc_args ) {
|
|
$defaults = array(
|
|
'textdomain' => '',
|
|
'post_types' => "'post'"
|
|
);
|
|
|
|
if ( isset( $assoc_args['post_types'] ) ) {
|
|
$assoc_args['post_types'] = $this->quote_comma_list_elements( $assoc_args['post_types'] );
|
|
}
|
|
|
|
$this->_scaffold( $args[0], $assoc_args, $defaults, '/taxonomies/', array(
|
|
'taxonomy.mustache',
|
|
'taxonomy_extended.mustache'
|
|
) );
|
|
}
|
|
|
|
private function _scaffold( $slug, $assoc_args, $defaults, $subdir, $templates ) {
|
|
$wp_filesystem = $this->init_wp_filesystem();
|
|
|
|
$control_args = $this->extract_args( $assoc_args, array(
|
|
'label' => preg_replace( '/_|-/', ' ', strtolower( $slug ) ),
|
|
'theme' => false,
|
|
'plugin' => false,
|
|
'raw' => false,
|
|
) );
|
|
|
|
$vars = $this->extract_args( $assoc_args, $defaults );
|
|
|
|
$vars['slug'] = $slug;
|
|
|
|
$vars['textdomain'] = $this->get_textdomain( $vars['textdomain'], $control_args );
|
|
|
|
$vars['label'] = $control_args['label'];
|
|
|
|
$vars['label_ucfirst'] = ucfirst( $vars['label'] );
|
|
$vars['label_plural'] = $this->pluralize( $vars['label'] );
|
|
$vars['label_plural_ucfirst'] = ucfirst( $vars['label_plural'] );
|
|
|
|
// We use the machine name for function declarations
|
|
$machine_name = preg_replace( '/-/', '_', $slug );
|
|
$machine_name_plural = $this->pluralize( $slug );
|
|
|
|
list( $raw_template, $extended_template ) = $templates;
|
|
|
|
$raw_output = Utils\mustache_render( $raw_template, $vars );
|
|
|
|
if ( ! $control_args['raw'] ) {
|
|
$vars = array_merge( $vars, array(
|
|
'machine_name' => $machine_name,
|
|
'output' => $raw_output
|
|
) );
|
|
|
|
$final_output = Utils\mustache_render( $extended_template, $vars );
|
|
} else {
|
|
$final_output = $raw_output;
|
|
}
|
|
|
|
if ( $path = $this->get_output_path( $control_args, $subdir ) ) {
|
|
$filename = $path . $slug . '.php';
|
|
|
|
$force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' );
|
|
$files_written = $this->create_files( array( $filename => $final_output ), $force );
|
|
$this->log_whether_files_written(
|
|
$files_written,
|
|
$skip_message = "Skipped creating '$filename'.",
|
|
$success_message = "Created '$filename'."
|
|
);
|
|
|
|
} else {
|
|
// STDOUT
|
|
echo $final_output;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate starter code for a theme based on _s.
|
|
*
|
|
* See the [Underscores website](http://underscores.me/) for more details.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* <slug>
|
|
* : The slug for the new theme, used for prefixing functions.
|
|
*
|
|
* [--activate]
|
|
* : Activate the newly downloaded theme.
|
|
*
|
|
* [--enable-network]
|
|
* : Enable the newly downloaded theme for the entire network.
|
|
*
|
|
* [--theme_name=<title>]
|
|
* : What to put in the 'Theme Name:' header in 'style.css'.
|
|
*
|
|
* [--author=<full-name>]
|
|
* : What to put in the 'Author:' header in 'style.css'.
|
|
*
|
|
* [--author_uri=<uri>]
|
|
* : What to put in the 'Author URI:' header in 'style.css'.
|
|
*
|
|
* [--sassify]
|
|
* : Include stylesheets as SASS.
|
|
*
|
|
* [--force]
|
|
* : Overwrite files that already exist.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* # Generate a theme with name "Sample Theme" and author "John Doe"
|
|
* $ wp scaffold _s sample-theme --theme_name="Sample Theme" --author="John Doe"
|
|
* Success: Created theme 'Sample Theme'.
|
|
*/
|
|
public function _s( $args, $assoc_args ) {
|
|
|
|
$theme_slug = $args[0];
|
|
$theme_path = WP_CONTENT_DIR . "/themes";
|
|
$url = "http://underscores.me";
|
|
$timeout = 30;
|
|
|
|
if ( in_array( $theme_slug, array( '.', '..' ) ) ) {
|
|
WP_CLI::error( "Invalid theme slug specified." );
|
|
}
|
|
|
|
if ( ! preg_match( '/^[a-z_]\w+$/i', str_replace( '-', '_', $theme_slug ) ) ) {
|
|
WP_CLI::error( "Invalid theme slug specified. Theme slugs can only contain letters, numbers, underscores and hyphens, and can only start with a letter or underscore." );
|
|
}
|
|
|
|
$data = wp_parse_args( $assoc_args, array(
|
|
'theme_name' => ucfirst( $theme_slug ),
|
|
'author' => "Me",
|
|
'author_uri' => "",
|
|
) );
|
|
|
|
$_s_theme_path = "$theme_path/$data[theme_name]";
|
|
|
|
if ( ! $this->check_target_directory( "theme", $_s_theme_path ) ) {
|
|
WP_CLI::error( "Invalid theme slug specified." );
|
|
}
|
|
|
|
$force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' );
|
|
$should_write_file = $this->prompt_if_files_will_be_overwritten( $_s_theme_path, $force );
|
|
if ( ! $should_write_file ) {
|
|
WP_CLI::log( 'No files created' );
|
|
die;
|
|
}
|
|
|
|
$theme_description = "Custom theme: " . $data['theme_name'] . ", developed by " . $data['author'];
|
|
|
|
$body = array();
|
|
$body['underscoresme_name'] = $data['theme_name'];
|
|
$body['underscoresme_slug'] = $theme_slug;
|
|
$body['underscoresme_author'] = $data['author'];
|
|
$body['underscoresme_author_uri'] = $data['author_uri'];
|
|
$body['underscoresme_description'] = $theme_description;
|
|
$body['underscoresme_generate_submit'] = "Generate";
|
|
$body['underscoresme_generate'] = "1";
|
|
if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'sassify' ) ) {
|
|
$body['underscoresme_sass'] = 1;
|
|
}
|
|
|
|
$tmpfname = wp_tempnam( $url );
|
|
$response = wp_remote_post( $url, array(
|
|
'timeout' => $timeout,
|
|
'body' => $body,
|
|
'stream' => true,
|
|
'filename' => $tmpfname
|
|
) );
|
|
|
|
if ( is_wp_error( $response ) ) {
|
|
WP_CLI::error( $response );
|
|
}
|
|
|
|
$response_code = wp_remote_retrieve_response_code( $response );
|
|
if ( 200 != $response_code ) {
|
|
WP_CLI::error( "Couldn't create theme (received $response_code response)." );
|
|
}
|
|
|
|
$this->maybe_create_themes_dir();
|
|
|
|
$this->init_wp_filesystem();
|
|
|
|
$unzip_result = unzip_file( $tmpfname, $theme_path );
|
|
unlink( $tmpfname );
|
|
|
|
if ( true === $unzip_result ) {
|
|
$this->create_files( array(
|
|
"$theme_path/{$theme_slug}/.editorconfig" => file_get_contents( WP_CLI_ROOT . "/templates/.editorconfig" ),
|
|
), false );
|
|
WP_CLI::success( "Created theme '{$data['theme_name']}'." );
|
|
} else {
|
|
WP_CLI::error( "Could not decompress your theme files ('{$tmpfname}') at '{$theme_path}': {$unzip_result->get_error_message()}" );
|
|
}
|
|
|
|
if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) {
|
|
WP_CLI::run_command( array( 'theme', 'activate', $theme_slug ) );
|
|
} else if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'enable-network' ) ) {
|
|
WP_CLI::run_command( array( 'theme', 'enable', $theme_slug ), array( 'network' => true ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate child theme based on an existing theme.
|
|
*
|
|
* Creates a child theme folder with `functions.php` and `style.css` files.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* <slug>
|
|
* : The slug for the new child theme.
|
|
*
|
|
* --parent_theme=<slug>
|
|
* : What to put in the 'Template:' header in 'style.css'.
|
|
*
|
|
* [--theme_name=<title>]
|
|
* : What to put in the 'Theme Name:' header in 'style.css'.
|
|
*
|
|
* [--author=<full-name>]
|
|
* : What to put in the 'Author:' header in 'style.css'.
|
|
*
|
|
* [--author_uri=<uri>]
|
|
* : What to put in the 'Author URI:' header in 'style.css'.
|
|
*
|
|
* [--theme_uri=<uri>]
|
|
* : What to put in the 'Theme URI:' header in 'style.css'.
|
|
*
|
|
* [--activate]
|
|
* : Activate the newly created child theme.
|
|
*
|
|
* [--enable-network]
|
|
* : Enable the newly created child theme for the entire network.
|
|
*
|
|
* [--force]
|
|
* : Overwrite files that already exist.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* # Generate a 'sample-theme' child theme based on TwentySixteen
|
|
* $ wp scaffold child-theme sample-theme --parent_theme=twentysixteen
|
|
* Success: Created '/var/www/example.com/public_html/wp-content/themes/sample-theme'.
|
|
*
|
|
* @subcommand child-theme
|
|
*/
|
|
function child_theme( $args, $assoc_args ) {
|
|
$theme_slug = $args[0];
|
|
|
|
if ( in_array( $theme_slug, array( '.', '..' ) ) ) {
|
|
WP_CLI::error( "Invalid theme slug specified." );
|
|
}
|
|
|
|
$data = wp_parse_args( $assoc_args, array(
|
|
'theme_name' => ucfirst( $theme_slug ),
|
|
'author' => "Me",
|
|
'author_uri' => "",
|
|
'theme_uri' => ""
|
|
) );
|
|
$data['slug'] = $theme_slug;
|
|
$data['parent_theme_function_safe'] = str_replace( '-', '_', $data['parent_theme'] );
|
|
|
|
$data['description'] = ucfirst( $data['parent_theme'] ) . " child theme.";
|
|
|
|
$theme_dir = WP_CONTENT_DIR . "/themes" . "/$theme_slug";
|
|
|
|
if ( ! $this->check_target_directory( "theme", $theme_dir ) ) {
|
|
WP_CLI::error( "Invalid theme slug specified." );
|
|
}
|
|
|
|
$theme_style_path = "$theme_dir/style.css";
|
|
$theme_functions_path = "$theme_dir/functions.php";
|
|
|
|
$this->maybe_create_themes_dir();
|
|
|
|
$force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' );
|
|
$files_written = $this->create_files( array(
|
|
$theme_style_path => Utils\mustache_render( 'child_theme.mustache', $data ),
|
|
$theme_functions_path => Utils\mustache_render( 'child_theme_functions.mustache', $data ),
|
|
"$theme_dir/.editorconfig" => file_get_contents( WP_CLI_ROOT . "/templates/.editorconfig" ),
|
|
), $force );
|
|
$this->log_whether_files_written(
|
|
$files_written,
|
|
$skip_message = 'All theme files were skipped.',
|
|
$success_message = "Created '$theme_dir'."
|
|
);
|
|
|
|
if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) {
|
|
WP_CLI::run_command( array( 'theme', 'activate', $theme_slug ) );
|
|
} else if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'enable-network' ) ) {
|
|
WP_CLI::run_command( array( 'theme', 'enable', $theme_slug ), array( 'network' => true ) );
|
|
}
|
|
}
|
|
|
|
private function get_output_path( $assoc_args, $subdir ) {
|
|
if ( $assoc_args['theme'] ) {
|
|
$theme = $assoc_args['theme'];
|
|
if ( is_string( $theme ) ) {
|
|
$path = get_theme_root( $theme ) . '/' . $theme;
|
|
} else {
|
|
$path = get_stylesheet_directory();
|
|
}
|
|
} elseif ( $assoc_args['plugin'] ) {
|
|
$plugin = $assoc_args['plugin'];
|
|
$path = WP_PLUGIN_DIR . '/' . $plugin;
|
|
if ( ! is_dir( $path ) ) {
|
|
WP_CLI::error( "Can't find '$plugin' plugin." );
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
$path .= $subdir;
|
|
|
|
return $path;
|
|
}
|
|
|
|
/**
|
|
* Generate starter code for a plugin.
|
|
*
|
|
* The following files are always generated:
|
|
*
|
|
* * `plugin-slug.php` is the main PHP plugin file.
|
|
* * `readme.txt` is the readme file for the plugin.
|
|
* * `package.json` needed by NPM holds various metadata relevant to the project. Packages: `grunt`, `grunt-wp-i18n` and `grunt-wp-readme-to-markdown`.
|
|
* * `Gruntfile.js` is the JS file containing Grunt tasks. Tasks: `i18n` containing `addtextdomain` and `makepot`, `readme` containing `wp_readme_to_markdown`.
|
|
* * `.editorconfig` is the configuration file for Editor.
|
|
* * `.gitignore` tells which files (or patterns) git should ignore.
|
|
* * `.distignore` tells which files and folders should be ignored in distribution.
|
|
*
|
|
* The following files are also included unless the `--skip-tests` is used:
|
|
*
|
|
* * `phpunit.xml.dist` is the configuration file for PHPUnit.
|
|
* * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service.
|
|
* * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database.
|
|
* * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite.
|
|
* * `tests/test-sample.php` is a sample file containing test cases.
|
|
* * `phpcs.ruleset.xml` is a collenction of PHP_CodeSniffer rules.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* <slug>
|
|
* : The internal name of the plugin.
|
|
*
|
|
* [--dir=<dirname>]
|
|
* : Put the new plugin in some arbitrary directory path. Plugin directory will be path plus supplied slug.
|
|
*
|
|
* [--plugin_name=<title>]
|
|
* : What to put in the 'Plugin Name:' header.
|
|
*
|
|
* [--plugin_description=<description>]
|
|
* : What to put in the 'Description:' header.
|
|
*
|
|
* [--plugin_author=<author>]
|
|
* : What to put in the 'Author:' header.
|
|
*
|
|
* [--plugin_author_uri=<url>]
|
|
* : What to put in the 'Author URI:' header.
|
|
*
|
|
* [--plugin_uri=<url>]
|
|
* : What to put in the 'Plugin URI:' header.
|
|
*
|
|
* [--skip-tests]
|
|
* : Don't generate files for unit testing.
|
|
*
|
|
* [--ci=<provider>]
|
|
* : Choose a configuration file for a continuous integration provider.
|
|
* ---
|
|
* default: travis
|
|
* options:
|
|
* - travis
|
|
* - circle
|
|
* - gitlab
|
|
* ---
|
|
*
|
|
* [--activate]
|
|
* : Activate the newly generated plugin.
|
|
*
|
|
* [--activate-network]
|
|
* : Network activate the newly generated plugin.
|
|
*
|
|
* [--force]
|
|
* : Overwrite files that already exist.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* $ wp scaffold plugin sample-plugin
|
|
* Success: Created plugin files.
|
|
* Success: Created test files.
|
|
*/
|
|
function plugin( $args, $assoc_args ) {
|
|
$plugin_slug = $args[0];
|
|
$plugin_name = ucwords( str_replace( '-', ' ', $plugin_slug ) );
|
|
$plugin_package = str_replace( ' ', '_', $plugin_name );
|
|
|
|
if ( in_array( $plugin_slug, array( '.', '..' ) ) ) {
|
|
WP_CLI::error( "Invalid plugin slug specified." );
|
|
}
|
|
|
|
$data = wp_parse_args( $assoc_args, array(
|
|
'plugin_slug' => $plugin_slug,
|
|
'plugin_name' => $plugin_name,
|
|
'plugin_package' => $plugin_package,
|
|
'plugin_description' => 'PLUGIN DESCRIPTION HERE',
|
|
'plugin_author' => 'YOUR NAME HERE',
|
|
'plugin_author_uri' => 'YOUR SITE HERE',
|
|
'plugin_uri' => 'PLUGIN SITE HERE',
|
|
'plugin_tested_up_to' => get_bloginfo('version'),
|
|
) );
|
|
|
|
$data['textdomain'] = $plugin_slug;
|
|
|
|
if ( ! empty( $assoc_args['dir'] ) ) {
|
|
if ( ! is_dir( $assoc_args['dir'] ) ) {
|
|
WP_CLI::error( "Cannot create plugin in directory that doesn't exist." );
|
|
}
|
|
$plugin_dir = $assoc_args['dir'] . "/$plugin_slug";
|
|
} else {
|
|
$plugin_dir = WP_PLUGIN_DIR . "/$plugin_slug";
|
|
$this->maybe_create_plugins_dir();
|
|
|
|
if ( ! $this->check_target_directory( "plugin", $plugin_dir ) ) {
|
|
WP_CLI::error( "Invalid plugin slug specified." );
|
|
}
|
|
}
|
|
|
|
$plugin_path = "$plugin_dir/$plugin_slug.php";
|
|
$plugin_readme_path = "$plugin_dir/readme.txt";
|
|
|
|
$force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' );
|
|
$files_written = $this->create_files( array(
|
|
$plugin_path => Utils\mustache_render( 'plugin.mustache', $data ),
|
|
$plugin_readme_path => Utils\mustache_render( 'plugin-readme.mustache', $data ),
|
|
"$plugin_dir/package.json" => Utils\mustache_render( 'plugin-packages.mustache', $data ),
|
|
"$plugin_dir/Gruntfile.js" => Utils\mustache_render( 'plugin-gruntfile.mustache', $data ),
|
|
"$plugin_dir/.gitignore" => Utils\mustache_render( 'plugin-gitignore.mustache', $data ),
|
|
"$plugin_dir/.distignore" => Utils\mustache_render( 'plugin-distignore.mustache', $data ),
|
|
"$plugin_dir/.editorconfig" => file_get_contents( WP_CLI_ROOT . "/templates/.editorconfig" ),
|
|
), $force );
|
|
|
|
$this->log_whether_files_written(
|
|
$files_written,
|
|
$skip_message = 'All plugin files were skipped.',
|
|
$success_message = 'Created plugin files.'
|
|
);
|
|
|
|
if ( ! \WP_CLI\Utils\get_flag_value( $assoc_args, 'skip-tests' ) ) {
|
|
WP_CLI::run_command( array( 'scaffold', 'plugin-tests', $plugin_slug ), array( 'dir' => $plugin_dir, 'ci' => $assoc_args['ci'], 'force' => $force ) );
|
|
}
|
|
|
|
if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate' ) ) {
|
|
WP_CLI::run_command( array( 'plugin', 'activate', $plugin_slug ) );
|
|
} else if ( \WP_CLI\Utils\get_flag_value( $assoc_args, 'activate-network' ) ) {
|
|
WP_CLI::run_command( array( 'plugin', 'activate', $plugin_slug), array( 'network' => true ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate files needed for running PHPUnit tests in a plugin.
|
|
*
|
|
* The following files are generated by default:
|
|
*
|
|
* * `phpunit.xml.dist` is the configuration file for PHPUnit.
|
|
* * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service.
|
|
* * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database.
|
|
* * `tests/bootstrap.php` is the file that makes the current plugin active when running the test suite.
|
|
* * `tests/test-sample.php` is a sample file containing the actual tests.
|
|
* * `phpcs.ruleset.xml` is a collenction of PHP_CodeSniffer rules.
|
|
*
|
|
* Learn more from the [plugin unit tests documentation](http://wp-cli.org/docs/plugin-unit-tests/).
|
|
*
|
|
* ## ENVIRONMENT
|
|
*
|
|
* The `tests/bootstrap.php` file looks for the WP_TESTS_DIR environment
|
|
* variable.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* [<plugin>]
|
|
* : The name of the plugin to generate test files for.
|
|
*
|
|
* [--dir=<dirname>]
|
|
* : Generate test files for a non-standard plugin path. If no plugin slug is specified, the directory name is used.
|
|
*
|
|
* [--ci=<provider>]
|
|
* : Choose a configuration file for a continuous integration provider.
|
|
* ---
|
|
* default: travis
|
|
* options:
|
|
* - travis
|
|
* - circle
|
|
* - gitlab
|
|
* ---
|
|
*
|
|
* [--force]
|
|
* : Overwrite files that already exist.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* # Generate unit test files for plugin 'sample-plugin'.
|
|
* $ wp scaffold plugin-tests sample-plugin
|
|
* Success: Created test files.
|
|
*
|
|
* @subcommand plugin-tests
|
|
*/
|
|
public function plugin_tests( $args, $assoc_args ) {
|
|
$this->scaffold_plugin_theme_tests( $args, $assoc_args, 'plugin' );
|
|
}
|
|
|
|
/**
|
|
* Generate files needed for running PHPUnit tests in a theme.
|
|
*
|
|
* The following files are generated by default:
|
|
*
|
|
* * `phpunit.xml.dist` is the configuration file for PHPUnit.
|
|
* * `.travis.yml` is the configuration file for Travis CI. Use `--ci=<provider>` to select a different service.
|
|
* * `bin/install-wp-tests.sh` configures the WordPress test suite and a test database.
|
|
* * `tests/bootstrap.php` is the file that makes the current theme active when running the test suite.
|
|
* * `tests/test-sample.php` is a sample file containing the actual tests.
|
|
* * `phpcs.ruleset.xml` is a collenction of PHP_CodeSniffer rules.
|
|
*
|
|
* Learn more from the [plugin unit tests documentation](http://wp-cli.org/docs/plugin-unit-tests/).
|
|
*
|
|
* ## ENVIRONMENT
|
|
*
|
|
* The `tests/bootstrap.php` file looks for the WP_TESTS_DIR environment
|
|
* variable.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* [<theme>]
|
|
* : The name of the theme to generate test files for.
|
|
*
|
|
* [--dir=<dirname>]
|
|
* : Generate test files for a non-standard theme path. If no theme slug is specified, the directory name is used.
|
|
*
|
|
* [--ci=<provider>]
|
|
* : Choose a configuration file for a continuous integration provider.
|
|
* ---
|
|
* default: travis
|
|
* options:
|
|
* - travis
|
|
* - circle
|
|
* - gitlab
|
|
* ---
|
|
*
|
|
* [--force]
|
|
* : Overwrite files that already exist.
|
|
*
|
|
* ## EXAMPLES
|
|
*
|
|
* # Generate unit test files for theme 'twentysixteenchild'.
|
|
* $ wp scaffold theme-tests twentysixteenchild
|
|
* Success: Created test files.
|
|
*
|
|
* @subcommand theme-tests
|
|
*/
|
|
public function theme_tests( $args, $assoc_args ) {
|
|
$this->scaffold_plugin_theme_tests( $args, $assoc_args, 'theme' );
|
|
}
|
|
|
|
private function scaffold_plugin_theme_tests( $args, $assoc_args, $type ) {
|
|
$wp_filesystem = $this->init_wp_filesystem();
|
|
|
|
if ( ! empty( $args[0] ) ) {
|
|
$slug = $args[0];
|
|
if ( in_array( $slug, array( '.', '..' ) ) ) {
|
|
WP_CLI::error( "Invalid {$type} slug specified." );
|
|
}
|
|
if ( 'theme' === $type ) {
|
|
$theme = wp_get_theme( $slug );
|
|
if ( $theme->exists() ) {
|
|
$target_dir = $theme->get_stylesheet_directory();
|
|
} else {
|
|
WP_CLI::error( "Invalid {$type} slug specified." );
|
|
}
|
|
} else {
|
|
$target_dir = WP_PLUGIN_DIR . "/$slug";
|
|
}
|
|
if ( empty( $assoc_args['dir'] ) && ! is_dir( $target_dir ) ) {
|
|
WP_CLI::error( "Invalid {$type} slug specified." );
|
|
}
|
|
if ( ! $this->check_target_directory( $type, $target_dir ) ) {
|
|
WP_CLI::error( "Invalid {$type} slug specified." );
|
|
}
|
|
}
|
|
|
|
if ( ! empty( $assoc_args['dir'] ) ) {
|
|
$target_dir = $assoc_args['dir'];
|
|
if ( ! is_dir( $target_dir ) ) {
|
|
WP_CLI::error( "Invalid {$type} directory specified." );
|
|
}
|
|
if ( empty( $slug ) ) {
|
|
$slug = Utils\basename( $target_dir );
|
|
}
|
|
}
|
|
|
|
if ( empty( $slug ) || empty( $target_dir ) ) {
|
|
WP_CLI::error( "Invalid {$type} specified." );
|
|
}
|
|
|
|
$name = ucwords( str_replace( '-', ' ', $slug ) );
|
|
$package = str_replace( ' ', '_', $name );
|
|
|
|
$tests_dir = "{$target_dir}/tests";
|
|
$bin_dir = "{$target_dir}/bin";
|
|
|
|
$wp_filesystem->mkdir( $tests_dir );
|
|
$wp_filesystem->mkdir( $bin_dir );
|
|
|
|
$wp_versions_to_test = array();
|
|
// Parse plugin readme.txt
|
|
if ( file_exists( $target_dir . '/readme.txt' ) ) {
|
|
$readme_content = file_get_contents( $target_dir . '/readme.txt' );
|
|
|
|
preg_match( '/Requires at least\:(.*)\n/m', $readme_content, $matches );
|
|
if ( isset( $matches[1] ) && $matches[1] ) {
|
|
$wp_versions_to_test[] = trim( $matches[1] );
|
|
}
|
|
}
|
|
$wp_versions_to_test[] = 'latest';
|
|
$wp_versions_to_test[] = 'trunk';
|
|
|
|
$template_data = array(
|
|
"{$type}_slug" => $slug,
|
|
"{$type}_package" => $package,
|
|
);
|
|
|
|
$force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' );
|
|
$files_to_create = array(
|
|
"$tests_dir/bootstrap.php" => Utils\mustache_render( "{$type}-bootstrap.mustache", $template_data ),
|
|
"$tests_dir/test-sample.php" => Utils\mustache_render( "{$type}-test-sample.mustache", $template_data ),
|
|
);
|
|
if ( 'travis' === $assoc_args['ci'] ) {
|
|
$files_to_create["{$target_dir}/.travis.yml"] = Utils\mustache_render( 'plugin-travis.mustache', compact( 'wp_versions_to_test' ) );
|
|
} else if ( 'circle' === $assoc_args['ci'] ) {
|
|
$files_to_create["{$target_dir}/circle.yml"] = Utils\mustache_render( 'plugin-circle.mustache', compact( 'wp_versions_to_test' ) );
|
|
} else if ( 'gitlab' === $assoc_args['ci'] ) {
|
|
$files_to_create["{$target_dir}/.gitlab-ci.yml"] = Utils\mustache_render( 'plugin-gitlab.mustache' );
|
|
}
|
|
$files_written = $this->create_files( $files_to_create, $force );
|
|
|
|
$to_copy = array(
|
|
'install-wp-tests.sh' => $bin_dir,
|
|
'phpunit.xml.dist' => $target_dir,
|
|
'phpcs.ruleset.xml' => $target_dir,
|
|
);
|
|
|
|
foreach ( $to_copy as $file => $dir ) {
|
|
$file_name = "$dir/$file";
|
|
$force = \WP_CLI\Utils\get_flag_value( $assoc_args, 'force' );
|
|
$should_write_file = $this->prompt_if_files_will_be_overwritten( $file_name, $force );
|
|
if ( ! $should_write_file ) {
|
|
continue;
|
|
}
|
|
$files_written[] = $file_name;
|
|
|
|
$wp_filesystem->copy( WP_CLI_ROOT . "/templates/$file", $file_name, true );
|
|
if ( 'install-wp-tests.sh' === $file ) {
|
|
if ( ! $wp_filesystem->chmod( "$dir/$file", 0755 ) ) {
|
|
WP_CLI::warning( "Couldn't mark 'install-wp-tests.sh' as executable." );
|
|
}
|
|
}
|
|
}
|
|
$this->log_whether_files_written(
|
|
$files_written,
|
|
$skip_message = 'All test files were skipped.',
|
|
$success_message = 'Created test files.'
|
|
);
|
|
}
|
|
|
|
private function check_target_directory( $type, $target_dir ) {
|
|
if ( realpath( $target_dir ) ) {
|
|
$target_dir = realpath( $target_dir );
|
|
}
|
|
|
|
$parent_dir = dirname( $target_dir );
|
|
|
|
if ( "theme" === $type ) {
|
|
if ( WP_CONTENT_DIR . '/themes' === $parent_dir ) {
|
|
return true;
|
|
}
|
|
} elseif ( "plugin" === $type ) {
|
|
if ( WP_PLUGIN_DIR === $parent_dir ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function create_files( $files_and_contents, $force ) {
|
|
$wp_filesystem = $this->init_wp_filesystem();
|
|
$wrote_files = array();
|
|
|
|
foreach ( $files_and_contents as $filename => $contents ) {
|
|
$should_write_file = $this->prompt_if_files_will_be_overwritten( $filename, $force );
|
|
if ( ! $should_write_file ) {
|
|
continue;
|
|
}
|
|
|
|
$wp_filesystem->mkdir( dirname( $filename ) );
|
|
|
|
if ( ! $wp_filesystem->put_contents( $filename, $contents ) ) {
|
|
WP_CLI::error( "Error creating file: $filename" );
|
|
} elseif ( $should_write_file ) {
|
|
$wrote_files[] = $filename;
|
|
}
|
|
}
|
|
return $wrote_files;
|
|
}
|
|
|
|
protected function prompt_if_files_will_be_overwritten( $filename, $force ) {
|
|
$should_write_file = true;
|
|
if ( ! file_exists( $filename ) ) {
|
|
return true;
|
|
}
|
|
|
|
WP_CLI::warning( 'File already exists.' );
|
|
WP_CLI::log( $filename );
|
|
if ( ! $force ) {
|
|
do {
|
|
$answer = cli\prompt(
|
|
'Skip this file, or replace it with scaffolding?',
|
|
$default = false,
|
|
$marker = '[s/r]: '
|
|
);
|
|
} while ( ! in_array( $answer, array( 's', 'r' ) ) );
|
|
$should_write_file = 'r' === $answer;
|
|
}
|
|
|
|
$outcome = $should_write_file ? 'Replacing' : 'Skipping';
|
|
WP_CLI::log( $outcome . PHP_EOL );
|
|
|
|
return $should_write_file;
|
|
}
|
|
|
|
protected function log_whether_files_written( $files_written, $skip_message, $success_message ) {
|
|
if ( empty( $files_written ) ) {
|
|
WP_CLI::log( $skip_message );
|
|
} else {
|
|
WP_CLI::success( $success_message );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If you're writing your files to your theme directory your textdomain also needs to be the same as your theme.
|
|
* Same goes for when plugin is being used.
|
|
*/
|
|
private function get_textdomain( $textdomain, $args ) {
|
|
if ( strlen( $textdomain ) ) {
|
|
return $textdomain;
|
|
}
|
|
|
|
if ( $args['theme'] ) {
|
|
return strtolower( wp_get_theme()->template );
|
|
}
|
|
|
|
if ( $args['plugin'] && true !== $args['plugin'] ) {
|
|
return $args['plugin'];
|
|
}
|
|
|
|
return 'YOUR-TEXTDOMAIN';
|
|
}
|
|
|
|
private function pluralize( $word ) {
|
|
$plural = array(
|
|
'/(quiz)$/i' => '\1zes',
|
|
'/^(ox)$/i' => '\1en',
|
|
'/([m|l])ouse$/i' => '\1ice',
|
|
'/(matr|vert|ind)ix|ex$/i' => '\1ices',
|
|
'/(x|ch|ss|sh)$/i' => '\1es',
|
|
'/([^aeiouy]|qu)ies$/i' => '\1y',
|
|
'/([^aeiouy]|qu)y$/i' => '\1ies',
|
|
'/(hive)$/i' => '\1s',
|
|
'/(?:([^f])fe|([lr])f)$/i' => '\1\2ves',
|
|
'/sis$/i' => 'ses',
|
|
'/([ti])um$/i' => '\1a',
|
|
'/(buffal|tomat)o$/i' => '\1oes',
|
|
'/(bu)s$/i' => '1ses',
|
|
'/(alias|status)/i' => '\1es',
|
|
'/(octop|vir)us$/i' => '1i',
|
|
'/(ax|test)is$/i' => '\1es',
|
|
'/s$/i' => 's',
|
|
'/$/' => 's'
|
|
);
|
|
|
|
$uncountable = array( 'equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep' );
|
|
|
|
$irregular = array(
|
|
'person' => 'people',
|
|
'man' => 'men',
|
|
'woman' => 'women',
|
|
'child' => 'children',
|
|
'sex' => 'sexes',
|
|
'move' => 'moves'
|
|
);
|
|
|
|
$lowercased_word = strtolower( $word );
|
|
|
|
foreach ( $uncountable as $_uncountable ) {
|
|
if ( substr( $lowercased_word, ( - 1 * strlen( $_uncountable ) ) ) == $_uncountable ) {
|
|
return $word;
|
|
}
|
|
}
|
|
|
|
foreach ( $irregular as $_plural => $_singular ) {
|
|
if ( preg_match( '/(' . $_plural . ')$/i', $word, $arr ) ) {
|
|
return preg_replace( '/(' . $_plural . ')$/i', substr( $arr[0], 0, 1 ) . substr( $_singular, 1 ), $word );
|
|
}
|
|
}
|
|
|
|
foreach ( $plural as $rule => $replacement ) {
|
|
if ( preg_match( $rule, $word ) ) {
|
|
return preg_replace( $rule, $replacement, $word );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected function extract_args( $assoc_args, $defaults ) {
|
|
$out = array();
|
|
|
|
foreach ( $defaults as $key => $value ) {
|
|
$out[ $key ] = \WP_CLI\Utils\get_flag_value( $assoc_args, $key, $value );
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
protected function quote_comma_list_elements( $comma_list ) {
|
|
return "'" . implode( "', '", explode( ',', $comma_list ) ) . "'";
|
|
}
|
|
|
|
/**
|
|
* Create the themes directory if it doesn't already exist
|
|
*/
|
|
protected function maybe_create_themes_dir() {
|
|
|
|
$themes_dir = WP_CONTENT_DIR . '/themes';
|
|
if ( ! is_dir( $themes_dir ) ) {
|
|
wp_mkdir_p( $themes_dir );
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Create the plugins directory if it doesn't already exist
|
|
*/
|
|
protected function maybe_create_plugins_dir() {
|
|
|
|
if ( ! is_dir( WP_PLUGIN_DIR ) ) {
|
|
wp_mkdir_p( WP_PLUGIN_DIR );
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Initialize WP Filesystem
|
|
*/
|
|
protected function init_wp_filesystem() {
|
|
global $wp_filesystem;
|
|
WP_Filesystem();
|
|
|
|
return $wp_filesystem;
|
|
}
|
|
|
|
}
|
|
|
|
WP_CLI::add_command( 'scaffold', 'Scaffold_Command' );
|