Failed Conditions
Push — master ( ed2e91...da30fd )
by Alexander
02:59
created

SearchCommand::completeOptionValues()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 2
crap 6
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\Exception\CommandException;
15
use ConsoleHelpers\SVNBuddy\Repository\RevisionLog\RevisionLog;
16
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
22
class SearchCommand extends AbstractCommand
23
{
24
25
	const MATCH_TYPE_FIRST = 'first';
26
27
	const MATCH_TYPE_LAST = 'last';
28
29
	/**
30
	 * Revision log
31
	 *
32
	 * @var RevisionLog
33
	 */
34
	private $_revisionLog;
35
36
	/**
37
	 * {@inheritdoc}
38
	 */
39
	protected function configure()
40
	{
41
		$this
42
			->setName('search')
43
			->setDescription('Searches for a revision, where text was added to a file or removed from it')
44
			->addArgument(
45
				'path',
46
				InputArgument::REQUIRED,
47
				'File path'
48
			)
49
			->addArgument(
50
				'keywords',
51
				InputArgument::REQUIRED,
52
				'Search keyword'
53
			)
54
			->addOption(
55
				'match-type',
56
				't',
57
				InputOption::VALUE_REQUIRED,
58
				'Match type, e.g. <comment>first</comment> or <comment>last</comment>',
59
				self::MATCH_TYPE_LAST
60
			);
61
62
		parent::configure();
63
	}
64
65
	/**
66
	 * Return possible values for the named option
67
	 *
68
	 * @param string            $optionName Option name.
69
	 * @param CompletionContext $context    Completion context.
70
	 *
71
	 * @return array
72
	 */
73
	public function completeOptionValues($optionName, CompletionContext $context)
74
	{
75
		$ret = parent::completeOptionValues($optionName, $context);
76
77
		if ( $optionName === 'match-type' ) {
78
			return $this->getMatchTypes();
79
		}
80
81
		return $ret;
82
	}
83
84
	/**
85
	 * {@inheritdoc}
86
	 */
87
	public function initialize(InputInterface $input, OutputInterface $output)
88
	{
89
		parent::initialize($input, $output);
90
91
		$this->_revisionLog = $this->getRevisionLog($this->getWorkingCopyUrl());
92
	}
93
94
	/**
95
	 * {@inheritdoc}
96
	 *
97
	 * @throws \RuntimeException When invalid direction is specified.
98
	 * @throws CommandException When no revisions found for path specified.
99
	 */
100
	protected function execute(InputInterface $input, OutputInterface $output)
101
	{
102
		$match_type = $this->io->getOption('match-type');
103
104
		if ( !in_array($match_type, $this->getMatchTypes()) ) {
105
			throw new \RuntimeException('The "' . $match_type . '" match type is invalid.');
106
		}
107
108
		$wc_path = $this->getWorkingCopyPath();
109
		$relative_path = $this->repositoryConnector->getRelativePath($wc_path);
110
111
		$keywords = $this->io->getArgument('keywords');
112
		$this->io->writeln(sprintf(
113
			'Searching for %s match of "<info>%s</info>" in "<info>%s</info>":',
114
			$match_type,
115
			$keywords,
116
			'.../' . basename($relative_path)
117
		));
118
119
		$revisions = $this->_revisionLog->find('paths', $relative_path);
120
121
		if ( !$revisions ) {
122
			throw new CommandException('No revisions found for "' . $relative_path . '" path.');
123
		}
124
125
		if ( $match_type === self::MATCH_TYPE_LAST ) {
126
			$revisions = array_reverse($revisions);
127
		}
128
129
		$scanned_revisions = 0;
130
		$total_revisions = count($revisions);
131
132
		$progress_bar = $this->io->createProgressBar($total_revisions);
133
		$progress_bar->setFormat(
134
			'<info>%message:6s%</info> %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%'
135
		);
136
137
		$index = 0;
138
		$found_revision = false;
139
140
		foreach ( $revisions as $index => $revision ) {
141
			$scanned_revisions++;
142
			$progress_bar->setMessage('r' . $revision);
143
			$progress_bar->display();
144
145
			$file_content = $this->repositoryConnector->getFileContent($wc_path, $revision);
146
147
			if ( strpos($file_content, $keywords) !== false ) {
148
				$found_revision = $revision;
149
				break;
150
			}
151
152
			$progress_bar->advance();
153
		}
154
155
		$this->io->writeln('');
156
157
		$scanned_percent = round(($scanned_revisions / $total_revisions) * 100);
158
		$this->io->writeln('Search efficiency: ' . $this->getSearchEfficiency(100 - $scanned_percent));
159
160
		if ( $found_revision ) {
161
			if ( $match_type === self::MATCH_TYPE_LAST ) {
162
				$this->io->writeln('Last seen at ' . $this->getRevisionInfo($found_revision) . '.');
163
164
				if ( array_key_exists($index - 1, $revisions) ) {
165
					$this->io->writeln('Deleted at ' . $this->getRevisionInfo($revisions[$index - 1]) . '.');
166
				}
167
			}
168
			else {
169
				$this->io->writeln('First seen at ' . $this->getRevisionInfo($found_revision) . '.');
170
			}
171
172
			return;
173
		}
174
175
		$this->io->writeln('<error>Keywords not found.</error>');
176
	}
177
178
	/**
179
	 * Returns match types.
180
	 *
181
	 * @return array
182
	 */
183
	protected function getMatchTypes()
184
	{
185
		return array(self::MATCH_TYPE_FIRST, self::MATCH_TYPE_LAST);
186
	}
187
188
	/**
189
	 * Returns search efficiency.
190
	 *
191
	 * @param float $percent Percent.
192
	 *
193
	 * @return string
194
	 */
195
	protected function getSearchEfficiency($percent)
196
	{
197
		if ( $percent >= 0 && $percent <= 20 ) {
198
			return '<fg=red>Very Poor</>';
199
		}
200
201
		if ( $percent > 20 && $percent <= 40 ) {
202
			return '<fg=red;options=bold>Poor</>';
203
		}
204
205
		if ( $percent > 40 && $percent <= 60 ) {
206
			return '<fg=yellow>Fair</>';
207
		}
208
209
		if ( $percent > 60 && $percent <= 80 ) {
210
			return '<fg=green;options=bold>Good</>';
211
		}
212
213
		if ( $percent > 80 && $percent <= 100 ) {
214
			return '<fg=green>Excellent</>';
215
		}
216
217
		return '';
218
	}
219
220
	/**
221
	 * Get revision information.
222
	 *
223
	 * @param integer $revision Revision.
224
	 *
225
	 * @return string
226
	 */
227
	protected function getRevisionInfo($revision)
228
	{
229
		$bugs = $this->_revisionLog->getRevisionsData('bugs', array($revision));
230
231
		$ret = '<info>' . $revision . '</info>';
232
233
		if ( $bugs[$revision] ) {
234
			$ret .= ' (bugs: <info>' . implode('</info>, <info>', $bugs[$revision]) . '</info>)';
235
		}
236
237
		return $ret;
238
	}
239
240
}
241