console-helpers /
svn-buddy
| 1 | <?php |
||||
| 2 | /** |
||||
| 3 | * This file is part of the SVN-Buddy library. |
||||
| 4 | * For the full copyright and license information, please view |
||||
| 5 | * the LICENSE file that was distributed with this source code. |
||||
| 6 | * |
||||
| 7 | * @copyright Alexander Obuhovich <[email protected]> |
||||
| 8 | * @link https://github.com/console-helpers/svn-buddy |
||||
| 9 | */ |
||||
| 10 | |||||
| 11 | namespace ConsoleHelpers\SVNBuddy\Command; |
||||
| 12 | |||||
| 13 | |||||
| 14 | use ConsoleHelpers\ConsoleKit\Config\ConfigEditor; |
||||
| 15 | use ConsoleHelpers\SVNBuddy\Config\AbstractConfigSetting; |
||||
| 16 | use ConsoleHelpers\SVNBuddy\Config\ArrayConfigSetting; |
||||
| 17 | use ConsoleHelpers\SVNBuddy\Config\ChoiceConfigSetting; |
||||
| 18 | use ConsoleHelpers\SVNBuddy\InteractiveEditor; |
||||
| 19 | use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext; |
||||
| 20 | use Symfony\Component\Console\Helper\Table; |
||||
| 21 | use Symfony\Component\Console\Helper\TableSeparator; |
||||
| 22 | use Symfony\Component\Console\Input\InputArgument; |
||||
| 23 | use Symfony\Component\Console\Input\InputInterface; |
||||
| 24 | use Symfony\Component\Console\Input\InputOption; |
||||
| 25 | use Symfony\Component\Console\Output\OutputInterface; |
||||
| 26 | |||||
| 27 | class ConfigCommand extends AbstractCommand implements IAggregatorAwareCommand |
||||
| 28 | { |
||||
| 29 | |||||
| 30 | /** |
||||
| 31 | * Editor. |
||||
| 32 | * |
||||
| 33 | * @var InteractiveEditor |
||||
| 34 | */ |
||||
| 35 | private $_editor; |
||||
| 36 | |||||
| 37 | /** |
||||
| 38 | * Config editor. |
||||
| 39 | * |
||||
| 40 | * @var ConfigEditor |
||||
| 41 | */ |
||||
| 42 | private $_configEditor; |
||||
| 43 | |||||
| 44 | /** |
||||
| 45 | * Config settings. |
||||
| 46 | * |
||||
| 47 | * @var AbstractConfigSetting[] |
||||
| 48 | */ |
||||
| 49 | protected $configSettings = array(); |
||||
| 50 | |||||
| 51 | /** |
||||
| 52 | * {@inheritdoc} |
||||
| 53 | */ |
||||
| 54 | protected function configure() |
||||
| 55 | { |
||||
| 56 | $this |
||||
| 57 | ->setName('config') |
||||
| 58 | ->setDescription('Change configuration settings, that are used by other commands') |
||||
| 59 | ->setAliases(array('cfg')) |
||||
| 60 | ->addArgument( |
||||
| 61 | 'path', |
||||
| 62 | InputArgument::OPTIONAL, |
||||
| 63 | 'Working copy path', |
||||
| 64 | '.' |
||||
| 65 | ) |
||||
| 66 | ->addOption( |
||||
| 67 | 'show', |
||||
| 68 | 's', |
||||
| 69 | InputOption::VALUE_REQUIRED, |
||||
| 70 | 'Shows only given (instead of all) setting value' |
||||
| 71 | ) |
||||
| 72 | ->addOption( |
||||
| 73 | 'edit', |
||||
| 74 | 'e', |
||||
| 75 | InputOption::VALUE_REQUIRED, |
||||
| 76 | 'Change setting value in the Interactive Editor' |
||||
| 77 | ) |
||||
| 78 | ->addOption( |
||||
| 79 | 'delete', |
||||
| 80 | 'd', |
||||
| 81 | InputOption::VALUE_REQUIRED, |
||||
| 82 | 'Delete setting' |
||||
| 83 | ) |
||||
| 84 | ->addOption( |
||||
| 85 | 'global', |
||||
| 86 | 'g', |
||||
| 87 | InputOption::VALUE_NONE, |
||||
| 88 | 'Operate on global instead of working copy-specific settings' |
||||
| 89 | ); |
||||
| 90 | |||||
| 91 | parent::configure(); |
||||
| 92 | } |
||||
| 93 | |||||
| 94 | /** |
||||
| 95 | * Prepare dependencies. |
||||
| 96 | * |
||||
| 97 | * @return void |
||||
| 98 | */ |
||||
| 99 | protected function prepareDependencies() |
||||
| 100 | { |
||||
| 101 | parent::prepareDependencies(); |
||||
| 102 | |||||
| 103 | $container = $this->getContainer(); |
||||
| 104 | |||||
| 105 | $this->_editor = $container['editor']; |
||||
| 106 | $this->_configEditor = $container['config_editor']; |
||||
| 107 | $this->configSettings = $this->getConfigSettings(); |
||||
| 108 | } |
||||
| 109 | |||||
| 110 | /** |
||||
| 111 | * Return possible values for the named option |
||||
| 112 | * |
||||
| 113 | * @param string $optionName Option name. |
||||
| 114 | * @param CompletionContext $context Completion context. |
||||
| 115 | * |
||||
| 116 | * @return array |
||||
| 117 | */ |
||||
| 118 | public function completeOptionValues($optionName, CompletionContext $context) |
||||
| 119 | { |
||||
| 120 | $ret = parent::completeOptionValues($optionName, $context); |
||||
| 121 | |||||
| 122 | if ( in_array($optionName, array('show', 'edit', 'delete')) ) { |
||||
| 123 | return array_keys($this->configSettings); |
||||
| 124 | } |
||||
| 125 | |||||
| 126 | return $ret; |
||||
| 127 | } |
||||
| 128 | |||||
| 129 | /** |
||||
| 130 | * {@inheritdoc} |
||||
| 131 | */ |
||||
| 132 | protected function execute(InputInterface $input, OutputInterface $output) |
||||
| 133 | { |
||||
| 134 | if ( $this->processShow() || $this->processEdit() || $this->processDelete() ) { |
||||
| 135 | return; |
||||
| 136 | } |
||||
| 137 | |||||
| 138 | $this->listSettings(); |
||||
| 139 | } |
||||
| 140 | |||||
| 141 | /** |
||||
| 142 | * Shows setting value. |
||||
| 143 | * |
||||
| 144 | * @return boolean |
||||
| 145 | */ |
||||
| 146 | protected function processShow() |
||||
| 147 | { |
||||
| 148 | $setting_name = $this->io->getOption('show'); |
||||
| 149 | |||||
| 150 | if ( $setting_name === null ) { |
||||
| 151 | return false; |
||||
| 152 | } |
||||
| 153 | |||||
| 154 | $this->listSettings($setting_name); |
||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 155 | |||||
| 156 | return true; |
||||
| 157 | } |
||||
| 158 | |||||
| 159 | /** |
||||
| 160 | * Changes setting value. |
||||
| 161 | * |
||||
| 162 | * @return boolean |
||||
| 163 | */ |
||||
| 164 | protected function processEdit() |
||||
| 165 | { |
||||
| 166 | $setting_name = $this->io->getOption('edit'); |
||||
| 167 | |||||
| 168 | if ( $setting_name === null ) { |
||||
| 169 | return false; |
||||
| 170 | } |
||||
| 171 | |||||
| 172 | $config_setting = $this->getConfigSetting($setting_name); |
||||
|
0 ignored issues
–
show
It seems like
$setting_name can also be of type string[]; however, parameter $name of ConsoleHelpers\SVNBuddy\...and::getConfigSetting() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 173 | $value = $config_setting->getValue($this->getValueFilter()); |
||||
| 174 | $retry = false; |
||||
|
0 ignored issues
–
show
|
|||||
| 175 | |||||
| 176 | if ( $config_setting instanceof ArrayConfigSetting ) { |
||||
| 177 | $value = implode(PHP_EOL, $value); |
||||
| 178 | } |
||||
| 179 | |||||
| 180 | do { |
||||
| 181 | try { |
||||
| 182 | $retry = false; |
||||
| 183 | |||||
| 184 | if ( $config_setting instanceof ChoiceConfigSetting ) { |
||||
| 185 | $value = $this->io->choose( |
||||
| 186 | 'Please choose value for "' . $setting_name . '" config setting [' . $value . ']:', |
||||
|
0 ignored issues
–
show
Are you sure
$setting_name of type boolean|string|string[] can be used in concatenation?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 187 | $config_setting->getChoices(), |
||||
| 188 | (string)$value, |
||||
| 189 | 'Option value "%s" is invalid.' |
||||
| 190 | ); |
||||
| 191 | } |
||||
| 192 | else { |
||||
| 193 | $value = $this->openEditor($value); |
||||
| 194 | } |
||||
| 195 | |||||
| 196 | $config_setting->setValue($value, $this->getScopeFilter()); |
||||
| 197 | $this->io->writeln('Setting <info>' . $setting_name . '</info> was edited.'); |
||||
| 198 | } |
||||
| 199 | catch ( \InvalidArgumentException $e ) { |
||||
| 200 | $this->io->writeln(array('<error>' . $e->getMessage() . '</error>', '')); |
||||
| 201 | |||||
| 202 | if ( $this->io->askConfirmation('Retry editing', false) ) { |
||||
| 203 | $retry = true; |
||||
| 204 | } |
||||
| 205 | } |
||||
| 206 | } while ( $retry ); |
||||
| 207 | |||||
| 208 | return true; |
||||
| 209 | } |
||||
| 210 | |||||
| 211 | /** |
||||
| 212 | * Opens value editing. |
||||
| 213 | * |
||||
| 214 | * @param mixed $value Value. |
||||
| 215 | * |
||||
| 216 | * @return mixed |
||||
| 217 | */ |
||||
| 218 | protected function openEditor($value) |
||||
| 219 | { |
||||
| 220 | return $this->_editor |
||||
| 221 | ->setDocumentName('config_setting_value') |
||||
| 222 | ->setContent($value) |
||||
| 223 | ->launch(); |
||||
| 224 | } |
||||
| 225 | |||||
| 226 | /** |
||||
| 227 | * Deletes setting value. |
||||
| 228 | * |
||||
| 229 | * @return boolean |
||||
| 230 | */ |
||||
| 231 | protected function processDelete() |
||||
| 232 | { |
||||
| 233 | $setting_name = $this->io->getOption('delete'); |
||||
| 234 | |||||
| 235 | if ( $setting_name === null ) { |
||||
| 236 | return false; |
||||
| 237 | } |
||||
| 238 | |||||
| 239 | $config_setting = $this->getConfigSetting($setting_name); |
||||
|
0 ignored issues
–
show
It seems like
$setting_name can also be of type string[]; however, parameter $name of ConsoleHelpers\SVNBuddy\...and::getConfigSetting() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 240 | $config_setting->setValue(null, $this->getScopeFilter()); |
||||
| 241 | $this->io->writeln('Setting <info>' . $setting_name . '</info> was deleted.'); |
||||
|
0 ignored issues
–
show
Are you sure
$setting_name of type boolean|string|string[] can be used in concatenation?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 242 | |||||
| 243 | return true; |
||||
| 244 | } |
||||
| 245 | |||||
| 246 | /** |
||||
| 247 | * Lists values for every stored setting. |
||||
| 248 | * |
||||
| 249 | * @param string $setting_name Setting name. |
||||
| 250 | * |
||||
| 251 | * @return void |
||||
| 252 | */ |
||||
| 253 | protected function listSettings($setting_name = null) |
||||
| 254 | { |
||||
| 255 | if ( isset($setting_name) ) { |
||||
| 256 | $this->getConfigSetting($setting_name); |
||||
| 257 | } |
||||
| 258 | |||||
| 259 | $extra_title = isset($setting_name) ? ' (filtered)' : ''; |
||||
| 260 | |||||
| 261 | if ( $this->isGlobal() ) { |
||||
| 262 | $this->io->writeln('Showing global settings' . $extra_title . ':'); |
||||
| 263 | } |
||||
| 264 | else { |
||||
| 265 | $this->io->writeln( |
||||
| 266 | 'Showing settings' . $extra_title . ' for <info>' . $this->getWorkingCopyUrl() . '</info> url:' |
||||
| 267 | ); |
||||
| 268 | } |
||||
| 269 | |||||
| 270 | $table = new Table($this->io->getOutput()); |
||||
| 271 | |||||
| 272 | $table->setHeaders(array( |
||||
| 273 | 'Setting Name', |
||||
| 274 | 'Setting Value', |
||||
| 275 | )); |
||||
| 276 | |||||
| 277 | $value_filter = $this->getValueFilter(); |
||||
| 278 | |||||
| 279 | $prev_heading = null; |
||||
| 280 | |||||
| 281 | foreach ( $this->getConfigSettingsByScope($this->getScopeFilter()) as $name => $config_setting ) { |
||||
| 282 | if ( isset($setting_name) && $name !== $setting_name ) { |
||||
| 283 | continue; |
||||
| 284 | } |
||||
| 285 | |||||
| 286 | list($new_heading,) = explode('.', $name); |
||||
| 287 | |||||
| 288 | if ( $prev_heading !== null && $new_heading !== $prev_heading ) { |
||||
| 289 | $table->addRow(new TableSeparator()); |
||||
| 290 | } |
||||
| 291 | |||||
| 292 | $table->addRow(array( |
||||
| 293 | $name, |
||||
| 294 | var_export($config_setting->getValue($value_filter), true), |
||||
| 295 | )); |
||||
| 296 | |||||
| 297 | $prev_heading = $new_heading; |
||||
| 298 | } |
||||
| 299 | |||||
| 300 | $table->render(); |
||||
| 301 | } |
||||
| 302 | |||||
| 303 | /** |
||||
| 304 | * Returns config settings filtered by scope. |
||||
| 305 | * |
||||
| 306 | * @param integer $scope_filter Scope filter. |
||||
| 307 | * |
||||
| 308 | * @return AbstractConfigSetting[] |
||||
| 309 | */ |
||||
| 310 | protected function getConfigSettingsByScope($scope_filter) |
||||
| 311 | { |
||||
| 312 | $ret = array(); |
||||
| 313 | |||||
| 314 | foreach ( $this->configSettings as $name => $config_setting ) { |
||||
| 315 | if ( $config_setting->isWithinScope($scope_filter) ) { |
||||
| 316 | $ret[$name] = $config_setting; |
||||
| 317 | } |
||||
| 318 | } |
||||
| 319 | |||||
| 320 | return $ret; |
||||
| 321 | } |
||||
| 322 | |||||
| 323 | /** |
||||
| 324 | * Validates setting name. |
||||
| 325 | * |
||||
| 326 | * @param string $name Setting name. |
||||
| 327 | * |
||||
| 328 | * @return AbstractConfigSetting |
||||
| 329 | * @throws \InvalidArgumentException When non-existing/outside of scope setting given. |
||||
| 330 | */ |
||||
| 331 | protected function getConfigSetting($name) |
||||
| 332 | { |
||||
| 333 | if ( !array_key_exists($name, $this->configSettings) ) { |
||||
| 334 | throw new \InvalidArgumentException('The "' . $name . '" setting is unknown.'); |
||||
| 335 | } |
||||
| 336 | |||||
| 337 | $config_setting = $this->configSettings[$name]; |
||||
| 338 | |||||
| 339 | if ( !$config_setting->isWithinScope($this->getScopeFilter()) ) { |
||||
| 340 | throw new \InvalidArgumentException('The "' . $name . '" setting cannot be used in this scope.'); |
||||
| 341 | } |
||||
| 342 | |||||
| 343 | return $config_setting; |
||||
| 344 | } |
||||
| 345 | |||||
| 346 | /** |
||||
| 347 | * Returns scope filter for viewing config settings. |
||||
| 348 | * |
||||
| 349 | * @return integer |
||||
| 350 | */ |
||||
| 351 | protected function getScopeFilter() |
||||
| 352 | { |
||||
| 353 | return $this->isGlobal() ? AbstractConfigSetting::SCOPE_GLOBAL : AbstractConfigSetting::SCOPE_WORKING_COPY; |
||||
| 354 | } |
||||
| 355 | |||||
| 356 | /** |
||||
| 357 | * Returns value filter for editing config settings. |
||||
| 358 | * |
||||
| 359 | * @return integer |
||||
| 360 | */ |
||||
| 361 | protected function getValueFilter() |
||||
| 362 | { |
||||
| 363 | return $this->isGlobal() ? AbstractConfigSetting::SCOPE_GLOBAL : null; |
||||
| 364 | } |
||||
| 365 | |||||
| 366 | /** |
||||
| 367 | * Returns possible settings with their defaults. |
||||
| 368 | * |
||||
| 369 | * @return AbstractConfigSetting[] |
||||
| 370 | */ |
||||
| 371 | protected function getConfigSettings() |
||||
| 372 | { |
||||
| 373 | /** @var AbstractConfigSetting[] $config_settings */ |
||||
| 374 | $config_settings = array(); |
||||
| 375 | |||||
| 376 | foreach ( $this->getApplication()->all() as $command ) { |
||||
| 377 | if ( $command instanceof IConfigAwareCommand ) { |
||||
| 378 | foreach ( $command->getConfigSettings() as $config_setting ) { |
||||
| 379 | $config_settings[$config_setting->getName()] = $config_setting; |
||||
| 380 | } |
||||
| 381 | } |
||||
| 382 | } |
||||
| 383 | |||||
| 384 | // Allow to operate on global settings outside of working copy. |
||||
| 385 | $wc_url = $this->isGlobal() ? '' : $this->getWorkingCopyUrl(); |
||||
| 386 | |||||
| 387 | foreach ( $config_settings as $config_setting ) { |
||||
| 388 | $config_setting->setWorkingCopyUrl($wc_url); |
||||
| 389 | $config_setting->setEditor($this->_configEditor); |
||||
| 390 | } |
||||
| 391 | |||||
| 392 | return $config_settings; |
||||
| 393 | } |
||||
| 394 | |||||
| 395 | /** |
||||
| 396 | * Determines if global only config settings should be used. |
||||
| 397 | * |
||||
| 398 | * @return boolean |
||||
| 399 | */ |
||||
| 400 | protected function isGlobal() |
||||
| 401 | { |
||||
| 402 | // During auto-complete the IO isn't set. |
||||
| 403 | if ( !isset($this->io) ) { |
||||
| 404 | return true; |
||||
| 405 | } |
||||
| 406 | |||||
| 407 | return $this->io->getOption('global'); |
||||
|
0 ignored issues
–
show
|
|||||
| 408 | } |
||||
| 409 | |||||
| 410 | /** |
||||
| 411 | * Returns option names, that makes sense to use in aggregation mode. |
||||
| 412 | * |
||||
| 413 | * @return array |
||||
| 414 | */ |
||||
| 415 | public function getAggregatedOptions() |
||||
| 416 | { |
||||
| 417 | return array(); |
||||
| 418 | } |
||||
| 419 | |||||
| 420 | } |
||||
| 421 |