Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added more helpers. #34

Merged
merged 3 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions .customizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class CustomizerConfig {
/**
* Messages used by the command.
*
* @param CustomizeCommand $customizer
* @param CustomizeCommand $c
* The Customizer instance.
*
* @return array<string,string|array<string>>
Expand All @@ -23,7 +23,7 @@ class CustomizerConfig {
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public static function messages(CustomizeCommand $customizer): array {
public static function messages(CustomizeCommand $c): array {
return [
// This is an example of a custom message that overrides the default
// message with name `welcome`.
Expand Down Expand Up @@ -70,7 +70,7 @@ public static function messages(CustomizeCommand $customizer): array {
* ];
* @endcode
*
* @param CustomizeCommand $customizer
* @param CustomizeCommand $c
* The CustomizeCommand object. Can be used to access the command properties
* and methods to prepare questions. Note that the questions callbacks
* already receive the command object as an argument, so this argument is
Expand All @@ -95,7 +95,7 @@ public static function messages(CustomizeCommand $customizer): array {
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public static function questions(CustomizeCommand $customizer): array {
public static function questions(CustomizeCommand $c): array {
// This an example of questions that can be asked to customize the project.
// You can adjust this method to ask questions that are relevant to your
// project.
Expand All @@ -107,7 +107,7 @@ public static function questions(CustomizeCommand $customizer): array {
'Name' => [
// The question callback function defines how the question is asked.
// In this case, we ask the user to provide a package name as a string.
'question' => static fn(array $answers, CustomizeCommand $customizer): mixed => $customizer->io->ask('Package name', NULL, static function (string $value): string {
'question' => static fn(array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', NULL, static function (string $value): string {
// This is a validation callback that checks if the package name is
// valid. If not, an exception is thrown with a message shown to the
// user.
Expand All @@ -120,33 +120,33 @@ public static function questions(CustomizeCommand $customizer): array {
// The process callback function defines how the answer is processed.
// The processing takes place only after all answers are received and
// the user confirms the intended changes.
'process' => static function (string $title, string $answer, array $answers, CustomizeCommand $customizer): void {
$name = is_string($customizer->packageData['name'] ?? NULL) ? $customizer->packageData['name'] : '';
'process' => static function (string $title, string $answer, array $answers, CustomizeCommand $c): void {
$name = is_string($c->packageData['name'] ?? NULL) ? $c->packageData['name'] : '';
// Update the package data.
$customizer->packageData['name'] = $answer;
$c->packageData['name'] = $answer;
// Write the updated composer.json file.
$customizer->writeComposerJson($customizer->packageData);
CustomizeCommand::writeComposerJson($c->cwd . '/composer.json', $c->packageData);
// Replace the package name in the project files.
$customizer->replaceInPath($customizer->cwd, $name, $answer);
$c->replaceInPath($c->cwd, $name, $answer);
},
],
'Description' => [
// For this question, we are using an answer from the previous question
// in the title of the question.
'question' => static fn(array $answers, CustomizeCommand $customizer): mixed => $customizer->io->ask(sprintf('Description for %s', $answers['Name'])),
'process' => static function (string $title, string $answer, array $answers, CustomizeCommand $customizer): void {
$description = is_string($customizer->packageData['description'] ?? NULL) ? $customizer->packageData['description'] : '';
$customizer->packageData['description'] = $answer;
$customizer->writeComposerJson($customizer->packageData);
$customizer->replaceInPath($customizer->cwd, $description, $answer);
'question' => static fn(array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])),
'process' => static function (string $title, string $answer, array $answers, CustomizeCommand $c): void {
$description = is_string($c->packageData['description'] ?? NULL) ? $c->packageData['description'] : '';
$c->packageData['description'] = $answer;
CustomizeCommand::writeComposerJson($c->cwd . '/composer.json', $c->packageData);
$c->replaceInPath($c->cwd, $description, $answer);
},
],
'License' => [
// For this question, we are using a pre-defined list of options.
// For processing, we are using a separate method named 'processLicense'
// (only for the demonstration purposes; it could have been an
// anonymous function).
'question' => static fn(array $answers, CustomizeCommand $customizer): mixed => $customizer->io->choice('License type', [
'question' => static fn(array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', [
'MIT',
'GPL-3.0-or-later',
'Apache-2.0',
Expand All @@ -167,25 +167,25 @@ public static function questions(CustomizeCommand $customizer): array {
* The answer to the question.
* @param array<string,string> $answers
* All answers received so far.
* @param CustomizeCommand $customizer
* @param CustomizeCommand $c
* The Customizer instance.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public static function processLicense(string $title, string $answer, array $answers, CustomizeCommand $customizer): void {
$customizer->packageData['license'] = $answer;
$customizer->writeComposerJson($customizer->packageData);
public static function processLicense(string $title, string $answer, array $answers, CustomizeCommand $c): void {
$c->packageData['license'] = $answer;
CustomizeCommand::writeComposerJson($c->cwd . '/composer.json', $c->packageData);
}

/**
* A callback to process cleanup.
*
* @param array<string,mixed> $composerjson
* The composer.json file content passed by reference.
* @param \AlexSkrypnyk\Customizer\CustomizeCommand $customizer
* @param \AlexSkrypnyk\Customizer\CustomizeCommand $c
* The Customizer instance.
*/
public static function cleanup(array &$composerjson, CustomizeCommand $customizer): void {
public static function cleanup(array &$composerjson, CustomizeCommand $c): void {
// Here you can remove any sections from the composer.json file that are not
// needed for the project before all dependencies are updated.
//
Expand Down
92 changes: 69 additions & 23 deletions CustomizeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->cwd = (string) getcwd();
$this->fs = new Filesystem();
$this->initConfig();
$this->packageData = $this->readComposerJson();
$this->packageData = static::readComposerJson($this->cwd . '/composer.json');
$this->isComposerDependenciesInstalled = file_exists($this->cwd . '/composer.lock') && file_exists($this->cwd . '/vendor');

$this->io->title($this->message('welcome'));
Expand Down Expand Up @@ -225,17 +225,17 @@ protected function process(array $answers): void {
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function cleanup(): void {
$json = $this->readComposerJson();
$json = $this->readComposerJson($this->cwd . '/composer.json');

$is_dependency = (
!empty($json['require'])
&& is_array($json['require'])
&& isset($json['require']['alexskrypnyk/customizer'])
) || (
!empty($json['require-dev'])
&& is_array($json['require-dev'])
&& isset($json['require-dev']['alexskrypnyk/customizer'])
);
!empty($json['require'])
&& is_array($json['require'])
&& isset($json['require']['alexskrypnyk/customizer'])
) || (
!empty($json['require-dev'])
&& is_array($json['require-dev'])
&& isset($json['require-dev']['alexskrypnyk/customizer'])
);

static::arrayUnsetDeep($json, ['autoload', 'classmap'], basename(__FILE__), FALSE);
static::arrayUnsetDeep($json, ['scripts', 'customize']);
Expand All @@ -251,7 +251,7 @@ protected function cleanup(): void {

// If the package data has changed, update the composer.json file.
if (!empty($json) && strcmp(serialize($this->packageData), serialize($json)) !== 0) {
$this->writeComposerJson($json);
$this->writeComposerJson($this->cwd . '/composer.json', $json);

// We can only update the composer.lock file if the Customizer was not run
// after the Composer dependencies were installed and the Customizer
Expand Down Expand Up @@ -488,8 +488,8 @@ protected function debug(string $message, string ...$args): void {
* @return array <string,mixed>
* Composer.json data as an associative array.
*/
public function readComposerJson(): array {
$contents = file_get_contents($this->cwd . '/composer.json');
public static function readComposerJson(string $file): array {
$contents = @file_get_contents($file);

if ($contents === FALSE) {
throw new \RuntimeException('Failed to read composer.json');
Expand All @@ -509,11 +509,13 @@ public function readComposerJson(): array {
*
* This is a helper method to be used in processing callbacks.
*
* @param array<string,mixed> $data
* @param string $file
* File path.
* @param array<int|string,mixed> $data
* Composer.json data as an associative array.
*/
public function writeComposerJson(array $data): void {
file_put_contents($this->cwd . '/composer.json', json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
public static function writeComposerJson(string $file, array $data): void {
file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
}

/**
Expand All @@ -528,11 +530,16 @@ public function writeComposerJson(array $data): void {
* @param string $replace
* Replace string .
* @param bool $replace_line
* Replace for a whole line or only the occurrence.
* Replace a whole line or only the occurrence.
* @param array<int,string>|null $exclude
* Directories to exclude.
* Defaults to ['.git', '.idea', 'vendor', 'node_modules'].
*/
public static function replaceInPath(string $path, string $search, string $replace, bool $replace_line = FALSE): void {
public static function replaceInPath(string $path, string $search, string $replace, bool $replace_line = FALSE, ?array $exclude = NULL): void {
$dir = dirname($path);
$filename = basename($path);
$is_regex = @preg_match($search, '') !== FALSE;
$exclude = $exclude ?? ['.git', '.idea', 'vendor', 'node_modules'];

if (is_dir($path)) {
$dir = $path;
Expand All @@ -542,7 +549,7 @@ public static function replaceInPath(string $path, string $search, string $repla
$finder = Finder::create()
->ignoreVCS(TRUE)
->ignoreDotFiles(FALSE)
->exclude(['vendor'])
->exclude($exclude)
->files()
->contains($search)
->in($dir);
Expand All @@ -555,7 +562,11 @@ public static function replaceInPath(string $path, string $search, string $repla
$file_path = $file->getRealPath();
$file_content = file_get_contents($file_path);
if ($file_content !== FALSE) {
if ($replace_line) {

if ($is_regex) {
$new_content = preg_replace($search, $replace, $file_content);
}
elseif ($replace_line) {
$new_content = preg_replace(sprintf('/^.*%s.*/m', $search), $replace, $file_content);
}
else {
Expand All @@ -569,6 +580,41 @@ public static function replaceInPath(string $path, string $search, string $repla
}
}

/**
* Replace a string in a file or all files in a directory between two markers.
*
* @param string $path
* Directory or file path.
* @param string $search
* Search string.
* @param string $replace
* Replace string.
* @param string $start
* Start marker.
* @param string $end
* End marker.
*/
public static function replaceInPathBetween(string $path, string $search, string $replace, string $start, string $end): void {
$search = empty($search) ? '.*' : preg_quote($search, '/');
$replace = empty($replace) ? '$1' : preg_quote($replace, '/');

self::replaceInPath($path, '/(\W?)(' . preg_quote($start, '/') . '\W' . $search . '\W' . preg_quote($end, '/') . ')(\W)/s', $replace);
}

/**
* Uncomment a line in a file or all files in a directory.
*
* @param string $path
* Directory or file path.
* @param string $search
* Search string.
* @param string $comment_string
* Comment string.
*/
public static function uncommentLine(string $path, string $search, string $comment_string = '#'): void {
self::replaceInPath($path, '/#\s*' . preg_quote(ltrim($search, $comment_string), '/') . '/', ltrim($search, $comment_string));
}

/**
* Unset a value in a nested array by path, removing empty arrays.
*
Expand Down Expand Up @@ -629,7 +675,7 @@ public static function arrayUnsetDeep(array &$array, array $path, ?string $value
* class will **recursively replace** the messages defined here. This means
* that only specific messages may be overridden by the configuration class.
*
* @param CustomizeCommand $customizer
* @param CustomizeCommand $c
* The command instance.
*
* @return array<string,string|array<string>>
Expand All @@ -638,7 +684,7 @@ public static function arrayUnsetDeep(array &$array, array $path, ?string $value
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public static function messages(CustomizeCommand $customizer): array {
public static function messages(CustomizeCommand $c): array {
return [
'welcome' => 'Welcome to {{ package.name }} project customizer',
'banner' => [
Expand Down Expand Up @@ -684,7 +730,7 @@ public static function messages(CustomizeCommand $customizer): array {
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public static function questions(CustomizeCommand $customizer): array {
public static function questions(CustomizeCommand $c): array {
return [];
}

Expand Down
Loading