Completed
Push — master ( ce3164...4a1015 )
by Sebastian
02:56
created

Cli::detectCmdLocation()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 7.0071

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 36
ccs 18
cts 19
cp 0.9474
rs 6.7272
cc 7
eloc 20
nc 9
nop 3
crap 7.0071
1
<?php
2
namespace phpbu\App\Util;
3
4
use RuntimeException;
5
6
/**
7
 * Cli utility
8
 *
9
 * @package    phpbu
10
 * @subpackage Util
11
 * @author     Sebastian Feldmann <[email protected]>
12
 * @copyright  Sebastian Feldmann <[email protected]>
13
 * @license    https://opensource.org/licenses/MIT The MIT License (MIT)
14
 * @link       http://phpbu.de/
15
 * @since      Class available since Release 1.0.0
16
 */
17
abstract class Cli
18
{
19
    /**
20
     * List of paths
21
     *
22
     * @var array
23
     */
24
    private static $basePaths = [];
25
26
    /**
27
     * List of console color codes.
28
     *
29
     * @var array
30
     */
31
    private static $ansiCodes = [
32
        'bold'       => 1,
33
        'fg-black'   => 30,
34
        'fg-red'     => 31,
35
        'fg-yellow'  => 33,
36
        'fg-cyan'    => 36,
37
        'fg-white'   => 37,
38
        'bg-red'     => 41,
39
        'bg-green'   => 42,
40
        'bg-yellow'  => 43
41
    ];
42
43
    /**
44
     * Optional command locations
45
     *
46
     * @var array
47 3
     */
48
    private static $optionalCommandLocations = [
49 3
        'mongodump' => [],
50 1
        'mysqldump' => [
51
            '/usr/local/mysql/bin/mysqldump', // Mac OS X
52 2
            '/usr/mysql/bin/mysqldump',       // Linux
53 2
        ],
54
        'tar'       => [],
55
    ];
56
57
    /**
58
     * Register a base path.
59
     *
60
     * @param  string $name
61
     * @param  string $path
62 1
     * @throws \RuntimeException
63
     */
64 1
    public static function registerBase($name, $path)
65 1
    {
66
        if (!self::isAbsolutePath($path)) {
67
            throw new RuntimeException(sprintf('path has to be absolute: %s', $path));
68
        }
69
        self::$basePaths[$name] = $path;
70
    }
71
72
73 125
    /**
74
     * Adds a new 'path' to the list of optional command locations.
75 125
     *
76
     * @param string $command
77
     * @param string $path
78
     */
79
    public static function addCommandLocation($command, $path)
80
    {
81
        self::$optionalCommandLocations[$command][] = $path;
82
    }
83
84
    /**
85 3
     * Returns the list of optional 'mysqldump' locations.
86
     *
87 3
     * @param  string $command
88 1
     * @return array
89
     */
90 2
    public static function getCommandLocations($command)
91
    {
92
        return isset(self::$optionalCommandLocations[$command]) ? self::$optionalCommandLocations[$command] : [];
93
    }
94
95
    /**
96
     * Retrieve a registered path.
97
     *
98
     * @param  string $name
99
     * @return string array
100
     * @throws \RuntimeException
101
     */
102 128
    public static function getBase($name)
103
    {
104
        if (!isset(self::$basePaths[$name])) {
105 128
            throw new RuntimeException(sprintf('base not registered: %s', $name));
106 120
        }
107 120
        return self::$basePaths[$name];
108 120
    }
109 1
110
    /**
111 119
     * Detect a given commands location.
112
     *
113
     * @param  string $cmd               The command to locate
114
     * @param  string $path              Directory where the command should be
115 8
     * @param  array  $optionalLocations Some fallback locations where to search for the command
116 8
     * @return string                    Absolute path to detected command including command itself
117 8
     * @throws \RuntimeException
118 8
     */
119
    public static function detectCmdLocation($cmd, $path = null, $optionalLocations = [])
120
    {
121 8
        // explicit path given
122
        if (null !== $path) {
123
            $command = $path . DIRECTORY_SEPARATOR . $cmd;
124 8
            $bin     = self::isExecutable($command);
125 8
            if (null === $bin) {
126 8
                throw new RuntimeException(sprintf('wrong path specified for \'%s\': %s', $cmd, $path));
127 8
            }
128 8
            return $bin;
129 6
        }
130
131 8
        // on nx systems use 'which' command.
132
        if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
133
            $command = `which $cmd`;
134 2
            $bin     = self::isExecutable($command);
135 1
            if (null !== $bin) {
136 1
                return $bin;
137 1
            }
138 1
        }
139
140 1
        // checking environment variable.
141 1
        $paths = explode(PATH_SEPARATOR, self::getEnvPath());
142
        $bin   = self::detectCmdLocationInPaths($paths, $cmd);
143
        if (null !== $bin) {
144
            return $bin;
145
        }
146
147
        // check optional locations
148
        $bin = self::detectCmdLocationInPaths($optionalLocations, $cmd);
149
        if (null !== $bin) {
150 9
            return $bin;
151
        }
152
153 9
        throw new RuntimeException(sprintf('\'%s\' was nowhere to be found please specify the correct path', $cmd));
154 9
    }
155 8
156
    /**
157 1
     * Check path list for executable command.
158 1
     *
159
     * @param array $paths
160
     * @param       $cmd
161
     * @return null|string
162
     */
