Skip to content

Commit

Permalink
Merge branch 'release/3.0.2'
Browse files Browse the repository at this point in the history
  • Loading branch information
stmh committed Mar 1, 2019
2 parents bc95ce8 + 64283db commit 4cc853e
Show file tree
Hide file tree
Showing 25 changed files with 387 additions and 105 deletions.
10 changes: 10 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 3.0.2 / 2019-03-01

* Fix scaffolding of empty files via http
* Add support to limit files handled by twig by an extension as third parameter to copy_assets
* Add support for a dedicated projectFolder, add support for dependent variables, so you can compose variables from other variables
* strip first subfolder from filenames to copy when running app:scaffold, keep folder hierarchy for subsequents folders
* Refactor TaskContext::getStyle to TaskContext::io for clearer code
* Fix a bug on copyFrom for specific multi.site setups
* Fix bug when running app:scaffold where stages do not fire existing docker-tasks

## 3.0.1 / 2019-02-25

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion bin/phab
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ $application->setDispatcher($dispatcher);

$version = '@git_tag@';
if ($version[0] == '@') {
$version = '3.0.0';
$version = \Phabalicious\Utilities\Utilities::FALLBACK_VERSION;
}

$application->setVersion($version);
Expand Down
180 changes: 121 additions & 59 deletions src/Command/AppScaffoldCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,56 +144,28 @@ protected function execute(InputInterface $input, OutputInterface $output)
];

$questions = !empty($data['questions']) ? $data['questions'] : [];
$helper = $this->getHelper('question');

foreach ($questions as $key => $question_data) {
$errors = new ValidationErrorBag();
$validation = new ValidationService($question_data, $errors, 'questions');
$validation->hasKey('question', 'Please provide a question');
if (!empty($question_data['validation'])) {
$validation->hasKey('validation', 'Please provide a regex for validation');
$validation->hasKey('error', 'Please provide an error message when a validation fails');
}
if ($errors->hasErrors()) {
throw new ValidationFailedException($errors);
}
$context = new TaskContext($this, $input, $output);

$option_name = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $key));
if (in_array($option_name, $this->dynamicOptions)) {
$value = $input->getOption($option_name);
} else {
$question = new Question($question_data['question']. ': ');
$value = $helper->ask($input, $output, $question);
}

if (!empty($question_data['validation'])) {
if (!preg_match($question_data['validation'], $value)) {
throw new \InvalidArgumentException($question_data['error'] . ': ' . $value);
}
}
if (!empty($question_data['transform'])) {
$transform = strtolower($question_data['transform']);
$mapping = [
'lowercase' => 'strtolower',
'uppercase' => 'strtoupper',
];
if (isset($mapping[$transform])) {
$value = call_user_func($mapping[$transform], $value);
}
}
$tokens[$key] = trim($value);
}
$tokens = $this->askQuestions($input, $questions, $context, $tokens);
if (empty($tokens['name'])) {
throw new \InvalidArgumentException('Missing `name` in questions, aborting!');
}

$tokens['projectFolder'] = Utilities::cleanupString($tokens['name']);
$tokens['rootFolder'] = realpath($root_folder) . '/' . Utilities::cleanupString($tokens['name']);


if (!empty($data['variables'])) {
$tokens = Utilities::mergeData($data['variables'], $tokens);
}
if (empty($tokens['projectFolder'])) {
$tokens['projectFolder'] = $tokens['name'];
}

// Do a first round of replacements.
$replacements = $this->getReplacements($tokens);
foreach ($tokens as $ndx => $token) {
$tokens[$ndx] = strtr($token, $replacements);
}

$tokens['projectFolder'] = Utilities::cleanupString($tokens['projectFolder']);
$tokens['rootFolder'] = realpath($root_folder) . '/' . $tokens['projectFolder'];

$logger = $this->configuration->getLogger();
$shell = new LocalShellProvider($logger);
Expand All @@ -203,7 +175,6 @@ protected function execute(InputInterface $input, OutputInterface $output)
'rootFolder' => realpath($input->getOption('output')),
'shellExecutable' => '/bin/bash'
], $shell);
$context = new TaskContext($this, $input, $output);

$context->set('scriptData', $data['scaffold']);
$context->set('variables', $tokens);
Expand All @@ -212,6 +183,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
]);
$context->set('scaffoldData', $data);
$context->set('tokens', $tokens);
$context->set('loaderBase', $twig_loader_base);

