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.

askChoice()   B
last analyzed

Complexity

Conditions 8
Paths 7

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 72

Importance

Changes 0
Metric Value
cc 8
eloc 17
nc 7
nop 4
dl 0
loc 32
ccs 0
cts 12
cp 0
crap 72
rs 8.4444
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 Deployer\Exception\GracefulShutdownException;
11
use Deployer\Exception\RunException;
12
use Deployer\Host\FileLoader;
13
use Deployer\Host\Host;
14
use Deployer\Host\Localhost;
15
use Deployer\Host\Range;
16
use Deployer\Support\ObjectProxy;
17
use Deployer\Task\Context;
18
use Deployer\Task\GroupTask;
19
use Deployer\Task\Task;
20
use Symfony\Component\Console\Helper\QuestionHelper;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\Console\Question\ChoiceQuestion;
25
use Symfony\Component\Console\Question\ConfirmationQuestion;
26
use Symfony\Component\Console\Question\Question;
27
use function Deployer\Support\array_merge_alternate;
28
use function Deployer\Support\array_to_string;
29
use function Deployer\Support\str_contains;
30
31
/**
32
 * @param string ...$hostname
33
 * @return Host|Host[]|ObjectProxy
34
 */
35
function host(...$hostname)
36
{
37
    $deployer = Deployer::get();
38
    $aliases = Range::expand($hostname);
39
40 1
    foreach ($aliases as $alias) {
41 1
        if ($deployer->hosts->has($alias)) {
42
            $host = $deployer->hosts->get($alias);
43 1
            throw new \InvalidArgumentException(
44 1
                "Host \"{$host->getTag()}\" already exists.\n" .
45
                "If you want to override configuration options, get host with <fg=yellow>getHost</> function.\n" .
46
                "\n" .
47
                "    <fg=yellow>getHost</>(<fg=green>'{$alias}'</>);" .
48
                "\n"
49
            );
50
        }
51
    }
52
53
    if (count($aliases) === 1) {
54
        $host = new Host($aliases[0]);
55
        $deployer->hosts->set($aliases[0], $host);
56 1
        return $host;
57 1
    } else {
58 1
        $hosts = array_map(function ($hostname) use ($deployer) {
59 1
            $host = new Host($hostname);
60
            $deployer->hosts->set($hostname, $host);
61
            return $host;
62 1
        }, $aliases);
63 1
        return new ObjectProxy($hosts);
64 1
    }
65 1
}
66 1
67
/**
68
 * @param string ...$hostnames
69
 * @return Localhost|Localhost[]|ObjectProxy
70
 */
71
function localhost(...$hostnames)
72
{
73
    $deployer = Deployer::get();
74
    $hostnames = Range::expand($hostnames);
75
76 13
    if (count($hostnames) <= 1) {
77 13
        $host = count($hostnames) === 1 ? new Localhost($hostnames[0]) : new Localhost();
78
        $deployer->hosts->set($host->getAlias(), $host);
79 13
        return $host;
80 13
    } else {
81 13
        $hosts = array_map(function ($hostname) use ($deployer) {
82 13
            $host = new Localhost($hostname);
83
            $deployer->hosts->set($host->getAlias(), $host);
84
            return $host;
85
        }, $hostnames);
86
        return new ObjectProxy($hosts);
87
    }
88
}
89
90
/**
91
 * Get host by host alias.
92
 *
93
 * @param string $alias
94
 * @return Host
95
 */
96
function getHost(string $alias)
97
{
98
    return Deployer::get()->hosts->get($alias);
99
}
100
101 5
/**
102
 * Get current host.
103
 *
104
 * @return Host
105
 */
106
function currentHost()
107
{
108
    return Context::get()->getHost();
109
}
110
111 8
112
/**
113
 * Load list of hosts from file
114
 *
115
 * @param string $file
116
 * @return ObjectProxy
117
 */