163
    public static function detectCmdLocationInPaths(array $paths, $cmd)
164
    {
165
        foreach ($paths as $path) {
166
            $command = $path . DIRECTORY_SEPARATOR . $cmd;
167
            $bin     = self::isExecutable($command);
168 128
            if (null !== $bin) {
169
                return $bin;
170 128
            }
171 126
        }
172
        return null;
173
    }
174 9
175
    /**
176
     * Return local $PATH variable.
177
     *
178
     * @return string
179
     * @throws \RuntimeException
180 9
     */
181
    public static function getEnvPath()
182
    {
183
        // check for unix and windows case $_SERVER index
184
        foreach (['PATH', 'Path', 'path'] as $index) {
185
            if (isset($_SERVER[$index])) {
186
                return $_SERVER[$index];
187
            }
188
        }
189 21
        throw new RuntimeException('cant find local PATH variable');
190
    }
191
192 21
    /**
193 4
     * Returns the executable command if the command is executable, null otherwise.
194
     * Search for $command.exe on Windows systems.
195
     *
196
     * @param  string $command
197
     * @return string
198
     */
199
    public static function isExecutable($command)
200
    {
201
        if (is_executable($command)) {
202
            return $command;
203
        }
204 17
        // on windows check the .exe suffix
205
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
206
            $command .= '.exe';
207
            if (is_executable($command)) {
208
                return $command;
209 17
            }
210 1
        }
211
        return null;
212
    }
213 16
214
    /**
215
     * Is given path absolute.
216
     *
217
     * @param  string $path
218
     * @return boolean
219
     */
220
    public static function isAbsolutePath($path)
221
    {
222 3
        // path already absolute?
223
        if ($path[0] === '/') {
224 3
            return true;
225
        }
226
227
        // Matches the following on Windows:
228
        //  - \\NetworkComputer\Path
229
        //  - \\.\D:
230
        //  - \\.\c:
231
        //  - C:\Windows
232
        //  - C:\windows
233
        //  - C:/windows
234
        //  - c:/windows
235 17
        if (defined('PHP_WINDOWS_VERSION_BUILD') && self::isAbsoluteWindowsPath($path)) {
236
            return true;
237 17
        }
238 3
239
        // Stream
240
        if (strpos($path, '://') !== false) {
241 14
            return true;
242
        }
243 14
244 1
        return false;
245 1
    }
246 1
247 1
    /**
248 1
     * Is given path an absolute windows path.
249 14
     *
250
     * @param  string $path
251
     * @return bool
252
     */
253
    public static function isAbsoluteWindowsPath($path)
254
    {
255
        return ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]\:[/\\\]#i', substr($path, 0, 3))));
256
    }
257 1
258
    /**
259 1
     * Converts a path to an absolute one if necessary relative to a given base path.
260 1
     *
261 1
     * @param  string  $path
262
     * @param  string  $base
263 1
     * @param  boolean $useIncludePath
264 1
     * @return string
265 1
     */
266 1
    public static function toAbsolutePath($path, $base, $useIncludePath = false)
267
    {
268 1
        if (self::isAbsolutePath($path)) {
269 1
            return $path;
270 1
        }
271
272
        $file = $base . DIRECTORY_SEPARATOR . $path;
273
274
        if ($useIncludePath && !file_exists($file)) {
275
            $includePathFile = stream_resolve_include_path($path);
276
            if ($includePathFile) {
277
                $file = $includePathFile;
278
            }
279
        }
280
        return $file;
281
    }
282
283
    /**
284
     * Formats a buffer with a specified ANSI color sequence if colors are enabled.
285
     *
286
     * @author Sebastian Bergmann <[email protected]>
287
     * @param  string $color
288
     * @param  string $buffer
289
     * @return string
290
     */
291
    public static function formatWithColor($color, $buffer)
292
    {
293
        $codes   = array_map('trim', explode(',', $color));
294
        $lines   = explode("\n", $buffer);
295
        $padding = max(array_map('strlen', $lines));
296
297
        $styles = [];
298
        foreach ($codes as $code) {
299
            $styles[] = self::$ansiCodes[$code];
300
        }
301
        $style = sprintf("\x1b[%sm", implode(';', $styles));
302
303
        $styledLines = [];
304
        foreach ($lines as $line) {
305
            $styledLines[] = strlen($line) ? $style . str_pad($line, $padding) . "\x1b[0m" : '';
306
        }
307
308
        return implode(PHP_EOL, $styledLines);
309
    }
310
311
    /**
312
     * Fills up a text buffer with '*' to consume 72 chars.
313
     *
314
     * @param  string $buffer
315
     * @param  int    $length
316
     * @return string
317
     */
318
    public static function formatWithAsterisk($buffer, $length = 75)
319
    {
320
        return $buffer . str_repeat('*', $length - strlen($buffer)) . PHP_EOL;
321
    }
322
323
    /**
324
     * Removes a directory that is not empty.
325
     *
326
     * @param $dir
327
     */
328
    public static function removeDir($dir)
329
    {
330
        foreach (scandir($dir) as $file) {
331
            if ('.' === $file || '..' === $file) {
332
                continue;
333
            }
334
            if (is_dir($dir . '/' . $file)) {
335
                self::removeDir($dir . '/' . $file);
336
            } else {
337
                unlink($dir . '/' . $file);
338
            }
339
        }
340
        rmdir($dir);
341
    }
342
}
343