Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

engine/classes/Elgg/CommitMessage.php (1 issue)

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 = [
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 = [
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
	 * Ignore messages that match this regex
100
	 *
101
	 * @var string
102
	 */
103
	private $ignoreRegex = '/^Merge |^Revert /i';
104
105
	/**
106
	 * Regex to extract the message parts
107
	 *
108
	 * type(component): message
109
	 * with an optional body following
110
	 *
111
	 * $matches = array(
112
	 *     0 => everything
113
	 *     1 => type
114
	 *     2 => component
115
	 *     3 => summary
116
	 *     4 => body (with leading \ns)
117
	 *     5 => body (without leading \ns)
118
	 * )
119
	 *
120
	 * @var string
121
	 */
122
	private $formatRegex = "/^(\w*)\(([\w]+)\)\: ([^\n]*)(\n\n?(.*))?$/is";
123
124
	/**
125
	 * Max length of any line
126
	 * @var int
127
	 */
128
	private $maxLineLength = 160;
129
130
	/**
131
	 * Checks if a commit message is in the correct format
132
	 *
133
	 * @param string|null $msg The commit message
134
	 */
135 11
	public function __construct($msg = null) {
136 11
		if ($msg) {
0 ignored issues
show
Bug Best Practice introduced by Brett Profitt
The expression $msg of type null|string 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...
137 6
			$this->setMsg($msg);
138
		}
139 11
	}
140
141
	/**
142
	 * Sets the active message
143
	 *
144
	 * @param string $msg The message content
145
	 *
146
	 * @return void
147
	 */
148 11
	public function setMsg($msg) {
149 11
		$this->originalMsg = $msg;
150
151 11
		$msg = str_replace(["\r", "\n"], "\n", $msg);
152 11
		$this->msg = $this->removeComments($msg);
153 11
		$this->processMsg();
154 11
	}
155
156
	/**
157
	 * Return the processed message.
158
	 *
159
	 * @return string
160
	 */
161
	public function getMsg() {
162
		return $this->msg;
163
	}
164
165
	/**
166
	 * Return the original message
167
	 *
168
	 * @return string
169
	 */
170
	public function getOriginalMsg() {
171
		return $this->originalMsg;
172
	}
173
174
	/**
175
	 * Should this msg be ignored for formatting?
176
	 *
177
	 * @return boolean
178
	 */
179 2
	public function shouldIgnore() {
180 2
		return preg_match($this->ignoreRegex, $this->msg) === 1;
181
	}
182
183
	/**
184
	 * Process the msg into its parts
185
	 *
186
	 * @return array
187
	 */
188 11
	private function processMsg() {
189 11
		$matches = [];
190
		
191 11
		preg_match($this->formatRegex, $this->msg, $matches);
192 11
		foreach ($this->validMsgParts as $i => $part) {
193 11
			$this->$part = isset($matches[$i]) ? $matches[$i] : '';
194
		}
195
196 11
		$this->lengthyLines = $this->findLengthyLines($this->msg, $this->maxLineLength);
197 11
	}
198
199
	/**
200
	 * Are all parts of the message valid
201
	 *
202
	 * @return bool
203
	 */
204
	public function isValid() {
205
		return $this->isValidFormat() &&
206
				$this->isValidLineLength() &&
207
				$this->isValidType();
208
	}
209
	
210
	/**
211
	 * Whether the message format conforms to our standards.
212
	 *
213
	 * @return boolean
214
	 */
215 6
	public function isValidFormat() {
216 6
		return preg_match($this->formatRegex, $this->msg) === 1;
217
	}
218
219
	/**
220
	 * Are any of the lines too long?
221
	 *
222
	 * @see getLengthyLines() to get line numbers
223
	 *
224
	 * @return bool
225
	 */
226 1
	public function isValidLineLength() {
227 1
		return count($this->lengthyLines) === 0;
228
	}
229
230
	/**
231
	 * Get the line number of lines that are too long
232
	 *
233
	 * @return array
234
	 */
235 1
	public function getLengthyLines() {
236 1
		return $this->lengthyLines;
237
	}
238
239
	/**
240
	 * Is the type valid
241
	 *
242
	 * @return boolean
243
	 */
244 1
	public function isValidType() {
245 1
		return in_array($this->type, self::$validTypes);
246
	}
247
248
	/**
249
	 * Return all valid types
250
	 *
251
	 * @return array
252
	 */
253 1
	public static function getValidTypes() {
254 1
		return self::$validTypes;
255
	}
256
257
	/**
258
	 * Return the max line length
259
	 *
260
	 * @return int
261
	 */
262
	public function getMaxLineLength() {
263
		return $this->maxLineLength;
264
	}
265
266
	/**
267
	 * Sets the max line length allowed.
268
	 * Defaults to 160.
269
	 *
270
	 * @param int $len The maximum length.
271
	 *
272
	 * @return void
273
	 */
274 2
	public function setMaxLineLength($len) {
275 2
		$this->maxLineLength = (int) $len;
276 2
	}
277
278
	/**
279
	 * Get part of the message
280
	 *
281
	 * @param string $part One section of the message.
282
	 *
283
	 * @return string
284
	 * @throws UnexpectedValueException
285
	 */
286 3
	public function getPart($part) {
287 3
		if ($part && in_array($part, $this->validMsgParts)) {
288 3
			return $this->$part;
289
		}
290
291
		throw new UnexpectedValueException("`$part` not a valid message part.");
292
	}
293
294
	/**
295
	 * Removes all lines that start with #
296
	 *
297
	 * @param string $msg The msg body of the commit
298
	 *
299
	 * @return string
300
	 */
301 12
	public static function removeComments($msg) {
302 12
		$msg_arr = [];
303 12
		foreach (explode("\n", rtrim($msg)) as $line) {
304 12
			if (substr($line, 0, 1) !== '#') {
305 12
				$msg_arr[] = $line;
306
			}
307
		}
308
309 12
		return implode("\n", $msg_arr);
310
	}
311
312
	/**
313
	 * Returns an array of line numbers > $max_len
314
	 *
315
	 * @param string $msg     The content to parse
316
	 * @param int    $max_len Maximum length between \n in the $msg
317
	 *
318
	 * @return array
319
	 */
320 12
	public static function findLengthyLines($msg, $max_len) {
321 12
		$lines = explode("\n", $msg);
322 12
		$lengthy_lines = [];
323
324 12
		foreach ($lines as $i => $line) {
325 12
			if (strlen($line) > $max_len) {
326 12
				$lengthy_lines[] = ++$i;
327
			}
328
		}
329
330 12
		return $lengthy_lines;
331
	}
332
333
	/**
334
	 * {@inheritDoc}
335
	 */
336
	public function __toString() {
337
		return $this->getMsg();
338
	}
339
}
340