GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Test Failed
Pull Request — master (#2172)
by
unknown
02:10
created

functions.php ➔ runLocally()   B

Complexity

Conditions 6
Paths 13

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6.0045

Importance

Changes 0
Metric Value
cc 6
nc 13
nop 2
dl 0
loc 34
ccs 19
cts 20
cp 0.95
crap 6.0045
rs 8.7537
c 0
b 0
f 0
1
<?php
2
/* (c) Anton Medvedev <[email protected]>
3
 *
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace Deployer;
9
10
use Closure;
11
use Deployer\Exception\Exception;
12
use Deployer\Exception\GracefulShutdownException;
13
use Deployer\Exception\RunException;
14
use Deployer\Host\FileLoader;
15
use Deployer\Host\Host;
16
use Deployer\Host\Localhost;
17
use Deployer\Host\Range;
18
use Deployer\Support\Proxy;
19
use Deployer\Task\Context;
20
use Deployer\Task\GroupTask;
21
use Deployer\Task\Task as T;
22
use Symfony\Component\Console\Exception\MissingInputException;
23
use Symfony\Component\Console\Helper\QuestionHelper;
24
use Symfony\Component\Console\Input\InputInterface;
25
use Symfony\Component\Console\Input\InputOption;
26
use Symfony\Component\Console\Output\OutputInterface;
27
use Symfony\Component\Console\Question\ChoiceQuestion;
28
use Symfony\Component\Console\Question\ConfirmationQuestion;
29
use Symfony\Component\Console\Question\Question;
30
use function Deployer\Support\array_merge_alternate;
31
use function Deployer\Support\array_to_string;
32
use function Deployer\Support\str_contains;
33
34
/**
35
 * @param string ...$hostname
36
 * @return Host|Host[]|Proxy
37
 */
38
function host(...$hostname)
39
{
40 1
    $deployer = Deployer::get();
41 1
    $aliases = Range::expand($hostname);
42
43 1
    foreach ($aliases as $alias) {
44 1
        if ($deployer->hosts->has($alias)) {
45
            $host = $deployer->hosts->get($alias);
46
            throw new \InvalidArgumentException(
47
                "Host \"{$host->getTag()}\" already exists.\n" .
48
                "If you want to override configuration options, get host with <fg=yellow>getHost</> function.\n" .
49
                "\n" .
50
                "    <fg=yellow>getHost</>(<fg=green>'{$alias}'</>);" .
51
                "\n"
52
            );
53
        }
54
    }
55
56 1
    if (count($aliases) === 1) {
57 1
        $host = new Host($aliases[0]);
58 1
        $deployer->hosts->set($aliases[0], $host);
59 1
        return $host;
60
    } else {
61
        $hosts = array_map(function ($hostname) use ($deployer) {
62 1
            $host = new Host($hostname);
63 1
            $deployer->hosts->set($hostname, $host);
64 1
            return $host;
65 1
        }, $aliases);
66 1
        return new Proxy($hosts);
67
    }
68
}
69
70
/**
71
 * @param string ...$hostnames
72
 * @return Localhost|Localhost[]|Proxy
73
 */
74
function localhost(...$hostnames)
75
{
76 12
    $deployer = Deployer::get();
77 12
    $hostnames = Range::expand($hostnames);
78
79 12
    if (count($hostnames) <= 1) {
80 12
        $host = count($hostnames) === 1 ? new Localhost($hostnames[0]) : new Localhost();
81 12
        $deployer->hosts->set($host->getAlias(), $host);
82 12
        return $host;
83
    } else {
84
        $hosts = array_map(function ($hostname) use ($deployer) {
85
            $host = new Localhost($hostname);
86
            $deployer->hosts->set($host->getAlias(), $host);
87
            return $host;
88
        }, $hostnames);
89
        return new Proxy($hosts);
90
    }
91
}
92
93
/**
94
 * Get host by host alias.
95
 *
96
 * @param string $alias
97
 * @return Host
98
 */
99
function getHost(string $alias)
100
{
101 1
    return Deployer::get()->hosts->get($alias);
102
}
103
104
/**
105
 * Get current host.
106
 *
107
 * @return Host
108
 */
109
function currentHost()
110
{
111 8
    return Context::get()->getHost();
112
}
113
114
115
/**
116
 * Load list of hosts from file
117
 *
118
 * @param string $file
119
 * @return Proxy
120
 */
121
function inventory($file)
122
{
123
    $deployer = Deployer::get();
124
    $fileLoader = new FileLoader();
125
    $fileLoader->load($file);
126
127
    $hosts = $fileLoader->getHosts();
128
    foreach ($hosts as $host) {
129
        $deployer->hosts->set($host->getAlias(), $host);
130
    }
131
132
    return new Proxy($hosts);
133
}
134
135
/**
136
 * Set task description.
137
 *
138
 * @param string $title
139
 * @return string
140
 */
141
function desc($title = null)
142
{
143 19
    static $store = null;
144
145 19
    if ($title === null) {
146 19
        return $store;
147
    } else {
148 11
        return $store = $title;
149
    }
150
}
151
152
/**
153
 * Define a new task and save to tasks list.
154
 *
155
 * Alternatively get a defined task.
156
 *
157
 * @param string $name Name of current task.
158
 * @param callable|array|string|null $body Callable task, array of other tasks names or nothing to get a defined tasks
159
 * @return Task\Task
160
 */
161
function task($name, $body = null)
162
{
163 19
    $deployer = Deployer::get();
164
165 19
    if (empty($body)) {
166 14
        return $deployer->tasks->get($name);
167
    }
168
169 19
    if (is_callable($body)) {
170 19
        $task = new T($name, $body);
171 12
    } elseif (is_array($body)) {
172 12
        $task = new GroupTask($name, $body);
173
    } else {
174
        throw new \InvalidArgumentException('Task should be a closure or array of other tasks.');
175
    }
176
177 19
    $task->saveSourceLocation();
178 19
    $deployer->tasks->set($name, $task);
179
180 19
    if (!empty(desc())) {
181 11
        $task->desc(desc());
182 11
        desc(''); // Clear title.
183
    }
184
185 19
    return $task;
186
}
187
188
/**
189
 * Call that task before specified task runs.
190
 *
191
 * @param string $task The task before $that should be run.
192
 * @param string|callable $do The task to be run.
193
 * @return T|void
194
 */
195
function before($task, $do)
196
{
197 1
    if (is_callable($do)) {
198 1
        $newTask = task("before:$task", $do);
199 1
        before($task, "before:$task");
200 1
        return $newTask;
201
    }
202 1
    task($task)->addBefore($do);
203 1
}
204
205
/**
206
 * Call that task after specified task runs.
207
 *
208
 * @param string $task The task after $that should be run.
209
 * @param string|callable $do The task to be run.
210
 * @return T|void
211
 */
212
function after($task, $do)
213
{
214 12 View Code Duplication
    if (is_callable($do)) {
215 1
        $newTask = task("after:$task", $do);
216 1
        after($task, "after:$task");
217 1
        return $newTask;
218
    }
219 12
    task($task)->addAfter($do);
220 12
}
221
222
/**
223
 * Setup which task run on failure of first.
224
 *
225
 * @param string $task The task which need to fail so $that should be run.
226
 * @param string $do The task to be run.
227
 * @return T|void
228
 */
229
function fail($task, $do)
230
{
231 11 View Code Duplication
    if (is_callable($do)) {
232
        $newTask = task("fail:$task", $do);
233
        fail($task, "fail:$task");
234
        return $newTask;
235
    }
236 11
    $deployer = Deployer::get();
237 11
    $deployer->fail->set($task, $do);
238 11
}
239
240
/**
241
 * Add users options.
242
 *
243
 * @param string $name The option name
244
 * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
245
 * @param int|null $mode The option mode: One of the VALUE_* constants
246
 * @param string $description A description text
247
 * @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
248
 */
249
function option($name, $shortcut = null, $mode = null, $description = '', $default = null)
250
{
251 11
    Deployer::get()->inputDefinition->addOption(
252 11
        new InputOption($name, $shortcut, $mode, $description, $default)
253
    );
254 11
}
255
256
/**
257
 * Change the current working directory.
258
 *
259
 * @param string $path
260
 */
261
function cd($path)
262
{
263 6
    set('working_path', parse($path));
264 6
}
265
266
/**
267
 * Execute a callback within a specific directory and revert back to the initial working directory.
268
 *
269
 * @param string $path
270
 * @param callable $callback
271
 */
272
function within($path, $callback)
273
{
274
    $lastWorkingPath = get('working_path', '');
275
    try {
276
        set('working_path', parse($path));
277
        $callback();
278
    } finally {
279
        set('working_path', $lastWorkingPath);
280
    }
281
}
282
283
/**
284
 * Run command.
285
 *
286
 * @param string $command
287
 * @param array $options
288
 * @return string
289
 */
290
function run($command, $options = [])
291
{
292
    $run = function ($command, $options) {
293 11
        $host = Context::get()->getHost();
294
295 11
        $command = parse($command);
296 11
        $workingPath = get('working_path', '');
297
298 11
        if (!empty($workingPath)) {
299 6
            $command = "cd $workingPath && ($command)";
300
        }
301
302 11
        $env = array_merge_alternate(get('env', []), $options['env'] ?? []);
303 11
        if (!empty($env)) {
304 1
            $env = array_to_string($env);
305 1
            $command = "export $env; $command";
306
        }
307
308 11
        if ($host instanceof Localhost) {
309 11
            $process = Deployer::get()->processRunner;
310 11
            $output = $process->run($host, $command, $options);
311
        } else {
312
            $client = Deployer::get()->sshClient;
313
            $output = $client->run($host, $command, $options);
314
        }
315
316 11
        return rtrim($output);
317 11
    };
318
319 11
    if (preg_match('/^sudo\b/', $command)) {
320
        try {
321
            return $run($command, $options);
322
        } catch (RunException $exception) {
323
            $askpass = get('sudo_askpass', '/tmp/dep_sudo_pass');
324
            $password = get('sudo_pass', false);
325
            if ($password === false) {
326
                writeln("<fg=green;options=bold>run</> $command");
327
                $password = askHiddenResponse('Password:');
328
            }
329
            $run("echo -e '#!/bin/sh\necho \"%secret%\"' > $askpass", array_merge($options, ['secret' => $password]));
330
            $run("chmod a+x $askpass", $options);
331
            $run(sprintf('export SUDO_ASKPASS=%s; %s', $askpass, preg_replace('/^sudo\b/', 'sudo -A', $command)), $options);
332
            $run("rm $askpass", $options);
333
        }
334
    } else {
335 11
        return $run($command, $options);
336
    }
337
}
338
339
340
/**
341
 * Execute commands on local machine
342
 *
343
 * @param string|Closure $command Command to run locally, or a closure that will be executed in local machine context
344
 * @param array $options
345
 * @return string Output of command.
346
 */
347
function runLocally($command, $options = [])
348
{
349 6
    if ($command instanceof Closure) {
350 6
        $input = Context::has() ? input() : null;
351 6
        $output = Context::has() ? output() : null;
352
353 6
        $localhostContext = new Context(new Localhost(), $input, $output);
354 6
        Context::push($localhostContext);
355
        try {
356 6
            $output = $command();
357 6
            if (!is_string($output)) {
358
                $output = '';
359
            }
360 6
        } finally {
361 6
            Context::pop();
362
        }
363
364 6
        $output = rtrim($output);
365 6
        return $output;
366
    } else {
367 6
        $command = parse($command);
368 6
        $env = array_merge_alternate(get('env', []), $options['env'] ?? []);
369
370
        return runLocally(function () use ($command, $options, $env) {
371 6
            if (!empty($env)) {
372 1
                $env = array_to_string($env);
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $env, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
373 1
                $command = "export $env; $command";
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $command, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
374
            }
375
376 6
            $output = run($command, $options);
377 6
            return $output;
378 6
        });
379
    }
380
}
381
382
/**
383
 * Run test command.
384
 * Example:
385
 *
386
 *     test('[ -d {{release_path}} ]')
387
 *
388
 * @param string $command
389
 * @return bool
390
 */
391
function test($command)
392
{
393 8
    return run("if $command; then echo 'true'; fi") === 'true';
394
}
395
396
/**
397
 * Run test command locally.
398
 * Example:
399
 *
400
 *     testLocally('[ -d {{local_release_path}} ]')
401
 *
402
 * @param string $command
403
 * @return bool
404
 */
405
function testLocally($command)
406
{
407
    return runLocally("if $command; then echo 'true'; fi") === 'true';
408
}
409
410
/**
411
 * Iterate other hosts, allowing to call run func in callback.
412
 *
413
 * @experimental
414
 * @param Host|Host[] $hosts
415
 * @param callable $callback
416
 */
417
function on($hosts, callable $callback)
418
{
419
    $deployer = Deployer::get();
420
421
    if (!is_array($hosts) && !($hosts instanceof \Traversable)) {
422
        $hosts = [$hosts];
423
    }
424
425
    foreach ($hosts as $host) {
426
        if ($host instanceof Host) {
427
            $host->getConfig()->load();
428
            Context::push(new Context($host, input(), output()));
429
            try {
430
                $callback($host);
431
                $host->getConfig()->save();
432
            } catch (GracefulShutdownException $e) {
433
                $deployer->messenger->renderException($e, $host);
434
            } finally {
435
                Context::pop();
436
            }
437
        } else {
438
            throw new \InvalidArgumentException("Function on can iterate only on Host instances.");
439
        }
440
    }
441
}
442
443
/**
444
 * Run task
445
 *
446
 * @experimental
447
 * @param string $task
448
 */
449
function invoke($task)
450
{
451
    $hosts = [Context::get()->getHost()];
452
    $tasks = Deployer::get()->scriptManager->getTasks($task, $hosts);
0 ignored issues
show
Unused Code introduced by
The call to ScriptManager::getTasks() has too many arguments starting with $hosts.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
453
454
    $master = Deployer::get()->master;
455
    $master->run($tasks, $hosts);
456
}
457
458
/*
459
 * Upload file or directory to host.
460
 */
461 View Code Duplication
function upload(string $source, string $destination, $config = [])
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
462
{
463
    $rsync = Deployer::get()->rsync;
464
    $host = currentHost();
465
    $source = parse($source);
466
    $destination = parse($destination);
467
468
    if ($host instanceof Localhost) {
469
        $rsync->call($host, $source, $destination, $config);
470
    } else {
471
        $rsync->call($host, $source, "{$host->getConnectionString()}:$destination", $config);
472
    }
473
}
474
475
/*
476
 * Download file or directory from host
477
 */
478 View Code Duplication
function download(string $source, string $destination, $config = [])
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
479
{
480
    $rsync = Deployer::get()->rsync;
481
    $host = currentHost();
482
    $source = parse($source);
483
    $destination = parse($destination);
484
485
    if ($host instanceof Localhost) {
486
        $rsync->call($host, $source, $destination, $config);
487
    } else {
488
        $rsync->call($host, "{$host->getConnectionString()}:$source", $destination, $config);
489
    }
490
}
491
492
/**
493
 * Writes an info message.
494
 * @param string $message
495
 */
496
function info($message)
497
{
498 5
    writeln("<fg=green;options=bold>info</> " . parse($message));
499 5
}
500
501
/**
502
 * Writes an warning message.
503
 * @param string $message
504
 */
505
function warning($message)
506
{
507
    writeln("<fg=yellow;options=bold>warning</> <comment>" . parse($message) . "</comment>");
508
}
509
510
/**
511
 * Writes a message to the output and adds a newline at the end.
512
 * @param string|array $message
513
 * @param int $options
514
 */
515
function writeln($message, $options = 0)
516
{
517 8
    $host = currentHost();
518 8
    output()->writeln("[{$host->getTag()}] " . parse($message), $options);
519 8
}
520
521
/**
522
 * Writes a message to the output.
523
 * @param string $message
524
 * @param int $options
525
 */
526
function write($message, $options = 0)
527
{
528
    output()->write(parse($message), false, $options);
529
}
530
531
/**
532
 * Parse set values.
533
 *
534
 * @param string $value
535
 * @return string
536
 */
537
function parse($value)
538
{
539 12
    return Context::get()->getConfig()->parse($value);
540
}
541
542
/**
543
 * Setup configuration option.
544
 *
545
 * @param string $name
546
 * @param mixed $value
547
 */
548 View Code Duplication
function set($name, $value)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
549
{
550 11
    if (!Context::has()) {
551 11
        Deployer::get()->config->set($name, $value);
552
    } else {
553 6
        Context::get()->getConfig()->set($name, $value);
554
    }
555 11
}
556
557
/**
558
 * Merge new config params to existing config array.
559
 *
560
 * @param string $name
561
 * @param array $array
562
 */
563 View Code Duplication
function add($name, $array)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
564
{
565
    if (!Context::has()) {
566
        Deployer::get()->config->add($name, $array);
567
    } else {
568
        Context::get()->getConfig()->add($name, $array);
569
    }
570
}
571
572
/**
573
 * Get configuration value.
574
 *
575
 * @param string $name
576
 * @param mixed|null $default
577
 * @return mixed
578
 */
579 View Code Duplication
function get($name, $default = null)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
580
{
581 11
    if (!Context::has()) {
582
        return Deployer::get()->config->get($name, $default);
583
    } else {
584 11
        return Context::get()->getConfig()->get($name, $default);
585
    }
586
}
587
588
/**
589
 * Check if there is such configuration option.
590
 *
591
 * @param string $name
592
 * @return boolean
593
 */
594 View Code Duplication
function has($name)
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
595
{
596 4
    if (!Context::has()) {
597
        return Deployer::get()->config->has($name);
598
    } else {
599 4
        return Context::get()->getConfig()->has($name);
600
    }
601
}
602
603
/**
604
 * @param string $message
605
 * @param string|null $default
606
 * @param string[]|null $autocomplete
607
 * @return string
608
 */
609
function ask($message, $default = null, $autocomplete = null)
610
{
611
    Context::required(__FUNCTION__);
612
613
    if (output()->isQuiet()) {
614
        return $default;
615
    }
616
617
    /** @var QuestionHelper $helper */
618
    $helper = Deployer::get()->getHelper('question');
619
620
    $tag = currentHost()->getTag();
621
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
622
623
    $question = new Question($message, $default);
624
    if (!empty($autocomplete)) {
625
        $question->setAutocompleterValues($autocomplete);
626
    }
627
628
    // Master process will stop spinner when this env variable is true.
629
630
    try {
631
        return $helper->ask(input(), output(), $question);
632
    } catch (MissingInputException $exception) {
633
        throw new Exception("Failed to read input from stdin.\nMake sure what you are asking for input not from parallel task.", $exception->getCode(), $exception);
634
    } finally {
635
    }
636
}
637
638
/**
639
 * @param string $message
640
 * @param string[] $availableChoices
641
 * @param string|null $default
642
 * @param bool|false $multiselect
643
 * @return string|string[]
644
 */
645
function askChoice($message, array $availableChoices, $default = null, $multiselect = false)
646
{
647
    Context::required(__FUNCTION__);
648
649
    if (empty($availableChoices)) {
650
        throw new \InvalidArgumentException('Available choices should not be empty');
651
    }
652
653
    if ($default !== null && !array_key_exists($default, $availableChoices)) {
654
        throw new \InvalidArgumentException('Default choice is not available');
655
    }
656
657
    if (output()->isQuiet()) {
658
        if ($default === null) {
659
            $default = key($availableChoices);
660
        }
661
        return [$default => $availableChoices[$default]];
662
    }
663
664
    $helper = Deployer::get()->getHelper('question');
665
666
    $tag = currentHost()->getTag();
667
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
668
669
    $question = new ChoiceQuestion($message, $availableChoices, $default);
670
    $question->setMultiselect($multiselect);
671
672
    return $helper->ask(input(), output(), $question);
673
}
674
675
/**
676
 * @param string $message
677
 * @param bool $default
678
 * @return bool
679
 */
680
function askConfirmation($message, $default = false)
681
{
682
    Context::required(__FUNCTION__);
683
684
    if (output()->isQuiet()) {
685
        return $default;
686
    }
687
688
    $helper = Deployer::get()->getHelper('question');
689
690
    $yesOrNo = $default ? 'Y/n' : 'y/N';
691
    $tag = currentHost()->getTag();
692
    $message = "[$tag] <question>$message</question> [$yesOrNo] ";
693
694
    $question = new ConfirmationQuestion($message, $default);
695
696
    return $helper->ask(input(), output(), $question);
697
}
698
699
/**
700
 * @param string $message
701
 * @return string
702
 */
703
function askHiddenResponse($message)
704
{
705
    Context::required(__FUNCTION__);
706
707
    if (output()->isQuiet()) {
708
        return '';
709
    }
710
711
    $helper = Deployer::get()->getHelper('question');
712
713
    $tag = currentHost()->getTag();
714
    $message = "[$tag] <question>$message</question> ";
715
716
    $question = new Question($message);
717
    $question->setHidden(true);
718
    $question->setHiddenFallback(false);
719
720
    return $helper->ask(input(), output(), $question);
721
}
722
723
/**
724
 * @return InputInterface
725
 */
726
function input()
727
{
728 6
    return Context::get()->getInput();
729
}
730
731
732
/**
733
 * @return OutputInterface
734
 */
735
function output()
736
{
737 10
    return Context::get()->getOutput();
738
}
739
740
/**
741
 * Check if command exists
742
 *
743
 * @param string $command
744
 * @return bool
745
 */
746
function commandExist($command)
747
{
748 3
    return test("hash $command 2>/dev/null");
749
}
750
751
function commandSupportsOption($command, $option)
752
{
753 5
    $man = run("(man $command 2>&1 || $command -h 2>&1 || $command --help 2>&1) | grep -- $option || true");
754 5
    if (empty($man)) {
755
        return false;
756
    }
757 5
    return str_contains($man, $option);
758
}
759
760
function locateBinaryPath($name)
761
{
762 4
    $nameEscaped = escapeshellarg($name);
763
764
    // Try `command`, should cover all Bourne-like shells
765
    // Try `which`, should cover most other cases
766
    // Fallback to `type` command, if the rest fails
767 4
    $path = run("command -v $nameEscaped || which $nameEscaped || type -p $nameEscaped");
768 4
    if (empty($path)) {
769
        throw new \RuntimeException("Can't locate [$nameEscaped] - neither of [command|which|type] commands are available");
770
    }
771
772
    // Deal with issue when `type -p` outputs something like `type -ap` in some implementations
773 4
    return trim(str_replace("$name is", "", $path));
774
775
}
776