Failed Conditions
Push — master ( fe778b...ae8928 )
by Alexander
02:48
created

ConfigCommand::getConfigSettings()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 23
ccs 0
cts 15
cp 0
rs 8.5906
cc 6
eloc 11
nc 16
nop 0
crap 42
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\SVNBuddy\Config\ArrayConfigSetting;
15
use ConsoleHelpers\ConsoleKit\Config\ConfigEditor;
16
use ConsoleHelpers\SVNBuddy\Config\AbstractConfigSetting;
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\Input\InputArgument;
22
use Symfony\Component\Console\Input\InputInterface;
23
use Symfony\Component\Console\Input\InputOption;
24
use Symfony\Component\Console\Output\OutputInterface;
25
26
class ConfigCommand extends AbstractCommand implements IAggregatorAwareCommand
27
{
28
29
	/**
30
	 * Editor.
31
	 *
32
	 * @var InteractiveEditor
33
	 */
34
	private $_editor;
35
36
	/**
37
	 * Config editor.
38
	 *
39
	 * @var ConfigEditor
40
	 */
41
	private $_configEditor;
42
43
	/**
44
	 * Config settings.
45
	 *
46
	 * @var AbstractConfigSetting[]
47
	 */
48
	protected $configSettings = array();
49
50
	/**
51
	 * {@inheritdoc}
52
	 */
53
	protected function configure()
54
	{
55
		$this
56
			->setName('config')
57
			->setDescription('Change configuration settings, that are used by other commands')
58
			->addArgument(
59
				'path',
60
				InputArgument::OPTIONAL,
61
				'Working copy path',
62
				'.'
63
			)
64
			->addOption(
65
				'show',
66
				's',
67
				InputOption::VALUE_REQUIRED,
68
				'Shows only given (instead of all) setting value'
69
			)
70
			->addOption(
71
				'edit',
72
				'e',
73
				InputOption::VALUE_REQUIRED,
74
				'Change setting value in the Interactive Editor'
75
			)
76
			->addOption(
77
				'delete',
78
				'd',
79
				InputOption::VALUE_REQUIRED,
80
				'Delete setting'
81
			)
82
			->addOption(
83
				'global',
84
				'g',
85
				InputOption::VALUE_NONE,
86
				'Operate on global instead of working copy-specific settings'
87
			);
88
89
		parent::configure();
90
	}
91
92
	/**
93
	 * Prepare dependencies.
94
	 *
95
	 * @return void
96
	 */
97
	protected function prepareDependencies()
98
	{
99
		parent::prepareDependencies();
100
101
		$container = $this->getContainer();
102
103
		$this->_editor = $container['editor'];
104
		$this->_configEditor = $container['config_editor'];
105
		$this->configSettings = $this->getConfigSettings();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getConfigSettings() of type array<integer|string,obj...AbstractConfigSetting>> is incompatible with the declared type array<integer,object<Con...AbstractConfigSetting>> of property $configSettings.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
106
	}
107
108
	/**
109
	 * Return possible values for the named option
110
	 *
111
	 * @param string            $optionName Option name.
112
	 * @param CompletionContext $context    Completion context.
113
	 *
114
	 * @return array
115
	 */
116
	public function completeOptionValues($optionName, CompletionContext $context)
117
	{
118
		$ret = parent::completeOptionValues($optionName, $context);
119
120
		if ( in_array($optionName, array('show', 'edit', 'delete')) ) {
121
			return array_keys($this->configSettings);
122
		}
123
124
		return $ret;
125
	}
126
127
	/**
128
	 * {@inheritdoc}
129
	 */
130
	protected function execute(InputInterface $input, OutputInterface $output)
131
	{
132
		if ( $this->processShow() || $this->processEdit() || $this->processDelete() ) {
133
			return;
134
		}
135
136
		$this->listSettings();
137
	}
138
139
	/**
140
	 * Shows setting value.
141
	 *
142
	 * @return boolean
143
	 */
144
	protected function processShow()
145
	{
146
		$setting_name = $this->io->getOption('show');
147
148
		if ( $setting_name === null ) {
149
			return false;
150
		}
151
152
		$this->listSettings($setting_name);
153
154
		return true;
155
	}
156
157
	/**
158
	 * Changes setting value.
159
	 *
160
	 * @return boolean
161
	 */
162
	protected function processEdit()
163
	{
164
		$setting_name = $this->io->getOption('edit');
165
166
		if ( $setting_name === null ) {
167
			return false;
168
		}
169
170
		$config_setting = $this->getConfigSetting($setting_name);
171
		$value = $config_setting->getValue($this->getValueFilter());
172
		$retry = false;
0 ignored issues
show
Unused Code introduced by
$retry is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
173
174
		if ( $config_setting instanceof ArrayConfigSetting ) {
175
			$value = implode(PHP_EOL, $value);
176
		}
177
178
		do {
179
			try {
180
				$retry = false;
181
182
				if ( $config_setting instanceof ChoiceConfigSetting ) {
183
					$value = $this->io->choose(
184
						'Please choose value for "' . $setting_name . '" config setting [' . $value . ']:',
185
						$config_setting->getChoices(),
186
						(string)$value,
187
						'Option value "%s" is invalid.'
188
					);
189
				}
190
				else {
191
					$value = $this->openEditor($value);
192
				}
193
194
				$config_setting->setValue($value, $this->getScopeFilter());
195
				$this->io->writeln('Setting <info>' . $setting_name . '</info> was edited.');
196
			}
197
			catch ( \InvalidArgumentException $e ) {
198
				$this->io->writeln(array('<error>' . $e->getMessage() . '</error>', ''));
199
200
				if ( $this->io->askConfirmation('Retry editing', false) ) {
201
					$retry = true;
202
				}
203
			}
204
		} while ( $retry );
205
206
		return true;
207
	}
208
209
	/**
210
	 * Opens value editing.
211
	 *
212
	 * @param mixed $value Value.
213
	 *
214
	 * @return mixed
215
	 */
216
	protected function openEditor($value)
217
	{
218
		return $this->_editor
219
			->setDocumentName('config_setting_value')
220
			->setContent($value)
221
			->launch();
222
	}
223
224
	/**
225
	 * Deletes setting value.
226
	 *
227
	 * @return boolean
228
	 */
229
	protected function processDelete()
230
	{
231
		$setting_name = $this->io->getOption('delete');
232
233
		if ( $setting_name === null ) {
234
			return false;
235
		}
236
237
		$config_setting = $this->getConfigSetting($setting_name);
238
		$config_setting->setValue(null, $this->getScopeFilter());
239
		$this->io->writeln('Setting <info>' . $setting_name . '</info> was deleted.');
240
241
		return true;
242
	}
243
244
	/**
245
	 * Lists values for every stored setting.
246
	 *
247
	 * @param string $setting_name Setting name.
248
	 *
249
	 * @return void
250
	 */
251
	protected function listSettings($setting_name = null)
252
	{
253
		if ( isset($setting_name) ) {
254
			$this->getConfigSetting($setting_name);
255
		}
256
257
		$extra_title = isset($setting_name) ? ' (filtered)' : '';
258
259
		if ( $this->isGlobal() ) {
260
			$this->io->writeln('Showing global settings' . $extra_title . ':');
261
		}
262
		else {
263
			$this->io->writeln(
264
				'Showing settings' . $extra_title . ' for <info>' . $this->getWorkingCopyUrl() . '</info> url:'
265
			);
266
		}
267
268
		$table = new Table($this->io->getOutput());
269
270
		$table->setHeaders(array(
271
			'Setting Name',
272
			'Setting Value',
273
		));
274
275
		$value_filter = $this->getValueFilter();
276
277
		foreach ( $this->getConfigSettingsByScope($this->getScopeFilter()) as $name => $config_setting ) {
278
			if ( isset($setting_name) && $name !== $setting_name ) {
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison !== seems to always evaluate to true as the types of $name (integer) and $setting_name (string) can never be identical. Maybe you want to use a loose comparison != instead?
Loading history...
279
				continue;
280
			}
281
282
			$table->addRow(array(
283
				$name,
284
				var_export($config_setting->getValue($value_filter), true),
285
			));
286
		}
287
288
		$table->render();
289
	}
290
291
	/**
292
	 * Returns config settings filtered by scope.
293
	 *
294
	 * @param integer $scope_filter Scope filter.
295
	 *
296
	 * @return AbstractConfigSetting[]
297
	 */
298
	protected function getConfigSettingsByScope($scope_filter)
299
	{
300
		$ret = array();
301
302
		foreach ( $this->configSettings as $name => $config_setting ) {
303
			if ( $config_setting->isWithinScope($scope_filter) ) {
304
				$ret[$name] = $config_setting;
305
			}
306
		}
307
308
		return $ret;
309
	}
310
311
	/**
312
	 * Validates setting name.
313
	 *
314
	 * @param string $name Setting name.
315
	 *
316
	 * @return AbstractConfigSetting
317
	 * @throws \InvalidArgumentException When non-existing/outside of scope setting given.
318
	 */
319
	protected function getConfigSetting($name)
320
	{
321
		if ( !array_key_exists($name, $this->configSettings) ) {
322
			throw new \InvalidArgumentException('The "' . $name . '" setting is unknown.');
323
		}
324
325
		$config_setting = $this->configSettings[$name];
326
327
		if ( !$config_setting->isWithinScope($this->getScopeFilter()) ) {
328
			throw new \InvalidArgumentException('The "' . $name . '" setting cannot be used in this scope.');
329
		}
330
331
		return $config_setting;
332
	}
333
334
	/**
335
	 * Returns scope filter for viewing config settings.
336
	 *
337
	 * @return integer
338
	 */
339
	protected function getScopeFilter()
340
	{
341
		return $this->isGlobal() ? AbstractConfigSetting::SCOPE_GLOBAL : AbstractConfigSetting::SCOPE_WORKING_COPY;
342
	}
343
344
	/**
345
	 * Returns value filter for editing config settings.
346
	 *
347
	 * @return integer
348
	 */
349
	protected function getValueFilter()
350
	{
351
		return $this->isGlobal() ? AbstractConfigSetting::SCOPE_GLOBAL : null;
352
	}
353
354
	/**
355
	 * Returns possible settings with their defaults.
356
	 *
357
	 * @return AbstractConfigSetting[]
358
	 */
359
	protected function getConfigSettings()
360
	{
361
		/** @var AbstractConfigSetting[] $config_settings */
362
		$config_settings = array();
363
364
		foreach ( $this->getApplication()->all() as $command ) {
365
			if ( $command instanceof IConfigAwareCommand ) {
366
				foreach ( $command->getConfigSettings() as $config_setting ) {
367
					$config_settings[$config_setting->getName()] = $config_setting;
368
				}
369
			}
370
		}
371
372
		// Allow to operate on global settings outside of working copy.
373
		$wc_url = $this->isGlobal() ? '' : $this->getWorkingCopyUrl();
374
375
		foreach ( $config_settings as $config_setting ) {
376
			$config_setting->setWorkingCopyUrl($wc_url);
377
			$config_setting->setEditor($this->_configEditor);
378
		}
379
380
		return $config_settings;
381
	}
382
383
	/**
384
	 * Determines if global only config settings should be used.
385
	 *
386
	 * @return boolean
387
	 */
388
	protected function isGlobal()
389
	{
390
		// During auto-complete the IO isn't set.
391
		if ( !isset($this->io) ) {
392
			return true;
393
		}
394
395
		return $this->io->getOption('global');
396
	}
397
398
	/**
399
	 * Returns option names, that makes sense to use in aggregation mode.
400
	 *
401
	 * @return array
402
	 */
403
	public function getAggregatedOptions()
404
	{
405
		return array();
406
	}
407
408
}
409