118
function inventory($file)
119
{
120
    $deployer = Deployer::get();
121
    $fileLoader = new FileLoader();
122
    $fileLoader->load($file);
123
124
    $hosts = $fileLoader->getHosts();
125
    foreach ($hosts as $host) {
126
        $deployer->hosts->set($host->getAlias(), $host);
127
    }
128
129
    return new ObjectProxy($hosts);
130
}
131
132
/**
133
 * Set task description.
134
 */
135
function desc($title = null)
136
{
137
    static $store = null;
138
139
    if ($title === null) {
140
        return $store;
141
    } else {
142
        return $store = $title;
143 15
    }
144
}
145 15
146 15
/**
147
 * Define a new task and save to tasks list.
148 8
 *
149
 * Alternatively get a defined task.
150
 *
151
 * @param string $name Name of current task.
152
 * @param callable|array|string|null $body Callable task, array of other tasks names or nothing to get a defined tasks
153
 * @return Task
154
 */
155
function task($name, $body = null)
156
{
157
    $deployer = Deployer::get();
158
159
    if (empty($body)) {
160
        return $deployer->tasks->get($name);
161
    }
162
163 15
    if (is_callable($body)) {
164
        $task = new Task($name, $body);
165 15
    } elseif (is_array($body)) {
166 15
        $task = new GroupTask($name, $body);
167
    } else {
168
        throw new \InvalidArgumentException('Task should be a closure or array of other tasks.');
169 15
    }
170 15
171 9
    $task->saveSourceLocation();
172 9
    $deployer->tasks->set($name, $task);
173
174
    if (!empty(desc())) {
175
        $task->desc(desc());
176
        desc(''); // Clear title.
177 15
    }
178 15
179
    return $task;
180 15
}
181 8
182 8
/**
183
 * Call that task before specified task runs.
184
 *
185 15
 * @param string $task The task before $that should be run.
186
 * @param string|callable $do The task to be run.
187
 * @return Task|void
188
 */
189
function before($task, $do)
190
{
191
    if (is_callable($do)) {
192
        $newTask = task("before:$task", $do);
193
        before($task, "before:$task");
194
        return $newTask;
195
    }
196
    task($task)->addBefore($do);
0 ignored issues
show
Bug introduced by
It seems like $do can also be of type callable; however, parameter $task of Deployer\Task\Task::addBefore() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

196
    task($task)->addBefore(/** @scrutinizer ignore-type */ $do);
Loading history...
197 1
}
198 1
199 1
/**
200 1
 * Call that task after specified task runs.
201
 *
202 1
 * @param string $task The task after $that should be run.
203 1
 * @param string|callable $do The task to be run.
204
 * @return Task|void
205
 */
206
function after($task, $do)
207
{
208
    if (is_callable($do)) {
209
        $newTask = task("after:$task", $do);
210
        after($task, "after:$task");
211
        return $newTask;
212
    }
213
    task($task)->addAfter($do);
0 ignored issues
show
Bug introduced by
It seems like $do can also be of type callable; however, parameter $task of Deployer\Task\Task::addAfter() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

213
    task($task)->addAfter(/** @scrutinizer ignore-type */ $do);
Loading history...
214 13
}
215 5
216 5
/**
217 5
 * Setup which task run on failure of first.
218
 *
219 13
 * @param string $task The task which need to fail so $that should be run.
220 13
 * @param string $do The task to be run.
221
 * @return Task|void
222
 */
223
function fail($task, $do)
224
{
225
    if (is_callable($do)) {
226
        $newTask = task("fail:$task", $do);
227
        fail($task, "fail:$task");
228
        return $newTask;
229
    }
230
    $deployer = Deployer::get();
231 8
    $deployer->fail->set($task, $do);
232
}
233
234
/**
235
 * Add users options.
236 8
 *
237 8
 * @param string $name The option name
238 8
 * @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
239
 * @param int|null $mode The option mode: One of the VALUE_* constants
240
 * @param string $description A description text
241
 * @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE)
242
 */
