Completed
Push — fix/parent-selector-for-premiu... ( cac9af...425486 )
by Jeremy
20:54 queued 09:58
created

Config::setComposerJsonPath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
2
/**
3
 * Configuration loader for the changelogger tool.
4
 *
5
 * @package automattic/jetpack-changelogger
6
 */
7
8
// phpcs:disable WordPress.NamingConventions.ValidFunctionName, WordPress.NamingConventions.ValidVariableName, WordPress.WP.AlternativeFunctions
9
10
namespace Automattic\Jetpack\Changelogger;
11
12
use Symfony\Component\Console\Output\OutputInterface;
13
14
/**
15
 * Configuration loader for the changelogger tool.
16
 */
17
class Config {
18
19
	/**
20
	 * Default config settings.
21
	 *
22
	 * @var array
23
	 */
24
	private static $defaultConfig = array(
25
		'changelog'     => 'CHANGELOG.md',
26
		'changes-dir'   => 'changelog',
27
		'link-template' => null,
28
		'ordering'      => array( 'subheading', 'content' ),
29
		'formatter'     => 'keepachangelog',
30
		'types'         => array(
31
			'security'   => 'Security',
32
			'added'      => 'Added',
33
			'changed'    => 'Changed',
34
			'deprecated' => 'Deprecated',
35
			'removed'    => 'Removed',
36
			'fixed'      => 'Fixed',
37
		),
38
		'versioning'    => 'semver',
39
	);
40
41
	/**
42
	 * Active config settings.
43
	 *
44
	 * @var array
45
	 */
46
	private static $config = array();
47
48
	/**
49
	 * Cached config settings.
50
	 *
51
	 * @var array
52
	 */
53
	private static $cache = array();
54
55
	/**
56
	 * Whether `load()` was called already.
57
	 *
58
	 * @var bool
59
	 */
60
	private static $loaded = false;
61
62
	/**
63
	 * Location of composer.json, overriding any COMPOSER environment variable.
64
	 *
65
	 * @var string|null
66
	 */
67
	private static $composerJsonPath;
68
69
	/**
70
	 * Set the location of composer.json, overriding auto-detection.
71
	 *
72
	 * @since 1.2.0
73
	 * @param string|null $path Path to composer.json, or null to re-enable auto-detection.
74
	 */
75
	public static function setComposerJsonPath( $path ) {
76
		self::$composerJsonPath = $path;
77
	}
78
79
	/**
80
	 * Former method used to set an OutputInterface. No longer used.
81
	 *
82
	 * @deprecated since 1.2.0, no longer needed.
83
	 * @param OutputInterface $out Ignored.
84
	 */
85
	public static function setOutput( OutputInterface $out ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
86
	}
87
88
	/**
89
	 * Load the configuration.
90
	 *
91
	 * @throws \DomainException If the path to composer.json exists but can't be `realpath`-ed.
92
	 * @throws ConfigException If composer.json is not found or is invalid (since 1.2.0).
93
	 */
94
	private static function load() {
95
		if ( self::$loaded ) {
96
			return;
97
		}
98
		self::$loaded = true;
99
100
		self::$config         = self::$defaultConfig;
101
		self::$config['base'] = getcwd();
102
103
		$from = '';
104
		if ( null !== self::$composerJsonPath ) {
105
			$composer = self::$composerJsonPath;
106
		} else {
107
			$composer = getenv( 'COMPOSER' );
108
			if ( $composer ) {
109
				$from = ' (as specified by the COMPOSER environment variable)';
110
			} else {
111
				$composer = getcwd() . DIRECTORY_SEPARATOR . 'composer.json';
112
			}
113
		}
114
		if ( ! file_exists( $composer ) ) {
115
			throw new ConfigException( "File {$composer}{$from} is not found." );
116
		}
117
		$data = json_decode( file_get_contents( $composer ), true );
118
		if ( ! is_array( $data ) ) {
119
			throw new ConfigException( "File {$composer}{$from} could not be parsed." );
120
		}
121
122
		$dir = realpath( $composer );
123
		if ( false === $dir ) {
124
			throw new \DomainException( "Path $composer is not valid" ); // @codeCoverageIgnore
125
		}
126
		self::$config['base'] = dirname( $dir );
127
		if ( isset( $data['extra']['changelogger'] ) ) {
128
			self::$config = array_merge( self::$config, $data['extra']['changelogger'] );
129
		}
130
	}
131
132
	/**
133
	 * Get the base directory.
134
	 *
135
	 * @return string
136
	 */
137
	public static function base() {
138
		self::load();
139
		return self::$config['base'];
140
	}
141
142
	/**
143
	 * Add the base directory to a path, if necessary.
144
	 *
145
	 * @param string $path Path.
146
	 * @return string
147
	 */
148
	private static function addBase( $path ) {
149
		// Stupid Windows requires a regex.
150
		if ( ! preg_match( '#^(?:/|' . preg_quote( DIRECTORY_SEPARATOR, '#' ) . '|[a-zA-Z]:\\\\)#', $path ) ) {
151
			$path = self::base() . DIRECTORY_SEPARATOR . $path;
152
		}
153
		return $path;
154
	}
155
156
	/**
157
	 * Get the changelog filename.
158
	 *
159
	 * @return string
160
	 */
161 View Code Duplication
	public static function changelogFile() {
162
		self::load();
163
		if ( ! isset( self::$cache['changelog'] ) ) {
164
			self::$cache['changelog'] = self::addBase( self::$config['changelog'] );
165
		}
166
		return self::$cache['changelog'];
167
	}
168
169
	/**
170
	 * Get the changes directory.
171
	 *
172
	 * @return string
173
	 */
174 View Code Duplication
	public static function changesDir() {
175
		self::load();
176
		if ( ! isset( self::$cache['changes-dir'] ) ) {
177
			self::$cache['changes-dir'] = self::addBase( self::$config['changes-dir'] );
178
		}
179
		return self::$cache['changes-dir'];
180
	}
181
182
	/**
183
	 * Get the link.
184
	 *
185
	 * @param string $old Old version number.
186
	 * @param string $new New version number.
187
	 * @return string|null
188
	 */
189
	public static function link( $old, $new ) {
190
		self::load();
191
		if ( null !== self::$config['link-template'] ) {
192
			return strtr(
193
				self::$config['link-template'],
194
				array(
195
					'${old}' => rawurlencode( $old ),
196
					'${new}' => rawurlencode( $new ),
197
				)
198
			);
199
		}
200
		return null;
201
	}
202
203
	/**
204
	 * Get change entry ordering.
205
	 *
206
	 * @return string[]
207
	 */
208
	public static function ordering() {
209
		self::load();
210
		if ( ! isset( self::$cache['ordering'] ) ) {
211
			self::$cache['ordering'] = array_map( 'strval', (array) self::$config['ordering'] );
212
		}
213
		return self::$cache['ordering'];
214
	}
215
216
	/**
217
	 * Get change types.
218
	 *
219
	 * @return array
220
	 */
221
	public static function types() {
222
		self::load();
223
		if ( ! isset( self::$cache['types'] ) ) {
224
			self::$cache['types'] = array();
225
			foreach ( self::$config['types'] as $k => $v ) {
226
				self::$cache['types'][ strtolower( $k ) ] = $v;
227
			}
228
		}
229
		return self::$cache['types'];
230
	}
231
232
	/**
233
	 * Get a plugin.
234
	 *
235
	 * @param string|array $config Plugin name or configuration array.
236
	 * @param string       $suffix Plugin class suffix.
237
	 * @param string       $interface Expected interface name.
238
	 * @return object|null Object, or null if the plugin was not found.
239
	 */
240
	private static function getPlugin( $config, $suffix, $interface ) {
241
		if ( is_string( $config ) ) {
242
			$config = array( 'name' => $config );
243
		}
244
245
		if ( isset( $config['name'] ) ) {
246
			$class = __NAMESPACE__ . '\\Plugins\\' . ucfirst( $config['name'] ) . $suffix;
247
		} elseif ( isset( $config['class'] ) ) {
248
			$class = $config['class'];
249
		} elseif ( isset( $config['filename'] ) ) {
250
			$classes = get_declared_classes();
251
			require $config['filename'];
252
			$classes = array_filter(
253
				array_diff( get_declared_classes(), $classes ),
254
				function ( $class ) use ( $interface ) {
255
					return is_a( $class, $interface, true );
256
				}
257
			);
258
			if ( count( $classes ) !== 1 ) {
259
				return null;
260
			}
261
			$class = array_pop( $classes );
262
		} else {
263
			return null;
264
		}
265
		if ( ! class_exists( $class ) || ! is_a( $class, $interface, true ) ) {
266
			return null;
267
		}
268
		return $class::instantiate( $config );
269
	}
270
271
	/**
272
	 * Get formatting plugin.
273
	 *
274
	 * @return Formatter
275
	 * @throws \RuntimeException If the configured formatter is unknown.
276
	 */
277 View Code Duplication
	public static function formatterPlugin() {
278
		self::load();
279
		if ( ! isset( self::$cache['formatter'] ) ) {
280
			$obj = self::getPlugin( self::$config['formatter'], 'Formatter', FormatterPlugin::class );
281
			if ( ! $obj instanceof FormatterPlugin ) {
282
				$info = json_encode( self::$config['formatter'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
283
				throw new \RuntimeException( "Unknown formatter plugin $info" );
284
			}
285
			self::$cache['formatter'] = $obj;
286
		}
287
		return self::$cache['formatter'];
288
	}
289
290
	/**
291
	 * Get verisoning plugin.
292
	 *
293
	 * @return Versioning
294
	 * @throws \RuntimeException If the configured versioning plugin is unknown.
295
	 */
296 View Code Duplication
	public static function versioningPlugin() {
297
		self::load();
298
		if ( ! isset( self::$cache['versioning'] ) ) {
299
			$obj = self::getPlugin( self::$config['versioning'], 'Versioning', VersioningPlugin::class );
300
			if ( ! $obj instanceof VersioningPlugin ) {
301
				$info = json_encode( self::$config['versioning'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
302
				throw new \RuntimeException( "Unknown versioning plugin $info" );
303
			}
304
			self::$cache['versioning'] = $obj;
305
		}
306
		return self::$cache['versioning'];
307
	}
308
309
}
310