CommitMessage::removeComments()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 1
dl 0
loc 10
ccs 8
cts 8
cp 1
crap 3
rs 9.9332
c 0
b 0
f 0
1
<?php
2
namespace Elgg;
3
4
use UnexpectedValueException;
5
6
7
/**
8
 * Provides a structured format for parsing and examining our commit messages.
9
 * 
10
 * @package Elgg.Core
11
 * @since   1.9
12
 * 
13
 * @access  private
14
 */
15
class CommitMessage {
16
	/**
17
	 * Valid parts of the message
18
	 * The index is the index of the $matches array for regex
19
	 * @var array
20
	 */
21
	private $validMsgParts = array(
22
		1 => 'type',
23
		2 => 'component',
24
		3 => 'summary',
25
		5 => 'body'
26
	);
27
28
	/**
29
	 * Message type
30
	 *
31
	 * @var string
32
	 */
33
	private $type;
34
35
	/**
36
	 * Message component
37
	 *
38
	 * @var string
39
	 */
40
	private $component;
41
42
	/**
43
	 * Message summary
44
	 *
45
	 * @var string
46
	 */
47
	private $summary;
48
49
	/**
50
	 * Optional message body
51
	 *
52
	 * @var string
53
	 */
54
	private $body;
55
56
	/**
57
	 * Original message text
58
	 *
59
	 * @var string
60
	 */
61
	private $originalMsg = '';
62
63
	/**
64
	 * Modified message text.
65
	 *
66
	 * @var string
67
	 */
68
	private $msg = '';
69
70
	/**
71
	 * An array of lines over the valid length
72
	 *
73
	 * @var array
74
	 */
75
	private $lengthyLines;
76
77
	/**
78
	 * Valid types
79
	 *
80
	 * @var array
81
	 */
82
	private static $validTypes = array(
83
		'feature',
84
		'feat',
85
		'fix',
86
		'fixes',
87
		'fixed',
88
		'doc',
89
		'docs',
90
		'chore',
91
		'perf',
92
		'performance',
93
		'security',
94
		'deprecate',
95
		'deprecates'
96
	);
97
98
	/**
99
	 * Valid components
100
	 *
101
	 * @todo Not checked yet.
102
	 *
103
	 * @var array
104
	 */
105
	private $validComponents = array(
106
		'i18n',
107
		'seo',
108
		'a11y',
109
		'cache',
110
		'db',
111
		'views',
112
		'session',
113
		'router'
114
	);
115
116
	/**
117
	 * Ignore messages that match this regex
118
	 *
119
	 * @var string
120
	 */
121
	private $ignoreRegex = '/^Merge |^Revert /i';
122
123
	/**
124
	 * Regex to extract the message parts
125
	 * 
126
	 * type(component): message
127
	 * with an optional body following
128
	 *
129
	 * $matches = array(
130
	 *     0 => everything
131
	 *     1 => type
132
	 *     2 => component
133
	 *     3 => summary
134
	 *     4 => body (with leading \ns)
135
	 *     5 => body (without leading \ns)
136
	 * )
137
	 *
138
	 * @var string
139
	 */
140
	private $formatRegex = "/^(\w*)\(([\w]+)\)\: ([^\n]*)(\n\n?(.*))?$/is";
141
142
	/**
143
	 * Max length of any line
144
	 * @var int
145
	 */
146
	private $maxLineLength = 160;
147
148
	/**
149
	 * Checks if a commit message is in the correct format
150
	 *
151
	 * @param string|null $msg The commit message
152
	 */
153 11
	public function __construct($msg = null) {
154 11
		if ($msg) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $msg of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
155 6
			$this->setMsg($msg);
156 6
		}
157 11
	}
158
159
	/**
160
	 * Sets the active message
161
	 * 
162
	 * @param string $msg The message content
163
	 * 
164
	 * @return void
165
	 */
166 11
	public function setMsg($msg) {
167 11
		$this->originalMsg = $msg;
168
169 11
		$msg = str_replace(array("\r", "\n"), "\n", $msg);
170 11
		$this->msg = $this->removeComments($msg);
171 11
		$this->processMsg();
172 11
	}
173
174
	/**
175
	 * Return the processed message.
176
	 *
177
	 * @return string
178
	 */
179
	public function getMsg() {
180
		return $this->msg;
181
	}
182
183
	/**
184
	 * Return the original message
185
	 *
186
	 * @return string
187
	 */
188
	public function getOriginalMsg() {
189
		return $this->originalMsg;
190
	}