243
function option($name, $shortcut = null, $mode = null, $description = '', $default = null)
244
{
245
    Deployer::get()->inputDefinition->addOption(
246
        new InputOption($name, $shortcut, $mode, $description, $default)
247
    );
248
}
249
250
/**
251 8
 * Change the current working directory.
252 8
 *
253
 * @param string $path
254 8
 */
255
function cd($path)
256
{
257
    set('working_path', parse($path));
258
}
259
260
/**
261
 * Execute a callback within a specific directory and revert back to the initial working directory.
262
 *
263 6
 * @param string $path
264 6
 * @param callable $callback
265
 * @return mixed|void Return value of the $callback function or void if callback doesn't return anything
266
 */
267
function within($path, $callback)
268
{
269
    $lastWorkingPath = get('working_path', '');
270
    try {
271
        set('working_path', parse($path));
272
        return $callback();
273
    } finally {
274
        set('working_path', $lastWorkingPath);
275
    }
276
}
277
278
/**
279
 * Executes given command on remote host.
280
 *
281
 * Options:
282
 * - `timeout` - Sets the process timeout (max. runtime). The timeout in seconds (default: 300 sec).
283
 * - `secret` - Placeholder `%secret%` can be used in command. Placeholder will be replaced with this value and will not appear in any logs.
284
 *
285
 * Examples:
286
 *
287
 * ```php
288
 * run('echo hello world');
289
 * run('cd {{deploy_path}} && git status');
290
 * run('password %secret%', ['secret' => getenv('CI_SECRET')]);
291
 * ```
292
 *
293 8
 * ```php
294
 * $path = run('readlink {{deploy_path}}/current');
295 8
 * run("echo $path");
296 8
 * ```
297
 *
298 8
 * @param string $command
299 6
 * @param array $options
300
 * @return string
301
 */
302 8
function run($command, $options = [])
303 8
{
304
    $run = function ($command, $options) {
305
        $host = Context::get()->getHost();
306
307
        $command = parse($command);
308 8
        $workingPath = get('working_path', '');
309 8
310 8
        if (!empty($workingPath)) {
311
            $command = "cd $workingPath && ($command)";
312
        }
313
314
        $env = array_merge_alternate(get('env', []), $options['env'] ?? []);
0 ignored issues
show
Bug introduced by
It seems like get('env', array()) can also be of type string; however, parameter $original of Deployer\Support\array_merge_alternate() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

314
        $env = array_merge_alternate(/** @scrutinizer ignore-type */ get('env', []), $options['env'] ?? []);
Loading history...
315
        if (!empty($env)) {
316 8
            $env = array_to_string($env);
317 8
            $command = "export $env; $command";
318
        }
319 8
320
        if ($host instanceof Localhost) {
321
            $process = Deployer::get()->processRunner;
322
            $output = $process->run($host, $command, $options);
323
        } else {
324
            $client = Deployer::get()->sshClient;
325
            $output = $client->run($host, $command, $options);
326
        }
327
328
        return rtrim($output);
329
    };
330
331
    if (preg_match('/^sudo\b/', $command)) {
332
        try {
333
            return $run($command, $options);
334
        } catch (RunException $exception) {
335 8
            $askpass = get('sudo_askpass', '/tmp/dep_sudo_pass');
336
            $password = get('sudo_pass', false);
337
            if ($password === false) {
338
                writeln("<fg=green;options=bold>run</> $command");
339
                $password = askHiddenResponse('Password:');
340
            }
341
            $run("echo -e '#!/bin/sh\necho \"%sudo_pass%\"' > $askpass", array_merge($options, ['sudo_pass' => $password]));
342
            $run("chmod a+x $askpass", $options);
343
            $run(sprintf('export SUDO_ASKPASS=%s; %s', $askpass, preg_replace('/^sudo\b/', 'sudo -A', $command)), $options);
344
            $run("rm $askpass", $options);
345
        }
346
    } else {
347
        return $run($command, $options);
348
    }
349 6
}
350 6
351
352 6
/**
353 6
 * Execute commands on local machine
354 1
 *
355 1
 * @param string $command Command to run locally.
356
 * @param array $options
357
 * @return string Output of command.
358 6
 */
