Issues (22)

src/Parser/KeepAChangeLog.php (2 issues)

Severity
1
<?php
2
/**
3
 * @category Library
4
 * @license MIT http://opensource.org/licenses/MIT
5
 * @link https://github.com/emlynwest/changelog
6
 */
7
8
namespace ChangeLog\Parser;
9
10
use ChangeLog\Log;
11
use ChangeLog\ParserInterface;
12
use ChangeLog\Release;
13
use DateTime;
14
15
/**
16
 * Allows change logs to be parsed from the http://keepachangelog.com format.
17
 */
18
class KeepAChangeLog implements ParserInterface
19
{
20
21
	/**
22
	 * Takes the given content and parses it into a populated Log object.
23
	 *
24
	 * @param string[] $content
25
	 *
26
	 * @return Log
27
	 */
28 12
	public function parse($content)
29
	{
30 12
		$log = new Log;
31 12
		$description = [];
32 12
		$links = [];
33 12
		$matches = [];
34 12
		$bodyEnded = false;
35
36 12
		$line = current($content);
37 12
		while ($line !== false)
38
		{
39 12
			$line = ltrim($line);
40
41 12
			if (preg_match('/^#(?!#).+/', $line) === 1)
42
			{
43 12
				$log->setTitle($this->trimHashes($line));
44
			}
45 12
			elseif (preg_match('/^##(?!#).+/', $line) === 1)
46
			{
47 11
				$release = $this->parseRelease($content);
48 11
				$log->addRelease($release);
49 11
				$bodyEnded = true;
50
			}
51 12
			elseif (preg_match('/^\[(.+)\]: (.+)/', $line, $matches))
52
			{
53 10
				if (count($matches) >= 3)
54
				{
55 10
					$links[$matches[1]] = $matches[2];
56
				}
57 10
				$bodyEnded = true;
58
			}
59 12
			elseif ( ! $bodyEnded)
60
			{
61 12
				$description[] = $line;
62
			}
63
64 12
			$line = next($content);
65
		}
66
67 12
		$log->setDescription(implode("\n", $description));
68
69
		// Assign the releases their real links
70 12
		$this->assignLinks($log, $links);
71
72 12
		return $log;
73
	}
74
75
	/**
76
	 * Trims off whitespace and excess hashes from the start of a string.
77
	 *
78
	 * @param string $line
79
	 *
80
	 * @return string
81
	 */
82 13
	public function trimHashes($line)
83
	{
84 13
		return ltrim($line, "\t\n\r\0\x0B# ");
85
	}
86
87
	/**
88
	 * Returns true if $haystack starts with $needle.
89
	 *
90
	 * @param $haystack
91
	 * @param $needle
92
	 *
93
	 * @return bool
94
	 */
95 14
	public function startsWith($haystack, $needle)
96
	{
97 14
		return (substr($haystack, 0, strlen($needle)) === $needle);
98
	}
99
100
	/**
101
	 * Builds a release.
102
	 *
103
	 * @param string[] $content
104
	 *
105
	 * @return Release
106
	 */
107 14
	public function parseRelease(&$content)
108
	{
109 14
		$release = new Release;
110 14
		$types = [];
111 14
		$lastType = '';
112 14
		$nameSet = false;
113
114 14
		$line = current($content);
115 14
		while ($line !== false)
116
		{
117 14
			$line = ltrim($line);
118
119 14
			if ($this->startsWith($line, '###'))
120
			{
121 11
				$type = $this->trimHashes($line);
122 11
				$types[$type] = [];
123 11
				$lastType = $type;
124
			}
125 14
			elseif ($nameSet &&
0 ignored issues
show
Consider adding parentheses for clarity. Current Interpretation: ($nameSet && $this->star...>startsWith($line, '['), Probably Intended Meaning: $nameSet && ($this->star...startsWith($line, '['))
Loading history...
126 12
				$this->startsWith($line, '##') ||
127 14
				$this->startsWith($line, '[')
128
			)
129
			{
130 11
				prev($content);
131 11
				break;
132
			}
133 14
			elseif ($this->startsWith($line, '##'))
134
			{
135 14
				$this->handleName($release, $line);
136 14
				$nameSet = true;
137
			}
138
			else
139
			{
140 12
				$change = ltrim($line, "\t\n\r\0\x0B -");
141
142 12
				if ($change !== '')
143
				{
144 11
					$types[$lastType][] = $change;
145
				}
146
			}
147
148 14
			$line = next($content);
149
		}
150
151 14
		$release->setAllChanges($types);
152 14
		return $release;
153
	}
154
155
	/**
156
	 * Pulls out the needed information from a Release title and assigns that to the
157
	 * given release.
158
	 *
159
	 * @param Release $release
160
	 * @param string  $line
161
	 */
162 14
	protected function handleName(Release $release, $line)
163
	{
164 14
		$this->setName($release, $line);
165 14
		$this->setDate($release, $line);
166 14
		$this->setLink($release, $line);
167 14
		$this->setYanked($release, $line);
168
	}
169
170
	/**
171
	 * Extracts and sets the name of the link if there is one.
172
	 *
173
	 * @param Release $release
174
	 * @param string  $line
175
	 */
176 14
	protected function setLink(Release $release, $line)
177
	{
178 14
		$matches = [];
179
180 14
		if (preg_match('/^## \[([\w\.-\.]+)\](?:\[(\w+)\])?/', $line, $matches))
181
		{
182 10
			if (count($matches) >= 3)
183
			{
184 9
				$release->setLink($matches[2]);
185 9
				$release->setLinkName($matches[2]);
186
			}
187
			else
188
			{
189 10
				$release->setLink($matches[1]);
190
			}
191
		}
192
	}
193
194
	/**
195
	 * Extracts and sets the yanked flag.
196
	 *
197
	 * @param Release $release
198
	 * @param string  $line
199
	 */
200 14
	protected function setYanked(Release $release, $line)
201
	{
202 14
		if (preg_match('/\[YANKED\]$/i', $line))
203
		{
204 1
			$release->setYanked(true);
205
		}
206
	}
207
208
	/**
209
	 * Extracts and sets the release Date.
210
	 *
211
	 * @param Release $release
212
	 * @param string  $line
213
	 */
214 14
	protected function setDate(Release $release, $line)
215
	{
216 14
		$matches = [];
217 14
		if (preg_match('/[0-9]{4,}-[0-9]{2,}-[0-9]{2,}/', $line, $matches))
218
		{
219 11
			$date = DateTime::createFromFormat('Y-m-d', $matches[0]);
220 11
			if ($date)
0 ignored issues
show
$date is of type DateTime, thus it always evaluated to true.
Loading history...
221
			{
222 11
				$release->setDate($date);
223
			}
224
		}
225
	}
226
227
	/**
228
	 * Extracts and sets the Release name.
229
	 *
230
	 * @param Release $release
231
	 * @param string  $line
232
	 */
233 14
	protected function setName(Release $release, $line)
234
	{
235 14
		$matches = [];
236 14
		if (preg_match('/([\w\.-]{1,})/', $line, $matches))
237
		{
238 14
			$release->setName($matches[0]);
239
		}
240
	}
241
242
	/**
243
	 * @param $log
244
	 * @param $links
245
	 *
246
	 * @since
247
	 */
248 12
	protected function assignLinks($log, $links)
249
	{
250
		/** @var Release $release */
251 12
		foreach ($log as $release)
252
		{
253 11
			$link = null;
254 11
			$linkName = $release->getLink();
255 11
			if (isset($links[$linkName]))
256
			{
257 10
				$link = $links[$linkName];
258
			}
259 11
			$release->setLink($link);
260
		}
261
	}
262
263
}
264