191
192
	/**
193
	 * Should this msg be ignored for formatting?
194
	 *
195
	 * @return boolean
196
	 */
197 2
	public function shouldIgnore() {
198 2
		return preg_match($this->ignoreRegex, $this->msg) === 1;
199
	}
200
201
	/**
202
	 * Process the msg into its parts
203
	 *
204
	 * @return array
205
	 */
206 11
	private function processMsg() {
207 11
		$matches = array();
208
		
209 11
		preg_match($this->formatRegex, $this->msg, $matches);
210 11
		foreach ($this->validMsgParts as $i => $part) {
211 11
			$this->$part = isset($matches[$i]) ? $matches[$i] : '';
212 11
		}
213
214 11
		$this->lengthyLines = $this->findLengthyLines($this->msg, $this->maxLineLength);
215 11
	}
216
217
	/**
218
	 * Are all parts of the message valid
219
	 *
220
	 * @return bool
221
	 */
222
	public function isValid() {
223
		return $this->isValidFormat() &&
224
				$this->isValidLineLength() &&
225
				$this->isValidType();
226
	}
227
	
228
	/**
229
	 * Whether the message format conforms to our standards.
230
	 * 
231
	 * @return boolean
232
	 */
233 6
	public function isValidFormat() {
234 6
		return preg_match($this->formatRegex, $this->msg) === 1;
235
	}
236
237
	/**
238
	 * Are any of the lines too long?
239
	 *
240
	 * @see getLengthyLines() to get line numbers
241
	 *
242
	 * @return bool
243
	 */
244 1
	public function isValidLineLength() {
245 1
		return count($this->lengthyLines) === 0;
246
	}
247
248
	/**
249
	 * Get the line number of lines that are too long
250
	 *
251
	 * @return array
252
	 */
253 1
	public function getLengthyLines() {
254 1
		return $this->lengthyLines;
255
	}
256
257
	/**
258
	 * Is the type valid
259
	 *
260
	 * @return boolean
261
	 */
262 1
	public function isValidType() {
263 1
		return in_array($this->type, self::$validTypes);
264
	}
265
266
	/**
267
	 * Return all valid types
268
	 *
269
	 * @return array
270
	 */
271 1
	public static function getValidTypes() {
272 1
		return self::$validTypes;
273
	}
274
275
	/**
276
	 * Return the max line length
277
	 *
278
	 * @return int
279
	 */
280
	public function getMaxLineLength() {
281
		return $this->maxLineLength;
282
	}
283
284
	/**
285
	 * Sets the max line length allowed.
286
	 * Defaults to 160.
287
	 *
288
	 * @param int $len The maximum length.
289
	 * 
290
	 * @return void
291
	 */
292 2
	public function setMaxLineLength($len) {
293 2
		$this->maxLineLength = (int)$len;
294 2
	}
295
296
	/**
297
	 * Get part of the message
298
	 *
299
	 * @param string $part One section of the message.
300
	 * 
301
	 * @return string
302
	 * @throws UnexpectedValueException
303
	 */
304 3
	public function getPart($part) {
305 3
		if ($part && in_array($part, $this->validMsgParts)) {
306 3
			return $this->$part;
307
		}
308
309
		throw new UnexpectedValueException("`$part` not a valid message part.");
310
	}
311
312
	/**
313
	 * Removes all lines that start with #
314
	 *
315
	 * @param string $msg The msg body of the commit
316
	 * 
317
	 * @return string
318
	 */
319 12
	public static function removeComments($msg) {
320 12
		$msg_arr = array();
321 12
		foreach (explode("\n", rtrim($msg)) as $line) {
322 12
			if (substr($line, 0, 1) !== '#') {
323 12
				$msg_arr[] = $line;
324 12
			}
325 12
		}
326
327 12
		return implode("\n", $msg_arr);
328
	}
329
330
	/**
331
	 * Returns an array of line numbers > $max_len
332
	 *
333
	 * @param string $msg     The content to parse
334
	 * @param int    $max_len Maximum length between \n in the $msg
335
	 * 
336
	 * @return array
337
	 */
338 12
	public static function findLengthyLines($msg, $max_len) {
339 12
		$lines = explode("\n", $msg);
340 12
		$lengthy_lines = array();
341
342 12
		foreach ($lines as $i => $line) {
343 12
			if (strlen($line) > $max_len) {
344 3
				$lengthy_lines[] = ++$i;
345 3
			}
346 12
		}
347
348 12
		return $lengthy_lines;
349
	}
350
351
352
	/** @inheritDoc */
353
	public function __toString() {
354
		return $this->getMsg();
355
	}
356
}
357