359
function runLocally($command, $options = [])
360 6
{
361
    $process = Deployer::get()->processRunner;
362
    $command = parse($command);
363
364
    $env = array_merge_alternate(get('env', []), $options['env'] ?? []);
0 ignored issues
show
Bug introduced by
It seems like get('env', array()) can also be of type string; however, parameter $original of Deployer\Support\array_merge_alternate() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

449
    /** @scrutinizer ignore-call */ 
450
    $tasks = Deployer::get()->scriptManager->getTasks($task, $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. Please note the @ignore annotation hint above.

Loading history...
450
451
    $master = Deployer::get()->master;
452
    $master->run($tasks, $hosts);
453
}
454
455
/**
456
 * Upload file or directory to host.
457
 *
458
 * > You may have noticed that there is a trailing slash (/) at the end of the first argument in the above command, this is necessary to mean “the contents of build“.
459
 * >
460
 * > The alternative, without the trailing slash, would place build, including the directory, within public. This would create a hierarchy that looks like: {{release_path}}/public/build
461
 *
462
 * @param string $source
463
 * @param string $destination
464
 * @param array $config
465
 * @throws RunException
466
 */
467
function upload(string $source, string $destination, $config = [])
468
{
469
    $rsync = Deployer::get()->rsync;
470
    $host = currentHost();
471
    $source = parse($source);
472
    $destination = parse($destination);
473
474
    if ($host instanceof Localhost) {
475
        $rsync->call($host, $source, $destination, $config);
476
    } else {
477
        $rsync->call($host, $source, "{$host->getConnectionString()}:$destination", $config);
478
    }
479 4
}
480 4
481
/**
482
 * Download file or directory from host
483
 *
484
 * @param string $source
485
 * @param string $destination
486
 * @param array $config
487
 * @throws RunException
488
 */
489
function download(string $source, string $destination, $config = [])
490
{
491
    $rsync = Deployer::get()->rsync;
492
    $host = currentHost();
493
    $source = parse($source);
494
    $destination = parse($destination);
495
496
    if ($host instanceof Localhost) {
497
        $rsync->call($host, $source, $destination, $config);
498 7
    } else {
499 7
        $rsync->call($host, "{$host->getConnectionString()}:$source", $destination, $config);
500 7
    }
501
}
502
503
/**
504
 * Writes an info message.
505
 * @param string $message
506
 */
507
function info($message)
508
{
509
    writeln("<fg=green;options=bold>info</> " . parse($message));
510
}
511
512
/**
513
 * Writes an warning message.
514
 * @param string $message
515
 */
516
function warning($message)
517
{
518
    writeln("<fg=yellow;options=bold>warning</> <comment>" . parse($message) . "</comment>");
519
}
520 10
521
/**
522
 * Writes a message to the output and adds a newline at the end.
523
 * @param string|array $message
524
 * @param int $options
525
 */
526
function writeln($message, $options = 0)
527
{
528
    $host = currentHost();
529
    output()->writeln("[{$host->getTag()}] " . parse($message), $options);
0 ignored issues
show
Bug introduced by
It seems like $message can also be of type array; however, parameter $value of Deployer\parse() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

529
    output()->writeln("[{$host->getTag()}] " . parse(/** @scrutinizer ignore-type */ $message), $options);
Loading history...
530
}
531 12
532 12
/**
533
 * Writes a message to the output.
534 6
 * @param string $message
535
 * @param int $options
536 12
 */
537
function write($message, $options = 0)
538
{
539
    output()->write(parse($message), false, $options);
540
}
541
542
/**
543
 * Parse set values.
544
 *
545
 * @param string $value
546
 * @return string
547
 */
548
function parse($value)
549
{
550
    return Context::get()->getConfig()->parse($value);
551
}
552
553
/**
554
 * Setup configuration option.
555
 *
556
 * @param string $name
557
 * @param mixed $value
558
 */
559
function set($name, $value)
560
{
561
    if (!Context::has()) {
562 10
        Deployer::get()->config->set($name, $value);
563
    } else {
564
        Context::get()->getConfig()->set($name, $value);
565 10
    }
566
}
567
568
/**
569
 * Merge new config params to existing config array.
570
 *
571
 * @param string $name
572
 * @param array $array
573
 */
574
function add($name, $array)
575
{
576
    if (!Context::has()) {
577 4
        Deployer::get()->config->add($name, $array);
578
    } else {
579
        Context::get()->getConfig()->add($name, $array);
580 4
    }
581
}
582
583
/**
584
 * Get configuration value.
585
 *
586
 * @param string $name
587
 * @param mixed|null $default
588
 * @return mixed
589
 */
590
function get($name, $default = null)
591
{
592 1
    if (!Context::has()) {
593
        return Deployer::get()->config->get($name, $default);
594 1
    } else {
595
        return Context::get()->getConfig()->get($name, $default);
596
    }
597
}
598 1
599
/**
600
 * Check if there is such configuration option.
601
 *
602
 * @param string $name
603 1
 * @return boolean
604
 */
605 1
function has($name)
606 1
{
607
    if (!Context::has()) {
608 1
        return Deployer::get()->config->has($name);
609 1
    } else {
610
        return Context::get()->getConfig()->has($name);
611
    }
612
}
613 1
614
/**
615
 * @param string $message
616
 * @param string|null $default
617
 * @param string[]|null $autocomplete
618
 * @return string
619
 */
620
function ask($message, $default = null, $autocomplete = null)
621
{
622
    Context::required(__FUNCTION__);
623
624
    if (output()->isQuiet()) {
625
        return $default;
626
    }
627
628
    if (Deployer::isWorker()) {
629
        return Deployer::proxyCallToMaster(currentHost(), __FUNCTION__, ...func_get_args());
630
    }
631
632
    /** @var QuestionHelper $helper */
633
    $helper = Deployer::get()->getHelper('question');
634
635
    $tag = currentHost()->getTag();
636
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
637
638
    $question = new Question($message, $default);
639
    if (!empty($autocomplete)) {
640
        $question->setAutocompleterValues($autocomplete);
641
    }
642
643
    return $helper->ask(input(), output(), $question);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $helper->ask(input(), output(), $question) also could return the type boolean|string[] which is incompatible with the documented return type string.
Loading history...
644
}
645
646
/**
647
 * @param string $message
648
 * @param string[] $availableChoices
649
 * @param string|null $default
650
 * @param bool|false $multiselect
651
 * @return string|string[]
652
 */
653
function askChoice($message, array $availableChoices, $default = null, $multiselect = false)
654
{
655
    Context::required(__FUNCTION__);
656
657
    if (empty($availableChoices)) {
658
        throw new \InvalidArgumentException('Available choices should not be empty');
659
    }
660
661
    if ($default !== null && !array_key_exists($default, $availableChoices)) {
662
        throw new \InvalidArgumentException('Default choice is not available');
663
    }
664
665
    if (output()->isQuiet()) {
666
        if ($default === null) {
667
            $default = key($availableChoices);
668
        }
669
        return [$default => $availableChoices[$default]];
670
    }
671
672
    if (Deployer::isWorker()) {
673
        return Deployer::proxyCallToMaster(currentHost(), __FUNCTION__, ...func_get_args());
674
    }
675
676
    $helper = Deployer::get()->getHelper('question');
677
678
    $tag = currentHost()->getTag();
679
    $message = "[$tag] <question>$message</question> " . (($default === null) ? "" : "(default: $default) ");
680
681
    $question = new ChoiceQuestion($message, $availableChoices, $default);
682
    $question->setMultiselect($multiselect);
683
684
    return $helper->ask(input(), output(), $question);
0 ignored issues
show
Bug introduced by
The method ask() does not exist on Symfony\Component\Console\Helper\Helper. It seems like you code against a sub-type of Symfony\Component\Console\Helper\Helper such as Symfony\Component\Console\Helper\QuestionHelper. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

684
    return $helper->/** @scrutinizer ignore-call */ ask(input(), output(), $question);
Loading history...
685
}
686
687
/**
688
 * @param string $message
689
 * @param bool $default
690
 * @return bool
691
 */
692
function askConfirmation($message, $default = false)
693
{
694
    Context::required(__FUNCTION__);
695
696
    if (output()->isQuiet()) {
697
        return $default;
698
    }
699
700
    if (Deployer::isWorker()) {
701
        return Deployer::proxyCallToMaster(currentHost(), __FUNCTION__, ...func_get_args());
702
    }
703
704
    $helper = Deployer::get()->getHelper('question');
705
706
    $yesOrNo = $default ? 'Y/n' : 'y/N';
707
    $tag = currentHost()->getTag();
708
    $message = "[$tag] <question>$message</question> [$yesOrNo] ";
709
710
    $question = new ConfirmationQuestion($message, $default);
711
712
    return $helper->ask(input(), output(), $question);
713
}
714
715
/**
716
 * @param string $message
717
 * @return string
718 5
 */
719
function askHiddenResponse(string $message)
720
{
721
    Context::required(__FUNCTION__);
722
723
    if (output()->isQuiet()) {
724
        return '';
725
    }
726
727 8
    if (Deployer::isWorker()) {
728
        return Deployer::proxyCallToMaster(currentHost(), __FUNCTION__, ...func_get_args());
729
    }
730
731
    $helper = Deployer::get()->getHelper('question');
732
733
    $tag = currentHost()->getTag();
734
    $message = "[$tag] <question>$message</question> ";
735
736
    $question = new Question($message);
737
    $question->setHidden(true);
738 3
    $question->setHiddenFallback(false);
739
740
    return $helper->ask(input(), output(), $question);
741
}
742
743 5
/**
744 5
 * @return InputInterface
745
 */
746
function input()
747 5
{
748
    return Context::get()->getInput();
749
}
750
751
752 4
/**
753
 * @return OutputInterface
754
 */
755
function output()
756
{
757 4
    return Context::get()->getOutput();
758 4
}
759
760
/**
761
 * Check if command exists
762
 *
763 4
 * @param string $command
764
 * @return bool
765
 */
766
function commandExist($command)
767
{
768
    return test("hash $command 2>/dev/null");
769
}
770
771
function commandSupportsOption($command, $option)
772
{
773
    $man = run("(man $command 2>&1 || $command -h 2>&1 || $command --help 2>&1) | grep -- $option || true");
774
    if (empty($man)) {
775
        return false;
776
    }
777
    return str_contains($man, $option);
778
}
779
780
function locateBinaryPath($name)
781
{
782
    $nameEscaped = escapeshellarg($name);
783
784
    // Try `command`, should cover all Bourne-like shells
785
    // Try `which`, should cover most other cases
786
    // Fallback to `type` command, if the rest fails
787
    $path = run("command -v $nameEscaped || which $nameEscaped || type -p $nameEscaped");
788
    if (empty($path)) {
789
        throw new \RuntimeException("Can't locate [$nameEscaped] - neither of [command|which|type] commands are available");
790
    }
791
792
    // Deal with issue when `type -p` outputs something like `type -ap` in some implementations
793
    return trim(str_replace("$name is", "", $path));
794
795
}
796