// Setup twig
$loader = new \Twig_Loader_Filesystem($twig_loader_base);
Expand All @@ -220,39 +192,43 @@ protected function execute(InputInterface $input, OutputInterface $output)


if (empty($input->getOption('override')) && is_dir($tokens['rootFolder'])) {
$question = new ConfirmationQuestion('Destination folder exists! Continue anyways? ', false);
if (!$helper->ask($input, $output, $question)) {
if (!$context->io()->confirm(
'Destination folder exists! Continue anyways?',
false
)) {
return 1;
}
}

$context->getStyle()->comment('Create destination folder ...');
$context->io()->comment('Create destination folder ...');
$shell->run(sprintf('mkdir -p %s', $tokens['rootFolder']));

$context->getStyle()->comment('Start scaffolding script ...');
$context->io()->comment('Start scaffolding script ...');
$script->runScript($host_config, $context);

$context->getStyle()->success('Scaffolding finished successfully!');
$context->io()->success('Scaffolding finished successfully!');
return 0;
}

/**
* @param TaskContextInterface $context
* @param $target_folder
* @param string $data_key
* @param bool $limitedForTwigExtension
*/
public function copyAssets(TaskContextInterface $context, $target_folder, $data_key = 'assets')
{
public function copyAssets(
TaskContextInterface $context,
$target_folder,
$data_key = 'assets',
$limitedForTwigExtension = false
) {
if (!is_dir($target_folder)) {
mkdir($target_folder, 0777, true);
}
$data = $context->get('scaffoldData');
$tokens = $context->get('tokens');
$is_remote = substr($data['base_path'], 0, 4) == 'http';
$replacements = [];
foreach ($tokens as $key => $value) {
$replacements['%' . $key . '%'] = $value;
}
$replacements = $this->getReplacements($tokens);

if (empty($data[$data_key])) {
throw new \InvalidArgumentException('Scaffold-data does not contain ' . $data_key);
Expand All @@ -262,7 +238,7 @@ public function copyAssets(TaskContextInterface $context, $target_folder, $data_
$tmp_target_file = false;
if ($is_remote) {
$tmpl = $this->configuration->readHttpResource($data['base_path'] . '/' . $file_name);
if (empty($tmpl)) {
if ($tmpl === false) {
throw new \RuntimeException('Could not read remote asset: '. $data['base_path'] . '/' . $file_name);
}
$tmp_target_file = '/tmp/' . $file_name;
Expand All @@ -271,14 +247,35 @@ public function copyAssets(TaskContextInterface $context, $target_folder, $data_
}
file_put_contents('/tmp/' . $file_name, $tmpl);
}
$converted = $this->twig->render($file_name, $tokens);

if ($limitedForTwigExtension &&
('.' . pathinfo($file_name, PATHINFO_EXTENSION) !== $limitedForTwigExtension)
) {
$converted = file_get_contents($context->get('loaderBase') . '/' . $file_name);
} else {
$converted = $this->twig->render($file_name, $tokens);
}

if ($limitedForTwigExtension) {
$file_name = str_replace($limitedForTwigExtension, '', $file_name);
}

if ($tmp_target_file) {
unlink($tmp_target_file);
}

$target_file_name = $target_folder. '/' . strtr(basename($file_name), $replacements);
$context->getStyle()->comment(sprintf('Creating %s ...', $target_file_name));
file_put_contents($target_file_name, $converted);
$file_name = strtr($file_name, $replacements);
if (strpos($file_name, '/') !== false) {
$file_name = substr($file_name, strpos($file_name, '/', 1) + 1);
}

$target_file_path = $target_folder . '/' . $file_name;
if (!is_dir(dirname($target_file_path))) {
mkdir(dirname($target_file_path), 0777, true);
}

$context->io()->comment(sprintf('Creating %s ...', $target_file_path));
file_put_contents($target_file_path, $converted);
}
}

Expand All @@ -290,4 +287,69 @@ private function fakeUUID()
bin2hex(openssl_random_pseudo_bytes(2)) . '-' .
bin2hex(openssl_random_pseudo_bytes(6));
}

/**
* @param InputInterface $input
* @param array $questions
* @param TaskContext $context
* @param array $tokens
* @return array
* @throws ValidationFailedException
*/
protected function askQuestions(InputInterface $input, array $questions, TaskContext $context, array $tokens): array
{
foreach ($questions as $key => $question_data) {
$errors = new ValidationErrorBag();
$validation = new ValidationService($question_data, $errors, 'questions');
$validation->hasKey('question', 'Please provide a question');
if (!empty($question_data['validation'])) {
$validation->hasKey('validation', 'Please provide a regex for validation');
$validation->hasKey('error', 'Please provide an error message when a validation fails');
}
if ($errors->hasErrors()) {
throw new ValidationFailedException($errors);
}

$option_name = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $key));
if (in_array($option_name, $this->dynamicOptions)) {
$value = $input->getOption($option_name);
} else {
$value = $context->io()->ask(
$question_data['question'],
isset($question_data['default']) ? $question_data['default'] : null
);
}

if (!empty($question_data['validation'])) {
if (!preg_match($question_data['validation'], $value)) {
throw new \InvalidArgumentException($question_data['error'] . ': ' . $value);
}
}
if (!empty($question_data['transform'])) {
$transform = strtolower($question_data['transform']);
$mapping = [
'lowercase' => 'strtolower',
'uppercase' => 'strtoupper',
];
if (isset($mapping[$transform])) {
$value = call_user_func($mapping[$transform], $value);
}
}
$tokens[$key] = trim($value);
}
return $tokens;
}

