Completed
Push — add/changelog-tooling ( 3f93f3...c37726 )
by
unknown
250:15 queued 241:25
created

Utils::loadChangeFile()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 10
nop 2
dl 0
loc 61
rs 7.2953
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php // phpcs:ignore WordPress.Files.FileName
2
/**
3
 * Utilities for the changelogger tool.
4
 *
5
 * @package automattic/jetpack-changelogger
6
 */
7
8
// phpcs:disable WordPress.NamingConventions.ValidFunctionName.MethodNameInvalid, WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
9
10
namespace Automattic\Jetpack\Changelogger;
11
12
use Symfony\Component\Console\Helper\DebugFormatterHelper;
13
use Symfony\Component\Console\Output\OutputInterface;
14
use Symfony\Component\Process\Process;
15
use function error_clear_last; // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.error_clear_lastFound
16
use function Wikimedia\quietCall;
17
18
/**
19
 * Utilities for the changelogger tool.
20
 */
21
class Utils {
22
23
	/**
24
	 * Calls `error_clear_last()` or emulates it.
25
	 */
26
	public static function error_clear_last() {
27
		if ( is_callable( 'error_clear_last' ) ) {
28
			// phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.error_clear_lastFound
29
			error_clear_last();
30
		} else {
31
			// @codeCoverageIgnoreStart
32
			quietCall( 'trigger_error', '', E_USER_NOTICE );
33
			// @codeCoverageIgnoreEnd
34
		}
35
	}
36
37
	/**
38
	 * Helper to run a process.
39
	 *
40
	 * @param string[]             $command Command to execute.
41
	 * @param OutputInterface      $output OutputInterface to write debug output to.
42
	 * @param DebugFormatterHelper $formatter Formatter to use to format debug output.
43
	 * @param array                $options An associative array with the following optional keys. Defaults are null unless otherwise specified.
44
	 *                 - cwd: (string|null) The working directory or null to use the working dir of the current PHP process.
45
	 *                 - env: (array|null) The environment variables or null to use the same environment as the current PHP process.
46
	 *                 - input: (mixed|null) The input as stream resource, scalar or \Traversable, or null for no input.
47
	 *                 - timeout: (float|null) The timeout in seconds or null to disable. Default 60.
48
	 *                 - mustRun: (boolean) If set true, an exception will be thrown if the command fails. Default false.
49
	 * @return Process The process, which has already been run.
50
	 */
51
	public static function runCommand( array $command, OutputInterface $output, DebugFormatterHelper $formatter, array $options = array() ) {
52
		$options += array(
53
			'cwd'     => null,
54
			'env'     => null,
55
			'input'   => null,
56
			'timeout' => 60,
57
			'mustRun' => false,
58
		);
59
60
		$process = new Process( $command, $options['cwd'], $options['env'], $options['input'], $options['timeout'] );
61
		$output->writeln(
62
			$formatter->start( spl_object_hash( $process ), $process->getCommandLine() ),
63
			OutputInterface::VERBOSITY_DEBUG
64
		);
65
		$func = $options['mustRun'] ? 'mustRun' : 'run';
66
		$process->$func(
67
			function ( $type, $buffer ) use ( $output, $formatter, $process ) {
68
				$output->writeln(
69
					$formatter->progress( spl_object_hash( $process ), $buffer, Process::ERR === $type ),
70
					OutputInterface::VERBOSITY_DEBUG
71
				);
72
			}
73
		);
74
		return $process;
75
	}
76
77
	/**
78
	 * Load and parse a change file to an array.
79
	 *
80
	 * Header names are normalized. The entry is returned under the empty
81
	 * string key.
82
	 *
83
	 * @param string $filename File to load.
84
	 * @param mixed  $diagnostics Output variable, set to an array with diagnostic data.
85
	 *   - warnings: An array of warning messages and applicable lines.
86
	 *   - lines: An array mapping headers to line numbers.
87
	 * @return array
88
	 * @throws \RuntimeException On error.
89
	 */
90
	public static function loadChangeFile( $filename, &$diagnostics = null ) {
91
		$diagnostics = array(
92
			'warnings' => array(),
93
			'lines'    => array(),
94
		);
95
96
		if ( ! file_exists( $filename ) ) {
97
			$ex           = new \RuntimeException( 'File does not exist.' );
98
			$ex->fileLine = null;
0 ignored issues
show
Bug introduced by
The property fileLine does not seem to exist in RuntimeException.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
99
			throw $ex;
100
		}
101
102
		$fileinfo = new \SplFileInfo( $filename );
103
		if ( $fileinfo->getType() !== 'file' ) {
104
			$ex           = new \RuntimeException( "Expected a file, got {$fileinfo->getType()}." );
105
			$ex->fileLine = null;
106
			throw $ex;
107
		}
108
		if ( ! $fileinfo->isReadable() ) {
109
			$ex           = new \RuntimeException( 'File is not readable.' );
110
			$ex->fileLine = null;
111
			throw $ex;
112
		}
113
114
		self::error_clear_last();
115
		$contents = quietCall( 'file_get_contents', $filename );
116
		// @codeCoverageIgnoreStart
117
		if ( false === $contents ) {
118
			$err          = error_get_last();
119
			$ex           = new \RuntimeException( "Failed to read file: {$err['message']}" );
120
			$ex->fileLine = null;
121
			throw $ex;
122
		}
123
		// @codeCoverageIgnoreEnd
124
125
		$ret  = array();
126
		$line = 1;
127
		while ( preg_match( '/^([A-Z][a-zA-Z0-9-]*):((?:.|\n[ \t])*)(?:\n|$)/', $contents, $m ) ) {
128
			if ( isset( $diagnostics['lines'][ $m[1] ] ) ) {
129
				$diagnostics['warnings'][] = array(
130
					"Duplicate header \"{$m[1]}\", previously seen on line {$diagnostics['lines'][ $m[1] ]}.",
131
					$line,
132
				);
133
			} else {
134
				$diagnostics['lines'][ $m[1] ] = $line;
135
				$ret[ $m[1] ]                  = trim( preg_replace( '/(\n[ \t]+)+/', ' ', $m[2] ) );
136
			}
137
			$line    += substr_count( $m[0], "\n" );
138
			$contents = (string) substr( $contents, strlen( $m[0] ) );
139
		}
140
141
		if ( '' !== $contents && "\n" !== $contents[0] ) {
142
			$ex           = new \RuntimeException( 'Invalid header.' );
143
			$ex->fileLine = $line;
144
			throw $ex;
145
		}
146
		$diagnostics['lines'][''] = $line + strspn( $contents, "\n" );
147
		$ret['']                  = trim( $contents );
148
149
		return $ret;
150
	}
151
152
}
153
154