/**
* @param $tokens
* @return array
*/
protected function getReplacements($tokens): array
{
$replacements = [];
foreach ($tokens as $key => $value) {
$replacements['%' . $key . '%'] = $value;
}
return $replacements;
}
}
2 changes: 1 addition & 1 deletion src/Command/BackupCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$files
);

$context->getStyle()->success('Backups created successfully!');
$context->io()->success('Backups created successfully!');
}

return $context->getResult('exitCode', 0);
Expand Down
2 changes: 1 addition & 1 deletion src/Command/GetBackupCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ protected function execute(InputInterface $input, OutputInterface $output)


if (count($files) > 0) {
$io = $context->getStyle();
$io = $context->io();
$io->title('Copied backup-set:');
$io->table(
['Type', 'File'],
Expand Down
2 changes: 1 addition & 1 deletion src/Command/GetFileCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
$context->set('sourceFile', $file);
$context->set('destFile', getcwd());

$context->getStyle()->comment('Get file `' . $file . '` from `' . $this->getHostConfig()['configName']. '`');
$context->io()->comment('Get file `' . $file . '` from `' . $this->getHostConfig()['configName']. '`');

$this->getMethods()->runTask('getFile', $this->getHostConfig(), $context);

Expand Down
18 changes: 7 additions & 11 deletions src/Command/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

namespace Phabalicious\Command;

use Phabalicious\Configuration\HostType;
use Phabalicious\Exception\EarlyTaskExitException;
use Phabalicious\Method\TaskContext;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
Expand Down Expand Up @@ -56,28 +54,26 @@ protected function execute(InputInterface $input, OutputInterface $output)
return $result;
}

$context = new TaskContext($this, $input, $output);

$host_config = $this->getHostConfig();
if ($host_config['supportsInstalls'] == false) {
throw new \InvalidArgumentException('This configuration disallows installs!');
}

if (!$input->getOption('yes')) {
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion(
'Install new database for configuration `' . $this->getHostConfig()['configName'] . '`? ',
false
);

if (!$helper->ask($input, $output, $question)) {
if (!$context->io()->confirm(sprintf(
'Install new database for configuration `%s`?',
$this->getHostConfig()['configName']
), false)) {
return 1;
}
}

$context = new TaskContext($this, $input, $output);

$next_tasks = $input->getOption('skip-reset') ? [] : ['reset'];

$context->getStyle()->comment('Installing new app for `' . $this->getHostConfig()['configName']. '`');
$context->io()->comment('Installing new app for `' . $this->getHostConfig()['configName']. '`');

try {
$this->getMethods()->runTask('install', $this->getHostConfig(), $context, $next_tasks);
Expand Down
4 changes: 0 additions & 4 deletions src/Command/InstallFromCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

namespace Phabalicious\Command;

use Phabalicious\Configuration\HostType;
use Phabalicious\Exception\EarlyTaskExitException;
use Phabalicious\Method\TaskContext;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class InstallFromCommand extends BaseCommand
{
Expand Down
4 changes: 2 additions & 2 deletions src/Command/JiraCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
);

$issues = $client->search($jql);
$context->getStyle()->title('My open tickets on ' . $this->configuration->getSetting('name'));
$context->getStyle()->table(
$context->io()->title('My open tickets on ' . $this->configuration->getSetting('name'));
$context->io()->table(
['Key', 'Summary', 'Url'],
array_map(function ($issue) use ($jira_config) {
return [
Expand Down
Loading

0 comments on commit 4cc853e

Please sign in to comment.